WordPress compresses every image size it creates at quality 82. On large hero images and full-width photos that compression can show as soft edges and banding, while on thumbnails nobody notices it. Setting everything to 100 fixes the large images but makes every thumbnail and medium size much heavier too.
This snippet sets the WordPress image compression quality by image size: copies at least 1000 pixels wide are saved at quality 100, smaller ones keep the WordPress default.
The snippet
<?php
/**
* Save large WordPress image sizes with less compression and keep small thumbnails light.
*/
// ===== Config =====
function dpc_image_compression_config(): array {
$config = array(
// Images at least this wide (in pixels) get 'large_quality'.
'min_width' => 1000,
// Quality 1-100 for those images. WordPress uses 82 by default.
'large_quality' => 100,
// Formats this applies to.
'formats' => array( 'image/jpeg', 'image/webp' ),
);
// ===== End of config =====
return (array) apply_filters( 'dpc_image_compression_config', $config );
}
add_filter( 'wp_editor_set_quality', 'dpc_image_compression_quality', 10, 3 );
function dpc_image_compression_quality( $quality, $mime_type = '', $size = array() ) {
$config = dpc_image_compression_config();
if ( ! in_array( $mime_type, (array) $config['formats'], true ) ) {
return $quality;
}
$width = is_array( $size ) ? (int) ( $size['width'] ?? 0 ) : 0;
if ( $width < (int) $config['min_width'] ) {
return $quality;
}
return max( 1, min( 100, (int) $config['large_quality'] ) );
}
How it works
- When WordPress resizes an upload, it asks the
wp_editor_set_qualityfilter which quality to save that copy with. Since WordPress 6.8 the filter also gets the width and height of the copy. - The snippet returns
large_qualityfor JPEG and WebP copies that are at leastmin_widthwide. Everything else gets the quality WordPress would use anyway. - Values are limited to 1–100.
- It works with both GD and Imagick, whichever your server uses.
- The older version of this post said the default is 90%. That was true before WordPress 4.5; it has been 82 since then.
Add it with Scripts Organizer
In Scripts Organizer 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.
Images are resized in wp-admin, in the REST API requests the block editor uploads through, and sometimes in cron. Everywhere covers all of them.
The same code also works unchanged in a small plugin or an mu-plugin.
Customise it
- A lighter setting: 90 is hard to tell apart from 100 and noticeably smaller.
- Other breakpoint: change
min_width, for example to1536to raise only the1536x1536,2048x2048and scaled copies. - Same quality for every size: set
min_widthto0, or use Remove WordPress default image compression, which sets one quality per format. - AVIF: add
'image/avif'toformats.
Gotchas
- Only new uploads are affected. Regenerate thumbnails, for example with
wp media regenerate, to apply it to existing images. - The original file you upload is never recompressed. Uploads larger than 2560 pixels get a
-scaledcopy that WordPress uses as the main image; that copy is wide, so it getslarge_quality. - WordPress versions before 6.8 do not pass the size, so the snippet leaves the quality unchanged there.
- Image optimisation plugins and CDNs may compress the files again afterwards.
In the Scripts Organizer library Import it with one click from Scripts Organizer.