How to Get the Featured Image in WordPress (PHP Examples)

To get the featured image of a WordPress post with PHP, use one of three core functions depending on what you need. If you are chasing sizing rather than code, our guide to WordPress featured image size explains which registered size your theme is actually rendering:

// The full <img> tag (easiest, drop into the loop)
the_post_thumbnail( 'large' );

// Just the URL as a string
$url = get_the_post_thumbnail_url( get_the_ID(), 'large' );

// Just the attachment ID
$id = get_post_thumbnail_id( get_the_ID() );

Below I’ll cover all the common variations: displaying as a responsive <img> tag, grabbing just the URL for CSS or srcset, getting the attachment ID for advanced use, checking whether a post even has a featured image, getting any image by its ID, the REST API route, and a handful of edge cases (alt text, lazy loading, fallback images, custom image sizes).

Key Takeaways
  • the_post_thumbnail( 'large' ) prints the full <img> tag; get_the_post_thumbnail_url( $id, 'large' ) gives you just the URL and returns false when there is no featured image.
  • Do not pass 'loading' => 'lazy' for a hero image. WordPress has lazy-loaded images automatically since 5.5 and, since 6.3, gives the likely Largest Contentful Paint image fetchpriority="high"; forcing lazy on it undoes that.
  • Since 5.3 core also generates 1536x1536 and 2048x2048 sizes, so large is not the biggest choice short of full.
  • For any image that is not the featured one, use the attachment ID with wp_get_attachment_image_url() or wp_get_attachment_image().
  • Over the REST API, request a post with _embed and read _embedded["wp:featuredmedia"][0].source_url.

If the “Featured image” panel isn’t showing in the block editor, your theme hasn’t declared support for post thumbnails. Add this to your theme’s functions.php (or a child theme’s functions.php). It uses the after_setup_theme action hook to register support for post thumbnails:

add_action( 'after_setup_theme', function() {
    add_theme_support( 'post-thumbnails' );
} );

Most modern themes (including Hello Elementor, Twenty Twenty-Four, Kadence, and Astra) already enable this. You only need to add it when building a theme from scratch or using a stripped-down starter theme.

Featured image option in the WordPress block editor sidebar
Featured image option in the Gutenberg editor

the_post_thumbnail() echoes a full <img> tag (with srcset, sizes, alt text, and proper classes baked in). Inside the loop, this is a one-liner:

// Inside the loop
if ( has_post_thumbnail() ) {
    the_post_thumbnail( 'large' );
}

// With custom attributes (class, alt override, etc.)
the_post_thumbnail( 'large', array(
    'class' => 'hero-image',
    'alt'   => esc_attr( get_the_title() ),
) );

Notice there is no loading attribute in that example. You will see 'loading' => 'lazy' in a lot of older snippets, and an earlier version of this one had it too. WordPress has added loading="lazy" to images automatically since 5.5, and since 6.3 it goes further: it leaves the first images on the page eager and gives the one it judges likely to be the Largest Contentful Paint a fetchpriority="high". Forcing lazy on a hero image overrides that and delays the biggest thing on the page. If you want to be explicit for an above-the-fold image, say the opposite:

// Above-the-fold hero: tell the browser it matters. Do not lazy-load it.
the_post_thumbnail( 'large', array(
    'class'         => 'hero-image',
    'loading'       => 'eager',
    'fetchpriority' => 'high',
) );

// Below the fold: leave loading out and let WordPress decide (lazy since 5.5).
the_post_thumbnail( 'medium_large', array( 'class' => 'card-image' ) );

Core respects what you pass: a loading value you supply is kept, and since 7.0 setting fetchpriority to low or auto also stops lazy-loading being added. For images below the fold, leave both attributes off and let WordPress handle them.

If you need the HTML as a string (to concatenate or pass around), use get_the_post_thumbnail() instead. Same arguments, but it returns the markup rather than echoing it.

$thumbnail_html = get_the_post_thumbnail( get_the_ID(), 'large' );
echo $thumbnail_html;

When you only need the URL (for a CSS background, an Open Graph tag, a custom image component, or a JSON API response), get_the_post_thumbnail_url() returns a plain string:

// Current post, large size
$url = get_the_post_thumbnail_url( get_the_ID(), 'large' );

// Specific post by ID, full size
$url = get_the_post_thumbnail_url( 42, 'full' );

// Inside the loop, default (post-thumbnail) size
$url = get_the_post_thumbnail_url();

echo esc_url( $url );

Returns false if the post has no featured image, so check for it before echoing. Available since WordPress 4.4 (2015), so effectively everywhere at this point.


The featured image ID is just the attachment ID of the media library item WordPress is using. You need it when you want to call other WordPress functions that take an attachment ID (wp_get_attachment_image_src(), wp_get_attachment_metadata(), get_post_meta(), etc.):

$thumbnail_id = get_post_thumbnail_id( get_the_ID() );

// Example: get the image at an arbitrary size as an array
// Returns [ url, width, height, is_intermediate ]
$image = wp_get_attachment_image_src( $thumbnail_id, 'full' );
if ( $image ) {
    $url    = $image[0];
    $width  = $image[1];
    $height = $image[2];
}

