How to upload SVG in WordPress

By default, in WordPress you can't upload SVG files for security reasons. But what to do when you really need to? There is a solution!

When trying to upload SVG, the message "Sorry, this file type is not allowed for security reasons" appears.

About whether uploading SVG is allowed in WordPress by default and why developers avoid it, read in the ticket #24251 Reconsider SVG inclusion to get_allowed_mime_types.

The plugin - you can also enable the upload of SVG through the plugin Safe SVG. Among other things, it cleans the code of the uploaded file.

Step 1 - Enabling SVG in the list of allowed file uploads

To do this, we'll use the upload_mimes hook, which allows you to change the list of files available for upload by MIME type.

add_filter( 'upload_mimes', 'svg_upload_allow' );

# Adds SVG to the list of allowed file uploads.
function svg_upload_allow( $mimes ) {
	$mimes['svg']  = 'image/svg+xml';

	return $mimes;
}

Sometimes this code is enough, but not for all SVG files. Let's figure out why.

The real MIME type of an SVG file, which is determined in WP by the php function finfo_file() (see the code wp_check_filetype_and_ext() ), can be two variants:

  • image/svg+xml — when the svg code has an xml header.
  • image/svg — when the svg code only has the <svg> tag, without any headers.

Both of these variants need to be added here, but only one can be added, so only this code is not enough and a second step is needed.

Step 2 - Substitution of the SVG mime type

As described above, the previous code is not enough to solve the problem. More precisely, as a result of the check and the mismatch of the specified MIME type and the real MIME type (determined by WP itself), the MIME type will simply be reset to zero. The hook wp_check_filetype_and_ext will help us to return it back.

add_filter( 'wp_check_filetype_and_ext', 'fix_svg_mime_type', 10, 5 );

# Fixing the MIME type for SVG files.
function fix_svg_mime_type( $data, $file, $filename, $mimes, $real_mime = '' ){

	// WP 5.1 +
	if( version_compare( $GLOBALS['wp_version'], '5.1.0', '>=' ) ){
		$dosvg = in_array( $real_mime, [ 'image/svg', 'image/svg+xml' ] );
	}
	else {
		$dosvg = ( '.svg' === strtolower( substr( $filename, -4 ) ) );
	}

	// the mime type was reset, let's fix it
	// and also check user rights
	if( $dosvg ){

		// allow
		if( current_user_can('manage_options') ){

			$data['ext']  = 'svg';
			$data['type'] = 'image/svg+xml';
		}
		// forbid
		else {
			$data['ext']  = false;
			$data['type'] = false;
		}

	}

	return $data;
}

If you remove the administrator's rights check from the above code, the code will become unsafe and potentially open a security hole in the site.

The code above has added a check for the WP version. With WP 5.1.0, thanks to the ticket of the author of this site, the $real_mime parameter appeared in the hook. It allows for a more reliable file check - at the level of determining the file's MIME type by its code, rather than by extension.

In addition, I recommend limiting the size of the uploaded SVG file, for example, not more than 50kb. SVG files are usually small and a large size may indicate that there is some bad code in the file.

The code above is sufficient for WordPress to allow uploading SVG to the media library. The next step is purely visual.

Displaying SVG in the media library

After the steps described above, SVG files will be displayed as documents, not images:

The code responsible for the layout of these image blocks is generated by the function wp_print_media_templates() from the file wp-includes/media-template.php:

<div class="thumbnail">
	<# if ( data.uploading ) { #>
		<div class="media-progress-bar"><div style="width: {{ data.percent }}%"></div></div>
	<# } else if ( 'image' === data.type && data.sizes ) { #>
		<div class="centered">
			<img src="{{ data.size.url }}" draggable="false" alt="" />
		</div>
	<# } else { #>
		<div class="centered">
			<# if ( data.image && data.image.src && data.image.src !== data.icon ) { #>
				<img src="{{ data.image.src }}" class="thumbnail" draggable="false" alt="" />
			<# } else if ( data.sizes && data.sizes.medium ) { #>
				<img src="{{ data.sizes.medium.url }}" class="thumbnail" draggable="false" alt="" />
			<# } else { #>
				<img src="{{ data.icon }}" class="icon" draggable="false" alt="" />
			<# } #>
		</div>
		<div class="filename">
			<div>{{ data.filename }}</div>
		</div>
	<# } #>
</div>

The meaning of the code is that it will display the image if:

  • it has a src property (link to the image) and it is not equal to the link to the document icon (any file is essentially a document);
  • or if the image has a medium size (SVG files are not cropped at all).

