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 withwp_unique_filename(), which runs the name throughsanitize_file_name(). The snippet hooks thesanitize_file_namefilter, so all of them are covered. - The extension is lowercased too:
My-Photo.JPGbecomesmy-photo.jpg. - WordPress already turns accented letters into plain ones (
Čaj.pngbecomesCaj.png, thencaj.png). For scripts it keeps, such as Cyrillic,mb_strtolower()lowercases them too:ФОТО.pngbecomesфото.png. Without the mbstring extension it falls back tostrtolower(). - The name is lowercased before WordPress checks for an existing file. Uploading
Photo.jpgafterphoto.jpggivesphoto-1.jpginstead of overwriting the first file. - With
images_onlyset totruethe extension decides: WordPress’s own image list fromwp_get_ext_types()(jpg, png, gif, webp, avif, heic and more) plussvg. 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_configfilter.
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.