WordPress wp_get_attachment_metadata(): Read Image Dimensions, Generated Sizes, and EXIF Data

When WordPress processes an image upload it generates multiple thumbnail sizes and stores detailed metadata about each one in the wp_postmeta table under the key _wp_attachment_metadata. This metadata includes the original file’s dimensions, the relative file path, and an array of every generated sub-size with its file name, width, height, and whether it was cropped. wp_get_attachment_metadata() retrieves this array, giving plugins and themes access to everything WordPress knows about an image attachment — without hitting the filesystem. Common uses include: validating that an image meets minimum dimensions before it can be selected, listing all available image sizes for a given attachment, reading EXIF data stored in the metadata array, and programmatically updating metadata after an external image manipulation process.

Problem: A plugin needs to ensure that a product image selected in a meta box is at least 800×800 px before allowing the selection, and also needs to read whether a specific thumbnail size was successfully generated for that attachment.

Solution: Call wp_get_attachment_metadata() to retrieve the metadata array, read ['width'] and ['height'] for the original dimensions, and check ['sizes'] for generated thumbnail entries.

<?php
$attachment_id = 42; // ID of the attachment post

// ── Get full metadata array ───────────────────────────────────────────
$meta = wp_get_attachment_metadata( $attachment_id );

// $meta structure:
// [
//   'width'    => 2000,
//   'height'   => 2000,
//   'file'     => '2021/04/product.jpg',   // relative to uploads dir
//   'filesize' => 524288,
//   'sizes'    => [
//     'thumbnail'    => [ 'file' => 'product-150x150.jpg', 'width' => 150, 'height' => 150, 'mime-type' => 'image/jpeg' ],
//     'medium'       => [ 'file' => 'product-300x300.jpg', 'width' => 300, 'height' => 300, 'mime-type' => 'image/jpeg' ],
//     'product-main' => [ 'file' => 'product-800x800.jpg', 'width' => 800, 'height' => 800, 'mime-type' => 'image/jpeg' ],
//   ],
//   'image_meta' => [ 'aperture' => '2.8', 'camera' => 'Canon EOS R5', ... ],
// ]

// ── Validate minimum dimensions ────────────────────────────────────────
function attachment_meets_minimum_size( int $attachment_id, int $min_w, int $min_h ): bool {
    $meta = wp_get_attachment_metadata( $attachment_id );
    if ( ! is_array( $meta ) ) return false;
    return ( $meta['width'] ?? 0 ) >= $min_w && ( $meta['height'] ?? 0 ) >= $min_h;
}

if ( ! attachment_meets_minimum_size( $attachment_id, 800, 800 ) ) {
    wp_send_json_error( [ 'message' => 'Image must be at least 800×800 px.' ] );
}

// ── Check if a specific size was generated ────────────────────────────
$sizes = $meta['sizes'] ?? [];
if ( isset( $sizes['product-main'] ) ) {
    $size_info = $sizes['product-main'];
    echo esc_html( $size_info['file'] );   // product-800x800.jpg
    echo (int) $size_info['width'];        // 800
    echo (int) $size_info['height'];       // 800
}

// ── Build the full URL for a specific generated size ──────────────────
$upload_dir = wp_upload_dir();
$base_dir   = $meta['file'] ? dirname( $upload_dir['basedir'] . '/' . $meta['file'] ) : $upload_dir['basedir'];
$base_url   = $meta['file'] ? dirname( $upload_dir['baseurl'] . '/' . $meta['file'] ) : $upload_dir['baseurl'];
$size_url   = isset( $sizes['product-main']['file'] )
    ? $base_url . '/' . $sizes['product-main']['file']
    : '';

// ── Update metadata after external processing ─────────────────────────
// If you resize an image externally, update the stored metadata:
$meta['width']  = 1600;
$meta['height'] = 900;
wp_update_attachment_metadata( $attachment_id, $meta );

// ── List all registered image sizes and their availability ────────────
$registered = wp_get_registered_image_subsizes(); // all sizes known to WP
foreach ( $registered as $size_name => $size_args ) {
    $generated = isset( $sizes[ $size_name ] );
    printf( "%s: %dx%d — %s
",
        esc_html( $size_name ),
        (int) $size_args['width'],
        (int) $size_args['height'],
        $generated ? 'generated' : 'MISSING'
    );
}

NOTE: wp_get_attachment_metadata() returns false (not an empty array) for non-image attachments like PDFs and videos. Always check is_array() on the result before accessing array keys. Also, the 'file' key in the top-level metadata is a relative path from the uploads base directory — not an absolute path. To get the full filesystem path, prepend wp_upload_dir()['basedir'] . '/'. The 'sizes' sub-array only lists sizes that were actually generated — a size will be absent if it was registered after the image was uploaded, or if the image was too small for that size to be created.