Lowercase Uploaded File Names in WordPress

WordPress keeps the capital letters in uploaded file names, so Team-Photo.JPG stays Team-Photo.JPG in the media library and in its URL. This snippet makes WordPress lowercase uploaded file names automatically, for every file type or only for images.

Lowercase names give predictable URLs. Most Linux servers treat Photo.jpg and photo.jpg as two different files, so a link typed in lowercase to an uploaded Photo.JPG returns a 404.

The snippet

<?php
/**
 * Lowercase the names of files uploaded to WordPress.
 */

// ===== Config =====
function dpc_lowercase_filenames_config() {
	$config = array(
		// false = every file type. true = images only (jpg, png, gif, webp, avif, heic, svg …).
		'images_only' => false,
	);
	// ===== End of config =====

	return (array) apply_filters( 'dpc_lowercase_filenames_config', $config );
}

add_filter( 'sanitize_file_name', 'dpc_lowercase_filename' );

function dpc_lowercase_filename( $filename ) {
	$config = dpc_lowercase_filenames_config();

	if ( ! empty( $config['images_only'] ) ) {
		$types  = wp_get_ext_types();
		$images = array_merge( $types['image'] ?? array(), array( 'svg' ) );
		$ext    = strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) );

		if ( ! in_array( $ext, $images, true ) ) {
			return $filename;
		}
	}

	return function_exists( 'mb_strtolower' ) ? mb_strtolower( $filename, 'UTF-8' ) : strtolower( $filename );
}

How it works

  • Every upload path in WordPress (the media library, the block editor, the REST API, media_sideload_image()) picks the final name with wp_unique_filename(), which runs the name through sanitize_file_name(). The snippet hooks the sanitize_file_name filter, so all of them are covered.
  • The extension is lowercased too: My-Photo.JPG becomes my-photo.jpg.
  • WordPress already turns accented letters into plain ones (Čaj.png becomes Caj.png, then caj.png). For scripts it keeps, such as Cyrillic, mb_strtolower() lowercases them too: ФОТО.png becomes фото.png. Without the mbstring extension it falls back to strtolower().
  • The name is lowercased before WordPress checks for an existing file. Uploading Photo.jpg after photo.jpg gives photo-1.jpg instead of overwriting the first file.
  • With images_only set to true the extension decides: WordPress’s own image list from wp_get_ext_types() (jpg, png, gif, webp, avif, heic and more) plus svg. Other files, such as PDFs, keep their name.

Add it with Scripts Organizer

Scripts Organizer ships this snippet in its One-Click Import library as Lowercase filenames for upload files. To add it by hand, go to Code Blocks → Add New and give the block a name. Set Trigger location to Everywhere, tick PHP under Script location, paste the code into the PHP editor, edit the config and publish. Leave Action/Hook empty.

Use Everywhere. Uploads happen in wp-admin and through REST API requests from the block editor, and Conditions only runs on front-end pages, so the filter would never be added when a file is uploaded.

The same code also works unchanged in a small plugin or an mu-plugin.

Customise it

  • Images only: set 'images_only' => true.
  • SVG uploads are blocked by default; to allow them safely see Allow SVG Uploads in WordPress for Admins Only. This snippet then lowercases those names as well.
  • Change the setting from other code with the dpc_lowercase_filenames_config filter.

Gotchas

  • Only new uploads change. Files already in the media library keep their names and URLs.
  • sanitize_file_name() is also used by some plugins for files they write themselves, for example exports. Those names are lowercased as well.
  • The attachment title still comes from the original name, so the media library shows the title as it was typed.

The older version of this post lowercased only a fixed list of image MIME types, which contained ico instead of a MIME type and missed WebP, AVIF and SVG. It also suggested loading the code on the front end, where it never runs for uploads.

In the Scripts Organizer library Import it with one click from Scripts Organizer.

// newsletter

Three tested WordPress snippets a week

Blocks, security, performance, Tailwind, PHP and React. Copy, paste, ship. You’ll also get DPlugins product updates. No spam, and you can unsubscribe anytime.

// newsletter

Three tested WordPress snippets a week

Blocks, security, performance, Tailwind, PHP and React. Copy, paste, ship. You'll also get DPlugins product updates. No spam, and you can unsubscribe anytime.