Skip to content
ThemesIonic — home
WordPress Tutorials

Allowing New Upload File Types in WordPress

Adding a type to this array is half the job. WordPress checks the real contents of the file separately, and a file whose innards do not match its extension is rejected even after the type is allowed.

4 min read advanced

Allowing the type and allowing the file are two different checks. upload_mimes filters the list of extensions WordPress will accept. A second, independent check reads the beginning of the uploaded file, works out what it really is, and refuses it if that disagrees with the extension. Adding an entry to the array and still seeing "Sorry, this file type is not permitted for security reasons" means the first check now passes and the second does not.

The array

Keys are extensions, values are MIME types. Several extensions sharing a type are written with a pipe:

add_filter( 'upload_mimes', 'ti_upload_mimes' );

function ti_upload_mimes( $mimes ) {
	$mimes['webp'] = 'image/webp';
	$mimes['avif'] = 'image/avif';

	return $mimes;
}

Removing a type uses the same array:

unset( $mimes['doc'], $mimes['docx'] );

Removing is worth more attention than it usually gets. A site that never publishes office documents has no reason to accept them, and every type removed is one less thing to handle. The same logic applies to any type only one role ever needs.

The second check, and how to satisfy it

The content check compares the detected type against the extension. For a genuinely well-formed file of a standard type, it agrees and the upload proceeds. It disagrees in two situations: the file is not what it claims, or the detected type is one this WordPress install does not have a mapping for.

The second case is the one worth fixing, and it has its own filter:

add_filter( 'wp_check_filetype_and_ext', 'ti_allow_type', 10, 4 );

function ti_allow_type( $data, $file, $filename, $mimes ) {
	if ( ! empty( $data['ext'] ) && ! empty( $data['type'] ) ) {
		return $data;
	}

	$check = wp_check_filetype( $filename, $mimes );

	if ( 'webp' === $check['ext'] ) {
		$data['ext']  = 'webp';
		$data['type'] = 'image/webp';
	}

	return $data;
}

The early return matters. When the check already resolved the file, overriding the result throws away a correct answer and replaces it with an assumption — which is precisely how a check meant to catch mismatched files stops catching them.

Restrict by capability, not by hope

The filter applies to everyone who can upload. A site with contributor accounts or customer-facing uploads has a wider audience for this change than a single-author blog:

add_filter( 'upload_mimes', 'ti_upload_mimes' );

function ti_upload_mimes( $mimes ) {
	if ( ! current_user_can( 'manage_options' ) ) {
		return $mimes;
	}

	$mimes['svg'] = 'image/svg+xml';

	return $mimes;
}

Capability checks belong inside the callback rather than around the add_filter call, because the filter may run in a context where the current user is not yet established.

Why SVG is a security decision

An SVG is XML, and XML can contain script that executes when the file is opened in a browser. Allowing SVG uploads means allowing anyone who can upload to publish active content on the domain. On a site where the only uploader is the owner, the risk is bounded. On a site with open registration or contributor roles, it is a path straight to stored script execution.

If SVG is genuinely needed, three things have to be true together: sanitisation strips script and event attributes on upload, the capability check limits who can do it, and the files are served in a way that does not invite direct navigation. Skipping any one of them undoes the other two. The consequences of getting this wrong look like the aftermath described in fixing a hacked WordPress site.

Multisite has a second gate

A network keeps its own list of permitted upload extensions in the network settings, applied in addition to this filter. An extension absent from that list is refused before the filter is consulted, which makes a perfectly correct snippet appear to do nothing.

The tell is that the same code works on a standalone install and fails on the network. Adding the extension to the network setting is the fix; there is no filter that substitutes for it.

Verify with three files

Upload a well-formed file of the new type and confirm it lands in the media library and renders. Upload a file with the right extension and deliberately wrong contents — a text file renamed — and confirm it is refused. Then log in as a lower role and confirm the type is unavailable if you restricted it.

If the well-formed file is still refused, the content check is the blocker and not the array. If the wrong-content file is accepted, the second filter is overriding too broadly and should be narrowed. Images that upload but display incorrectly are a different problem and start from optimising images in WordPress.

Frequently asked

Because a second check inspects the file's actual contents and compares them to the extension. If the detected type does not match what the extension claims, the upload is refused regardless of what this filter allows.
Because a network has its own list of permitted upload extensions in the network settings, applied on top of this filter. An extension missing from that list is blocked before the filter's opinion matters.
Not by default. An SVG is a document that can carry script, so allowing it means allowing anyone who can upload to place active content on the site. Only allow it with sanitisation in place and only for roles you trust.
Tagged Security

Related guides