When a user uploads an image to the WordPress Media Library, WordPress automatically generates resized copies based on the sizes registered with add_image_size(). The default sizes — thumbnail (150×150 hard-crop), medium (300×300 soft-crop), medium_large (768px wide), and large (1024px wide) — are a reasonable starting point but rarely match a theme’s actual layout. A theme with a 360px card image on mobile and an 800px card image on desktop needs dedicated sizes registered to match; otherwise WordPress serves the wrong file and browsers scale it, wasting bandwidth. The correct workflow is: register sizes in functions.php with add_image_size(), use the size name in theme templates, and feed the attachment ID to wp_get_attachment_image() or wp_get_attachment_image_srcset() to get a complete, accessible, responsive image tag. This article also covers regenerating thumbnails for existing uploads and removing sizes you no longer need.
Problem: Your theme's card images are being served from WordPress's default sizes, which do not match your layout's actual dimensions — causing browsers to download oversized images and scale them down, wasting bandwidth and degrading performance.
Solution: Register custom image sizes with add_image_size(), use the size name in your templates, and output responsive markup with wp_get_attachment_image().
<?php
add_action( 'after_setup_theme', 'register_custom_image_sizes' );
function register_custom_image_sizes() {
// add_image_size( name, width, height, crop )
// crop = false: proportional resize (soft crop)
// crop = true: exact dimensions, centred crop
// crop = array: position — e.g. [ 'left', 'top' ]
add_image_size( 'card-thumb', 360, 240, true ); // 3:2, hard crop centred
add_image_size( 'card-wide', 800, 450, true ); // 16:9 card
add_image_size( 'hero', 1440, 600, [ 'center', 'top' ] ); // hero, crop from top
add_image_size( 'avatar', 80, 80, true ); // square avatar
// Make custom sizes appear in the Media Library size selector
add_filter( 'image_size_names_choose', function ( $sizes ) {
return array_merge( $sizes, [
'card-thumb' => __( 'Card Thumbnail' ),
'card-wide' => __( 'Card Wide' ),
'hero' => __( 'Hero' ),
] );
} );
}
Output responsive image tags in templates:
<?php
$attachment_id = get_post_thumbnail_id();
// wp_get_attachment_image generates srcset and sizes attributes automatically
echo wp_get_attachment_image( $attachment_id, 'card-thumb', false, [
'class' => 'card__image',
'loading' => 'lazy',
] );
// For art-direction (different crops at different breakpoints) use a picture element:
$thumb_src = wp_get_attachment_image_url( $attachment_id, 'card-thumb' );
$wide_src = wp_get_attachment_image_url( $attachment_id, 'card-wide' );
$alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
?>
<picture>
<source media="(min-width: 768px)" srcset="<?php echo esc_url( $wide_src ); ?>">
<img src="<?php echo esc_url( $thumb_src ); ?>"
alt="<?php echo esc_attr( $alt ); ?>"
class="card__image" loading="lazy">
</picture>
NOTE: Registering a new image size with add_image_size() only generates that size for images uploaded after the code is in place. Existing images in the Media Library need to be regenerated — use the WP-CLI command wp media regenerate --yes or the Regenerate Thumbnails plugin. Removing a size does not delete the physical files from the server; you need a plugin like Force Regenerate Thumbnails or a custom script to clean up unused image files.