// Example: read the image's alt text from the media library
$alt = get_post_meta( $thumbnail_id, '_wp_attachment_image_alt', true );

Returns 0 if there’s no featured image, and false if the post itself does not exist (since 5.5), so either check the return value or use has_post_thumbnail() first.


has_post_thumbnail() returns a simple boolean. Use it to guard against empty states and to swap in a fallback image:

if ( has_post_thumbnail() ) {
    the_post_thumbnail( 'large' );
} else {
    // Fallback to a default image in your theme
    printf(
        '<img src="%s" alt="" loading="lazy" />',
        esc_url( get_template_directory_uri() . '/assets/default-featured.jpg' )
    );
}

Good rule: every template that displays a featured image should handle the “no image” case. Either a fallback, a different layout, or skipping the image container entirely. A broken <img> tag with no src is the worst outcome.


Method 5: Use the featured image as a CSS background

For hero sections, card overlays, and anywhere a CSS background image is cleaner than an <img>, grab the URL and drop it into an inline style:

<?php
$featured_url = get_the_post_thumbnail_url( get_the_ID(), 'large' );

if ( $featured_url ) :
?>
    <section
        class="post-hero"
        style="background-image: url('<?php echo esc_url( $featured_url ); ?>');">
        <h1><?php the_title(); ?></h1>
    </section>
<?php endif; ?>

For multiple breakpoint sizes, use image-set() in your CSS with different sizes pulled via wp_get_attachment_image_src(). Don’t use full on a hero unless you actually want the original upload (often 2000px+ wide). Pick a size that matches the rendered container.


Every featured image function accepts a size keyword as the second argument. The built-in sizes:

Size keywordDefault dimensionsTypical use
thumbnail150 × 150 (cropped)Small avatar-style lists
medium300 × 300 maxSidebar thumbnails
medium_large768 × anyCard grids
large1024 × 1024 maxMost post heroes
1536x15361536 × 1536 maxWide heroes and srcset steps (since 5.3)
2048x20482048 × 2048 maxThe largest generated size (since 5.3)
fullOriginal upload, or the 2560px scaled copy for big uploadsOnly when you need the raw image
post-thumbnailTheme-definedTheme’s declared default size

The two oddly named sizes arrived with WordPress 5.3’s big-image handling. Anything you upload above 2560px is scaled down and that copy becomes full; 1536x1536 and 2048x2048 exist so srcset has sensible steps between large and that ceiling. Our guide to large image scaling covers the mechanism and how to change it.

You can also register custom sizes in functions.php:

add_action( 'after_setup_theme', function() {
    add_theme_support( 'post-thumbnails' );

    // Hard-cropped 1200x630 for social cards and hero images
    add_image_size( 'hero', 1200, 630, true );

    // Proportional 400-wide for card grids
    add_image_size( 'card', 400, 9999, false );
} );

After registering a new size, regenerate existing images with the Regenerate Thumbnails plugin or wp media regenerate via WP-CLI. New sizes only apply to images uploaded after the declaration.


Get any image by its attachment ID

Everything above assumes the image is the post’s featured image. Often it is not: it is a logo stored in an option, a gallery item, or an ID saved in a custom field. All of those are attachments, and the featured-image functions are thin wrappers around the attachment functions, which you can call directly with any ID:

$attachment_id = 123; // any media library image, featured or not

// URL of a specific size (since 4.4). Returns false if the attachment does not exist.
$url = wp_get_attachment_image_url( $attachment_id, 'large' );

// URL of the original upload
$original = wp_get_attachment_url( $attachment_id );

// A complete <img> tag with srcset, sizes, alt and loading attributes
echo wp_get_attachment_image( $attachment_id, 'large', false, array( 'class' => 'gallery-item' ) );

wp_get_attachment_image_url() has been in core since 4.4 and is the one to reach for when you want a specific size; wp_get_attachment_url() always returns the original file. get_the_post_thumbnail_url() is literally wp_get_attachment_image_url( get_post_thumbnail_id( $post ), $size ), so once you have an ID from anywhere, the featured image stops being a special case.


If you are building a headless front end, an app, or just poking at a site from a script, the featured image is available without any PHP. A post from /wp-json/wp/v2/posts carries the attachment ID in featured_media, and adding _embed pulls the image object into the same response:

GET /wp-json/wp/v2/posts/42?_embed=wp:featuredmedia

// In the JSON response:
_embedded["wp:featuredmedia"][0].source_url                        // the full (or scaled) image
_embedded["wp:featuredmedia"][0].media_details.sizes.large.source_url  // a specific size
_embedded["wp:featuredmedia"][0].alt_text                          // alt text

Without _embed, take the ID from featured_media and fetch /wp-json/wp/v2/media/{id}; the same source_url and media_details.sizes fields are on that object. The sizes listed there are exactly the registered sizes from the table above, including 1536x1536 and 2048x2048. A featured_media of 0 means the post has no featured image.


Block themes: the Post Featured Image block

