If you are trying to upload other file types to WordPress, such as zip or tff, you are going to see this error message in the media upload page:

Sorry, This File Type Is Not Permitted for Security Reasons

Why am I getting this?

Due to security reason (WordPress Core is very secured), WordPress is default to limited the types of files that you can upload to your site.

According to WordPress’s official documentation, below is the accepted file types:

Images

  • .jpg
  • .jpeg
  • .png
  • .gif
  • .webp (this is newly added in WordPress version 5.8)

Documents

  • .pdf
  • .doc
  • .odt
  • .xls, .xlsx
  • .key
  • .ppt, .pptx, .pps, .ppsx

Audio

  • .mp3
  • .m4a
  • .ogg
  • .wav

Video

  • .mp4, .m4v
  • .mov
  • .wmv
  • .avi
  • .mpg
  • .ogv
  • .3gp
  • .3g2

Can I upload other file types?

Files can be dangerous to your server and your visitors. I can contain malicious code that being executed in your server, or transferred to your visitors. A simple way WordPress doing here is to exclude files that are not above extensions.

As a side note, there is a concern being raised that WordPress is not actually checking the file content but only the file extension. There is other plugins like Lord of the Files to help apply additional check.

How does WordPress implement this?

This is implemented in function get_allowed_mime_types and wp_upload_bits.

function wp_upload_bits( $name, $deprecated, $bits, $time = null ) {
	if ( ! empty( $deprecated ) ) {
		_deprecated_argument( __FUNCTION__, '2.0.0' );
	}

	if ( empty( $name ) ) {
		return array( 'error' => __( 'Empty filename' ) );
	}

	$wp_filetype = wp_check_filetype( $name );
	if ( ! $wp_filetype['ext'] && ! current_user_can( 'unfiltered_upload' ) ) {
		return array( 'error' => __( 'Sorry, this file type is not permitted for security reasons.' ) );
	}

	$upload = wp_upload_dir( $time );

	if ( false !== $upload['error'] ) {
		return $upload;
	}
......
function get_allowed_mime_types( $user = null ) {
	$t = wp_get_mime_types();

	unset( $t['swf'], $t['exe'] );
	if ( function_exists( 'current_user_can' ) ) {
		$unfiltered = $user ? user_can( $user, 'unfiltered_html' ) : current_user_can( 'unfiltered_html' );
	}

	if ( empty( $unfiltered ) ) {
		unset( $t['htm|html'], $t['js'] );
	}

	/**
	 * Filters list of allowed mime types and file extensions.
	 *
	 * @since 2.0.0
	 *
	 * @param array            $t    Mime types keyed by the file extension regex corresponding to those types.
	 * @param int|WP_User|null $user User ID, User object or null if not provided (indicates current user).
	 */
	return apply_filters( 'upload_mimes', $t, $user );
}

Solution 1: Use the hook upload_mimes

This hook is documented here.

It’s pretty easy to use it. For example, if I want to add ttf (font file type) support, I can add this to my theme functions.php

function ttf_mime_types( $mimes ) {   
  $mimes['ttf'] = 'font/ttf';
  return $mimes;
}
add_filter( 'upload_mimes', 'ttf_mime_types' );

That’s all we need to do to add tff into the allowed file types. But be aware that you should choose the correct MIME Type. That will return as the HTML content-type header to the browser. Using a wrong type may result in weird behavior in the visitor’s browser.

Solution 2: Use the Plugin

If you don’t want to write code, there is some plugin that can do this work for you. WP Add Mime Types is one the popular choice. This is actually doing the same thing we are doing in Solution 1, but it has a nice UI interface.

Solution 3: The unfiltered_upload Capabilities

As you can see in the code above, the current_user_can( ‘unfiltered_upload’ ) is used as a switch logic to bypass this security error.

This is mentioned in the document here.

  1. This is a special capabilities. You can’t just add this to your role to make it work.
  2. You need to define the constant in wp-config.php
define( 'ALLOW_UNFILTERED_UPLOADS', true );

Once this is added, for single site, all user’s upload will be unfiltered. For multisite, only super admin will be unfiltered.

This is the implementation in function map_meta_cap:

case 'unfiltered_upload':
  if ( defined( 'ALLOW_UNFILTERED_UPLOADS' ) && ALLOW_UNFILTERED_UPLOADS && ( ! is_multisite() || is_super_admin( $user_id ) ) ) {
    $caps[] = $cap;
  } else {
    $caps[] = 'do_not_allow';
  }
  break;

Conclusion

We always suggest to use Solution 1. Solution 3 will open the door to all file types. This may introduce security risk.

Solution 2 works great, but we always suggest to use less plugins if possible.