You drag an SVG icon, a WebP photo, or an iPhone HEIC into the media library and WordPress refuses it with Sorry, you are not allowed to upload this file type. (or, on older sites, the alternate wording Sorry, this file type is not permitted for security reasons.). The file is fine. The user account has full editor or admin rights. The error is not about the file's contents or your permissions inside WordPress. It is about whether the file's MIME type appears in the allow-list WordPress consults at upload time, and that list is conservative on purpose.
What makes this confusing is that the right fix is different for almost every file type. SVG is blocked by design and needs sanitisation, not just an allow-list entry. WebP has been supported natively since WordPress 5.8 but only if the server's image library can handle it. HEIC has been supported since WordPress 6.7 but only if Imagick was compiled with HEIC. The ALLOW_UNFILTERED_UPLOADS constant looks like a one-line fix and it is, but the price is that any compromised admin can upload an executable PHP file directly through the media uploader. This article walks through what the error actually means, the canonical filter-based fix for legitimate custom MIME types, and the right per-format approach for the three formats people hit most.
Why WordPress restricts file types in the first place
The upload gate exists because the WordPress media library is, from a server's point of view, an unauthenticated upload directory. Anything stored there gets a stable URL that anyone on the internet can request. Allowing arbitrary file types turns that directory into a place where an admin (or an attacker who has compromised an admin account) can drop a .php shell, an HTML file with embedded JavaScript, or an SVG with a <script> block, and then trigger it from a single GET request. The allow-list is the line of defence between "user uploads photos" and "user uploads a backdoor".
Internally, WordPress runs every upload through two sequential checks. First, the upload_mimes filter (which has existed since WordPress 2.0) returns a list of allowed extensions and their MIME types. The list is built from wp_get_mime_types() and then trimmed by capability: users without unfiltered_html lose htm|html and js, and swf and exe are removed for everyone regardless of role. Second, wp_check_filetype_and_ext() (since WordPress 3.0) reads the file's actual contents with PHP finfo and getimagesize and confirms that the bytes match the claimed extension. A file has to pass both gates to be accepted. That second gate is why simply adding SVG to the allow-list does not work on every host, as I cover further down.
The canonical fix: adding a MIME type with the upload_mimes filter
For most legitimate custom file types (a custom font, a vendor format your business uses, a .zip you keep getting blocked), the right fix is a small upload_mimes filter in a must-use plugin or functions.php. The pattern is documented in the upload_mimes hook reference and looks like this:
// wp-content/mu-plugins/allow-extra-mimes.php
// WordPress 6.5+, PHP 8.1+
add_filter( 'upload_mimes', function ( $mimes ) {
$mimes['woff'] = 'font/woff';
$mimes['woff2'] = 'font/woff2';
return $mimes;
} );
Two details that trip people up. The array key is the file extension (or a regex of extensions, separated by |, which is how WordPress core registers things like 'jpg|jpeg|jpe' => 'image/jpeg'). The value is the canonical MIME type. Putting the file in mu-plugins rather than functions.php means a theme switch or a theme update cannot drop the filter and re-break uploads.
You will know the fix worked when you can upload a .woff2 file from the media library and the file appears in the grid view with no error. If the upload still fails, the second gate (wp_check_filetype_and_ext) is rejecting the content because PHP's finfo returned a MIME that does not match the extension. That is rare for fonts but common for SVG.
Why the upload_mimes filter alone is not enough for SVG
SVG is the file type where naive fixes break and even careful fixes are insecure. It deserves its own section. The mechanical reason an upload_mimes filter alone fails for SVG is that SVG is XML, not a binary image format. PHP's exif_imagetype() cannot detect it. PHP's finfo returns image/svg+xml only when the system's magic database has an SVG entry, and that is inconsistent across hosts. When finfo returns nothing useful, wp_check_filetype_and_ext() decides the file's content does not match its claimed extension and rejects it.
This regression has its own history. WordPress 4.7.1, released January 2017 as a security release, tightened content-versus-extension validation in wp_check_filetype_and_ext(). The change broke SVG, DOCX, PPT, XLSM, AI and several other plugin-supported formats whose binary or XML payloads no longer matched the new image-MIME check. Trac ticket #39552 tracked the SVG side and #39550 tracked the broader non-image regression. The follow-up adjustments landed in 4.7.3. None of that meant SVG was added or removed from the default allow-list. SVG was never in defaults. What changed is that plugin-only workarounds that hooked upload_mimes without also hooking wp_check_filetype_and_ext stopped working.
The minimal code that satisfies both gates looks like this:
// THIS IS INSECURE WITHOUT SANITISATION. KEEP READING.
// WordPress 6.5+, PHP 8.1+
add_filter( 'upload_mimes', function ( $mimes ) {
$mimes['svg'] = 'image/svg+xml';
$mimes['svgz'] = 'image/svg+xml';
return $mimes;
} );
add_filter( 'wp_check_filetype_and_ext', function ( $data, $file, $filename, $mimes ) {
if ( ! $data['type'] ) {
$filetype = wp_check_filetype( $filename, $mimes );
if ( 'svg' === $filetype['ext'] ) {
$data['ext'] = 'svg';
$data['type'] = 'image/svg+xml';
}
}
return $data;
}, 10, 4 );
That snippet is what most "how to enable SVG in WordPress" tutorials hand you and stop. It is enough to make uploads work. It is not enough to make them safe. The file that lands in wp-content/uploads/ is whatever the user submitted, including <script> blocks, onload attributes, and external entity references. When a visitor's browser requests that SVG, those scripts execute in the context of your domain. This is exactly the privilege escalation the allow-list was built to prevent.
SVG, done correctly: the Safe SVG plugin
The right way to enable SVG uploads in WordPress is to install Safe SVG by 10up, which has more than one million active installs and is maintained by the same agency as several other widely used WordPress libraries. Safe SVG hooks both upload_mimes and wp_check_filetype_and_ext correctly, and (this is the part that matters) it runs every uploaded SVG through the enshrined/svg-sanitizer PHP library before the file is written to disk. The sanitiser strips <script> elements, event handlers (onload, onclick, etc.), JavaScript URLs in href attributes, and external entity references that could be used for XXE. It also enables thumbnail previews in the media library and lets you restrict uploads to specific roles.
The plugin's seriousness about security is visible in its update history: a patch in August 2025 bumped svg-sanitizer to 0.22.0 to close a case-insensitive attribute bypass, exactly the kind of edge case that is the reason to use a maintained sanitiser instead of writing one yourself.
To install: in wp-admin, go to Plugins: Add New, search for "Safe SVG", install and activate. No configuration is required for sanitisation to take effect. You will know it worked when you can upload an SVG, see a thumbnail preview in the media library, and (this is the security check) view the file directly and confirm any <script> block in the source has been stripped. Test with a deliberately hostile SVG, not a clean one from a designer.
If you cannot use the plugin (a strict no-third-party-plugin rule, a multisite environment with locked plugin lists), the fallback is to integrate svg-sanitizer manually into a must-use plugin and call it from the wp_handle_upload_prefilter hook. That is more work than installing the plugin and gets the same result. It is rarely worth it.
WebP: native since WordPress 5.8, but the server has to cooperate
WebP was added to the default allowed MIME types in WordPress 5.8, released July 2021. On any current WordPress install you should not need an upload_mimes filter for WebP. If the upload still fails with the not-allowed error, one of three things is true.
The first is that you are still on a pre-5.8 release. Check Dashboard: Updates and run any pending WordPress core updates. The default-MIME change shipped with 5.8 and will not be back-ported.
The second is that you are on a multisite that was created before 5.8 and has site-level network MIME settings frozen at the old defaults. Multisite stores its allowed MIME list at the network level under Network Admin: Settings: Network Settings: Upload Settings. The default list there does not auto-update when a new core release adds a format. Add webp to the Upload file types field and save.
The third (and most common) is that the upload itself succeeds, but WordPress cannot process the file because the server's image library does not support WebP encoding. In that case the symptom is a different error or a broken thumbnail rather than the not-allowed message. WebP support requires Imagick or GD compiled with WebP. Check Tools: Site Health: Info: Media Handling to see whether WebP is listed under the active image editor's supported formats. If it is not, the fix is server-side: ask your host to enable WebP, or on a self-managed VPS install the right system library and rebuild the PHP extension. The broader problem of broken thumbnails after a successful upload is covered in my WordPress media library not showing images article.
HEIC: native since WordPress 6.7, with mandatory JPEG conversion
HEIC and HEIF (the iPhone photo format and its container) were added to the default allowed MIME types in WordPress 6.7, released November 2024. The release added all four related MIME types: image/heic, image/heif, image/heic-sequence, and image/heif-sequence. It also added a helper function, wp_is_heic_image_mime_type(), and a new conversion path that runs server-side at upload time.
Here is the mechanism that matters. WordPress 6.7 does not store HEIC files as HEIC. When you upload a HEIC, WordPress converts it to JPEG using Imagick (assuming Imagick was compiled with HEIC support, which usually means ImageMagick 7.0.8-26 or newer). The original HEIC is deleted after conversion and only the JPEG remains, with a download link to the original on the attachment page. This is deliberate. Browsers do not natively render HEIC, so storing a HEIC and serving it directly would produce a broken <img> tag for most visitors.
Three things to know.
First, the conversion target is always JPEG. It is not WebP or AVIF. JPEG was chosen as the universally supported lowest common denominator. You can override it with the image_editor_output_format filter if you have a strong preference, but most sites are better off leaving it alone.
Second, if Imagick on your server does not support HEIC, WordPress 6.7 does not silently accept the upload. It shows a warning encouraging manual conversion. The not-allowed error you are seeing on a 6.7 site, then, is most likely because you are still on a build of Imagick that predates HEIC. Tools: Site Health: Info: Media Handling tells you whether HEIC conversion is available on the current server.
Third, on WordPress before 6.7, HEIC was never in defaults and there is no clean fix. You can add image/heic to upload_mimes and accept the file, but WordPress will not convert it, the browser cannot render it, and the result is a broken thumbnail in the library. The realistic options are to upgrade to WordPress 6.7 or later, or to convert HEIC files to JPEG on your laptop before uploading. A third-party "HEIC Support" plugin can also paper over this on older WordPress versions.
You will know the fix held when you upload a HEIC file in WordPress 6.7+, the file appears in the library as a JPEG with normal thumbnails, and the attachment page shows a download link to the original HEIC.
ALLOW_UNFILTERED_UPLOADS: the escape hatch and why it should not be permanent
There is one constant that bypasses the entire MIME allow-list. Adding this line to wp-config.php grants the current user the unfiltered_upload capability and makes the not-allowed error disappear for everything:
// wp-config.php
// Temporary. Remove after the migration is done.
define( 'ALLOW_UNFILTERED_UPLOADS', true );
The capability check is implemented in wp-includes/capabilities.php and only takes effect when the constant is defined and (on multisite) only for Super Admins, regardless of role. I cover the multisite specifics below.
This is genuinely useful as a temporary tool. If you are migrating a site and need to bulk-import a folder of mixed file types that does not justify a per-format upload_mimes filter, define the constant, do the import, then remove the line. The reason it is a bad permanent fix is straightforward: it allows any file type, including PHP scripts. If an attacker compromises an admin account on a site with this constant left enabled, they do not need to upload a plugin or edit a theme to get code execution. They open the media uploader, drag a shell.php, and request it directly under /wp-content/uploads/2026/04/shell.php. No exploit chain required.
The pattern I use, and the one I recommend: define it, use it, remove it the same hour. Treat it the way you would treat enabling root SSH temporarily on a server. Not "off forever, never use it", but "off by default, on briefly when there is a good reason, off again when the job is done". Several hosting and security writeups (including PreventDirectAccess and Kinsta's KB) make the same point.
For the long term, the right fix is always the targeted one: a per-format upload_mimes entry for legitimate vendor formats, Safe SVG for SVG, an updated WordPress version for WebP and HEIC. Those changes accept the specific files you actually need without opening the door to everything else.
How ALLOW_UNFILTERED_UPLOADS behaves on Multisite
On WordPress Multisite the constant has a quirk worth knowing. Even when defined, ALLOW_UNFILTERED_UPLOADS only grants the unfiltered_upload capability to Super Admins. Network Admins, site admins, editors, and authors do not get it, regardless of how loud you set the constant. The relevant capability check looks roughly like this in core:
if ( defined( 'ALLOW_UNFILTERED_UPLOADS' ) && ALLOW_UNFILTERED_UPLOADS
&& ( ! is_multisite() || is_super_admin( $user_id ) ) ) {
// grant unfiltered_upload
}
That behaviour is intentional, even if it has tripped people up enough to land in trac ticket #45818 over the years. On multisite, the assumption is that the network owner is the only person trusted to bypass the gate; site admins are not. If you are a site admin on a multisite trying to use ALLOW_UNFILTERED_UPLOADS to upload a custom format and it is not working, that is why. The right fix on multisite is the same as on single-site: add the format via upload_mimes (network-wide if you can, or as an mu-plugin), and use Safe SVG for SVG.
Common file types and their MIME strings
If you are writing your own upload_mimes filter, here are the canonical MIME types for the formats that come up most often. WordPress accepts the extension as the array key (with | separating equivalents) and the MIME type as the value. The full default list lives in wp_get_mime_types() in core.
| Extension | MIME type | Notes |
|---|---|---|
svg, svgz |
image/svg+xml |
Use Safe SVG; do not enable raw |
webp |
image/webp |
Native since WP 5.8, server image library must support |
avif |
image/avif |
Native since WP 6.5, requires AVIF in Imagick or PHP 8.1+ GD |
heic, heif |
image/heic, image/heif |
Native since WP 6.7, requires Imagick with HEIC |
woff, woff2 |
font/woff, font/woff2 |
Custom web fonts |
ttf, otf |
font/ttf, font/otf |
Desktop font formats |
eot |
application/vnd.ms-fontobject |
Legacy IE font format, rarely needed now |
mp4, m4v |
video/mp4 |
Default-allowed |
mov |
video/quicktime |
Default-allowed |
webm |
video/webm |
Default-allowed |
psd |
image/vnd.adobe.photoshop |
Designer asset, not default-allowed |
ai |
application/postscript |
Adobe Illustrator, not default-allowed |
zip |
application/zip |
Default-allowed but sometimes blocked by host security |
xml |
application/xml or text/xml |
Default-allowed |
Two practical notes when extending the list. The MIME types must match what PHP's finfo returns for the actual file content, or the second gate will reject the upload even after you add the entry. If the upload still fails after a clean filter, the cause is almost always a finfo mismatch and the fix is the same wp_check_filetype_and_ext filter pattern shown for SVG, adapted to your format.
When to escalate
If you have worked through the right fix for your file type and the upload is still rejected, gather the following before opening a support ticket. The first thing anyone experienced will ask for is exactly this list:
- Your WordPress version, PHP version, and the active image editor and supported formats from Tools: Site Health: Info: Media Handling.
- The exact file you tried to upload, including its real extension and its MIME type as reported by
file --mime-type yourfile.svg(or any equivalent). - The exact error message wording (the two variants are
Sorry, you are not allowed to upload this file type.andSorry, this file type is not permitted for security reasons.). - Whether the user trying to upload is an Administrator, Super Admin (multisite), Editor, or other role.
- A copy of any custom
upload_mimesfilter you have already added, including which file it lives in. - Whether the site is single-site or multisite, and if multisite, what the Upload file types setting under Network Settings currently contains.
- Whether
ALLOW_UNFILTERED_UPLOADSis currently defined inwp-config.php.
Send all of that in the first message and the ticket routes to the right engineer without a round trip. If the same root cause is producing related media problems, two adjacent articles cover specific facets of the same problem space: the HTTP error during media upload article covers uploads that fail with a generic HTTP error after passing the MIME check, and the increase the maximum file upload size article covers the separate upload_max_filesize ceiling that produces a different error message but the same end result.
For broader context on hardening a WordPress install so that the upload allow-list is one layer in a defended stack rather than the only layer, see my WordPress security hardening article.
How to prevent it from coming back
Most of the avoidable cases share a common pattern: a one-off fix that makes the immediate error go away but leaves the site less safe than it was. Three habits prevent that.
- Add MIME types one at a time, in
mu-plugins, with a comment explaining why. A singleadd_filter( 'upload_mimes', ... )call inwp-content/mu-plugins/allow-extra-mimes.phpis easier to audit than a constant inwp-config.phpand survives theme changes. - Never enable raw SVG uploads, even temporarily. Install Safe SVG instead. The cost of installing a plugin is lower than the cost of one stored XSS, and "I will sanitise it manually later" is the kind of plan that does not survive contact with a deadline.
- Treat
ALLOW_UNFILTERED_UPLOADSlike a temporary password. Define it, use it, remove it in the same session. If you find it in awp-config.phpyou inherited and nobody can explain why it is there, it should not be there.