On a block theme the template work is done by the Post Featured Image block, which calls the same functions with a settings panel in front of them, including a link-to-post toggle, aspect ratio, and an override for the hero image’s size. The PHP on this page is still what you need inside plugins, custom blocks and classic theme templates.


Frequently asked questions

How do I get the featured image URL in WordPress?

Use get_the_post_thumbnail_url( $post_id, $size ). It returns the URL as a string (or false if the post has no featured image). Pass a size keyword like 'large' or 'full' as the second argument. Inside the loop, call get_the_post_thumbnail_url() with no arguments to get the current post’s default size.

What’s the difference between the_post_thumbnail() and get_the_post_thumbnail()?

the_post_thumbnail() echoes the <img> tag directly to the page. get_the_post_thumbnail() returns the HTML as a string so you can modify, concatenate, or pass it around before output. Use the get_ version when you need flexibility; use the_post_thumbnail() for straight-line display in the loop.

How do I get the featured image ID in WordPress?

Use get_post_thumbnail_id( $post_id ). It returns the attachment ID of the featured image, or 0 if there isn’t one. You can then pass that ID to functions like wp_get_attachment_image_src(), wp_get_attachment_metadata(), or get_post_meta() to read the alt text with the _wp_attachment_image_alt meta key.

How do I check if a post has a featured image?

Use has_post_thumbnail(). It returns true if the post has a featured image and false if not. Wrap your the_post_thumbnail() call in an if ( has_post_thumbnail() ) check, or provide a fallback image in the else branch so templates don’t render broken <img> tags.

How do I get the featured image alt text?

The alt text is stored as post meta on the attachment itself: $alt = get_post_meta( get_post_thumbnail_id(), '_wp_attachment_image_alt', true );.

If you’re using the_post_thumbnail(), WordPress includes alt text in the output automatically. You only need to fetch it manually when building custom markup.

If you need to draft alt text in bulk for images that don’t have any, I built Image Caption Generator as an AI helper for exactly that.

Why doesn’t my theme show the featured image option?

Your theme hasn’t declared support for post thumbnails. Add add_theme_support( 'post-thumbnails' ); inside an after_setup_theme action in your theme’s functions.php. Refresh the post editor and the “Featured image” panel will appear in the sidebar.

How do I get a specific image size for the featured image?

Pass a size keyword as the second argument: get_the_post_thumbnail_url( $post_id, 'large' ). WordPress ships with thumbnail, medium, medium_large, large, 1536x1536, 2048x2048, full, and post-thumbnail. You can register custom sizes with add_image_size( 'hero', 1200, 630, true ) in functions.php, then pass 'hero' where you’d pass any built-in size.

Does get_the_post_thumbnail_url() return false when there is no featured image?

Yes. get_the_post_thumbnail_url() returns the URL as a string, or false if the post has no featured image or the requested size cannot be resolved, so test the return value before printing it. get_post_thumbnail_id() behaves slightly differently: it returns 0 when no thumbnail is set and false only when the post itself does not exist.

How do I get any image URL by its attachment ID?

wp_get_attachment_image_url( $attachment_id, 'large' ) returns the URL for a registered size, and wp_get_attachment_url( $attachment_id ) returns the original file. For a complete <img> tag with srcset and alt text, use wp_get_attachment_image( $attachment_id, 'large' ).

How do I get the featured image through the REST API?

Request the post with _embed, for example /wp-json/wp/v2/posts/42?_embed=wp:featuredmedia, and read _embedded["wp:featuredmedia"][0].source_url. Specific sizes are under media_details.sizes. Without _embed, the featured_media field holds the attachment ID, which you can fetch from /wp-json/wp/v2/media/{id}.

Should I lazy-load the featured image?

Not by hand. WordPress has applied loading="lazy" automatically since 5.5 and, since 6.3, leaves the first images eager and marks the likely Largest Contentful Paint image with fetchpriority="high". For an above-the-fold hero, pass 'loading' => 'eager' and 'fetchpriority' => 'high' if you want to be explicit; for everything else, leave the attribute out.


Bottom line

For display in the loop, the_post_thumbnail( 'large' ) is the one-liner to reach for. When you need the URL as a string, use get_the_post_thumbnail_url( $id, 'large' ). When you need the attachment ID for downstream calls, use get_post_thumbnail_id( $id ).

Always guard with has_post_thumbnail() and provide a fallback so your templates don’t break on posts without an image. Leave lazy-loading to WordPress unless the image is a hero, and once you have an attachment ID from anywhere, the wp_get_attachment_* functions work for featured and non-featured images alike.

Related: how to get a post ID, get the post title, get the current page slug, or browse the full WordPress code snippets library.

Picture of Andy Feliciotti

Andy Feliciotti

Andy has been a full time WordPress developer for over 15 years. Through his years of experience has built 100s of sites and learned plenty of tricks along the way. Found this article helpful? Buy Me A Coffee

One Response

Leave a Reply

Your email address will not be published. Required fields are marked *

WordPress Tips Monthly
Get the latest from SmartWP to your inbox.