Image Sizes
WordPress crops every upload into named sizes; add_image_size registers your own and srcset picks between them.
add_image_size( string $name, int $width = 0, int $height = 0, bool|array $crop = false ): voidWordPress does not serve the file you uploaded. On upload it generates a set of smaller copies — thumbnail, medium, medium_large (768px wide, added in 4.4) and large — and records them in the attachment's metadata. Since 5.3 an original wider or taller than the big_image_size_threshold filter's default of 2560px is itself scaled down, and the scaled copy becomes full.
Registering a size
add_image_size( $name, $width, $height, $crop ) adds one more. With $crop false the image is scaled to fit inside the box, so one dimension may come out smaller. With true it is cropped from the centre to exactly those dimensions. Passing an array such as array( 'center', 'top' ) crops from a chosen anchor instead. Call it on after_setup_theme.
Output
the_post_thumbnail( $size, $attr ) echoes the featured image; get_the_post_thumbnail() returns it; wp_get_attachment_image( $id, $size, $icon, $attr ) handles any attachment. All three go through the same builder, so all three get srcset and sizes for free (since 4.4). WordPress assembles srcset from every registered size that shares the original's aspect ratio, capped by the max_srcset_image_width filter at 2048px. Since 6.3, wp_get_loading_optimization_attributes() also decides the loading and fetchpriority attributes.
Regenerating
add_image_size() only affects future uploads. Existing attachments keep the sizes they had. Rebuild them with wp media regenerate in WP-CLI or a regeneration plugin.
Example
<?php
/**
* Register the sizes this theme actually uses.
* Must run before any upload happens, so hook after_setup_theme.
*/
function scx_register_image_sizes(): void {
add_theme_support( 'post-thumbnails' );
// The 'post-thumbnail' size used by the_post_thumbnail() with no args.
set_post_thumbnail_size( 1200, 675, true );
// Scaled to fit inside 800x450: same ratio as the original, so it can
// appear in srcset alongside medium and large.
add_image_size( 'scx-card', 800, 450, false );
// Hard crop from the top so faces survive a 1:1 avatar crop.
add_image_size( 'scx-avatar', 300, 300, array( 'center', 'top' ) );
}
add_action( 'after_setup_theme', 'scx_register_image_sizes' );
/**
* Expose the sizes in the block editor's size dropdown.
* Without this filter they exist but editors cannot pick them.
*/
function scx_selectable_sizes( array $sizes ): array {
$sizes['scx-card'] = __( 'Card', 'scx' );
$sizes['scx-avatar'] = __( 'Avatar', 'scx' );
return $sizes;
}
add_filter( 'image_size_names_choose', 'scx_selectable_sizes' );When to use it
- Add a 16:9 card size for an archive grid and make it selectable in the editor.
- Fix a blurry hero by supplying a correct sizes attribute instead of WordPress's guess.
- Cut storage by not generating the medium_large and 2048x2048 copies.
- Regenerate thumbnails after adding a size to a site that already has uploads.
More examples
Rendering a featured image with explicit sizes
WordPress guesses the sizes attribute from the requested width, and its guess is wrong whenever your layout is not full width. Passing sizes yourself is the single highest-value image tweak in a theme: the browser stops downloading a 1200px file for a 400px column.
<?php if ( has_post_thumbnail() ) : ?>
<figure>
<?php
// $size defaults to 'post-thumbnail'; $attr is merged into the tag.
the_post_thumbnail(
'scx-card',
array(
// Tell the browser how wide the image renders, per breakpoint.
'sizes' => '(max-width: 700px) 100vw, 800px',
// Alt comes from the attachment's _wp_attachment_image_alt meta,
// so only override it when the image is decorative.
'class' => 'card__image',
)
);
?>
<figcaption><?php echo wp_kses_post( get_the_post_thumbnail_caption() ); ?></figcaption>
</figure>
<?php endif; ?>Inspecting what srcset a given image will produce
Reach for this when a responsive image is not switching sources and you need to know whether WordPress produced any candidates at all. wp_get_attachment_image_srcset() was added in 4.4 and takes an attachment ID plus a size; a false return tells you the problem is upstream in the registered sizes, not in your markup.
$attachment_id = get_post_thumbnail_id();
// Returns the srcset string, or false if the image has no other candidates.
$srcset = wp_get_attachment_image_srcset( $attachment_id, 'large' );
if ( false === $srcset ) {
// Usually means the size is hard-cropped, or the original is too small
// for WordPress to have generated any intermediate sizes.
error_log( "No srcset candidates for attachment {$attachment_id}" );
}
// Raise the 2048px ceiling on candidate widths for a photography site.
add_filter(
'max_srcset_image_width',
static function ( int $max_width, array $size_array ): int {
return 2560;
},
10,
2
);Limiting which sizes get generated on upload
On a site with tens of thousands of uploads the default size set can triple your storage bill. intermediate_image_sizes_advanced receives the sizes about to be created for one upload, so removing entries here prevents the files being written at all. The second filter, added in 5.3, disables the automatic downscale of very large originals — only do that if you genuinely serve the originals.
/**
* Stop generating sizes this site never renders.
* Every removed size is disk space and upload time saved.
*/
function scx_trim_image_sizes( array $sizes ): array {
unset( $sizes['medium_large'] ); // 768px, added in WP 4.4
unset( $sizes['1536x1536'] );
unset( $sizes['2048x2048'] );
return $sizes;
}
add_filter( 'intermediate_image_sizes_advanced', 'scx_trim_image_sizes' );
// Keep the full-size original instead of the 2560px scaled copy.
add_filter( 'big_image_size_threshold', '__return_false' );
Discussion