The SVG file has neither the first nor the second, so we need to simulate one of these options ourselves, and the wp_prepare_attachment_for_js filter will help us, use any of the options you like.

Option 1 - With the display of the file name

add_filter( 'wp_prepare_attachment_for_js', 'show_svg_in_media_library' );

# Forms data for displaying SVG as an image in the media library.
function show_svg_in_media_library( $response ) {

	if ( $response['mime'] === 'image/svg+xml' ) {

		// With the display of the file name
		$response['image'] = [
			'src' => $response['url'],
		];
	}

	return $response;
}

Option 2 - Without displaying the file name

add_filter( 'wp_prepare_attachment_for_js', 'show_svg_in_media_library' );

# Forms data for displaying SVG as an image in the media library.
function show_svg_in_media_library( $response ) {

	if ( $response['mime'] === 'image/svg+xml' ) {

		// Without displaying the file name
		$response['sizes'] = [
			'medium' => [
				'url' => $response['url'],
			],
			// when editing an image
			'full' => [
				'url' => $response['url'],
			],
		];
	}

	return $response;
}

Ready class

GitHub
<?php

/**
 * Allow uploading svg webp file types
 *
 * @version 1.1
 */
final class Allow_SVG_Upload {

	public static function init(): void {

		// Allow file types to be uploaded
		add_filter( 'upload_mimes', [ __CLASS__, 'upload_allow_types' ] );
		add_filter( 'wp_check_filetype_and_ext', [ __CLASS__, 'fix_svg_mime_type' ], 10, 5 );
		add_filter( 'wp_prepare_attachment_for_js', [ __CLASS__, 'show_svg_in_media_library' ] );

		add_filter( 'wp_handle_sideload_prefilter', [ __CLASS__, 'check_upload_file_size' ] );
		add_filter( 'wp_handle_upload_prefilter', [ __CLASS__, 'check_upload_file_size' ] );

		add_filter( 'getimagesize_mimes_to_exts', [ __CLASS__, 'more_mimes_to_exts' ] );
		add_filter( 'image_sideload_extensions', [ __CLASS__, 'more_image_sideload_extensions' ] );
	}

	public static function upload_allow_types( $mimes ){

		if( current_user_can( 'upload_files' ) ){
			$mimes['svg']  = 'image/svg+xml';
		}

		$mimes['webp'] = 'image/webp';

		// deactivate existing
		unset( $mimes['mp4a'] );

		return $mimes;
	}

	public static function more_image_sideload_extensions( $allowed ){
		$allowed[] = 'svg';

		return $allowed;
	}

	public static function more_mimes_to_exts( $mime_to_ext ){
		$mime_to_ext['image/webp'] = 'webp';

		return $mime_to_ext;
	}

	public static function fix_svg_mime_type( $data, $file, $filename, $mimes, $real_mime = '' ){

		// WP 5.1 +
		if( version_compare( $GLOBALS['wp_version'], '5.1.0', '>=' ) ){
			$dosvg = in_array( $real_mime, [ 'image/svg', 'image/svg+xml' ] );
		}
		else{
			$dosvg = ( '.svg' === strtolower( substr( $filename, -4 ) ) );
		}

		// mime type was reset, let's fix it
		// and also check file size and user permissions
		if( $dosvg ){
			if( current_user_can( 'upload_files' ) ){
				$data['ext']  = 'svg';
				$data['type'] = 'image/svg+xml';
			}
			// prohibit
			else {
				$data['ext'] = $type_and_ext['type'] = false;
			}
		}

		return $data;
	}

	/**
	 * Generates data for displaying SVGs as images in the media library.
	 */
	public static function show_svg_in_media_library( $response ) {

		if ( $response['mime'] === 'image/svg+xml' ) {
			$response['sizes'] = [
				'medium' => [
					'url' => $response['url'],
				],
				// when editing a image
				'full' => [
					'url' => $response['url'],
				],
			];
		}

		return $response;
	}

	/**
	 * Limit the size of uploaded files by type
	 */
	public static function check_upload_file_size( $file ){

		if( ! $file ){
			return $file;
		}

		// for SVG
		if( str_contains( ( $file['type'] ?? '' ), 'image/svg+xml' ) ){
			$size_limit = 500 * 1024; // max size in KB

			if( (int) $file['size'] > $size_limit ){
				$file['error'] = sprintf(
					__( 'ERROR: Размер этого типа файлов не может превышать %s', 'km' ),
					size_format( $size_limit )
				);
			}
		}

		return $file;
	}

}