Skip to content

How to Get the Featured Image URL in WordPress: 5 Tested Ways

Get the featured image URL in WordPress with one line of PHP. I tested every method on WordPress 7.1, including why 'full' returns a -scaled copy.

Sunny Kumar
Sunny Kumar10 min readHow we test
TL;DR

Use get_the_post_thumbnail_url( $post_id, 'large' ) and check has_post_thumbnail() first. In my test on WordPress 7.1.2, 'full' did not return the original upload. It returned a 2,560px -scaled copy, and a mistyped size name quietly returned that same 445 KB file. Use wp_get_original_image_url() for the true original, and the REST API with _embed=wp:featuredmedia on headless sites.

Which method to use
MethodReturnsBest for
get_the_post_thumbnail_url()URL string, or falseThe simple, everyday answer
wp_get_attachment_image_src()URL + width + heightWhen you need dimensions
wp_get_original_image_url()URL of the untouched uploadWhen 'full' is not big enough
get_post_meta() + _thumbnail_idAttachment IDCustom and advanced workflows
REST API (_embed)JSON with every sizeHeadless and JavaScript apps

You came for one line of code. Here it is. Inside the loop, this returns the featured image URL as a string:

php
$url = get_the_post_thumbnail_url( get_the_ID(), 'large' );

I first wrote this guide in 2018, and back then I told you to use 'full'. That advice has aged badly.

This time I ran every snippet below on a fresh WordPress 7.1.2 site, with a real 4032 × 2688 photo (the size a phone camera takes) set as the featured image. Two results surprised me, and they change which line you should copy.

Tip

Featured image, post thumbnail, image tag: the naming, sorted

WordPress calls it the "featured image" in the editor, but the code uses the older word thumbnail: get_the_post_thumbnail_url(), get_post_thumbnail_id(), and the _thumbnail_id meta key. Same image, two names.

If you want the full <img> tag rather than the URL, use the_post_thumbnail(). This guide is about the URL by itself, which is what you need for backgrounds, Open Graph tags and JavaScript.

What does each function actually return?

Most guides copy the function signatures from the docs. I wanted to see the real output, so I built a small test site in WordPress Playground with the default Twenty Twenty-Five theme and printed what every call returned.

The callWhat came back
get_the_post_thumbnail_url( $id ), no sizemountains-scaled.jpg, 2560 wide
get_the_post_thumbnail_url( $id, 'full' )mountains-scaled.jpg, 2560 wide
get_the_post_thumbnail_url( $id, 'large' )mountains-1024x683.jpg
get_the_post_thumbnail_url( $id, 'lage' ), a typomountains-scaled.jpg, no error
get_the_post_thumbnail_url( $id, array( 600, 400 ) )mountains-768x512.jpg
wp_get_original_image_url( $thumb_id )mountains.jpg, 4032 wide
Any of the above on a post with no imagefalse

Surprise one: 'full' is not your original upload. Since WordPress 5.3, any image wider or taller than 2,560 pixels is scaled down on upload. That -scaled copy becomes "full". The original is kept, but no size name gives it to you.

Surprise two: a misspelt size name fails silently. Ask for 'lage' and WordPress hands back the biggest file it has, with no warning. The same happens when you leave the size out: the default name, post-thumbnail, is not registered by Twenty Twenty-Five, so it falls back to full too.

WordPress 7.1 Edit Media screen for the test image showing File name mountains-scaled.jpg, File size 434 KB, Dimensions 2560 by 1707 pixels, and Original image mountains.jpg
My test upload in WordPress 7.1. The File URL points at the -scaled copy, and the original sits in a separate 'Original image' link.

Which image size should you ask for?

The size name decides how many kilobytes every visitor downloads. I weighed every file WordPress made from my test photo.

Bar chart of file weights WordPress generated from one 4032 by 2688 photo on WordPress 7.1.2: thumbnail 4.6 KB, medium 10.5 KB, medium_large 50.7 KB, large 85.3 KB, 1536x1536 174.4 KB, 2048x2048 295.7 KB, full (the -scaled copy) 444.9 KB, original upload 1,446 KB
Every size WordPress generated from my test photo. 'full' weighs 5.2 times more than 'large'.
Size nameDimensions in my testFile sizeUse it for
thumbnail150 × 150, cropped4.6 KBSmall grids, related posts
medium300 × 20010.5 KBCards, sidebars
medium_large768 × 51250.7 KBMobile-width content
large1024 × 68385.3 KBContent images, most heroes
1536x15361536 × 1024174.4 KBWide heroes on big screens
2048x20482048 × 1365295.7 KBRetina full-width banners
full2560 × 1707444.9 KBDownloads, lightboxes

My default is large. It is wide enough for a normal content column, at about a fifth of the weight of full. Your theme can add its own sizes with add_image_size( 'hero', 1920, 600, true ). Ask for them by name, and spell the name right.

Serving oversized featured images is one of the most common, and most fixable, drags on WordPress site speed. Compressing the files themselves is a separate job, covered in my guide to optimising images in WordPress.

The functions below only return something if the active theme supports featured images. Every modern theme does, including all the default block themes.

If get_the_post_thumbnail_url() keeps returning false even on posts with an image, this is the first thing I check. Add this to your theme's functions.php, inside an after_setup_theme hook:

php
add_theme_support( 'post-thumbnails' );

With that in place, the "Featured image" panel appears in the editor, and the functions have something to read.

The WordPress block editor with the Set featured image panel in the post settings sidebar
Where it all starts: the 'Set featured image' panel in the editor sidebar. Every function below reads whatever you set here.

If your problem is the opposite, the theme printing the image where you do not want it, I have covered hiding the featured image separately.

Method 1

get_the_post_thumbnail_url(): the everyday one

Best for: You want the URL and nothing else.

This is the function to reach for first. get_the_post_thumbnail_url() takes a post ID and a size name, and returns the URL as a string, or false if the post has no featured image.

php
if ( has_post_thumbnail() ) {
    $featured_url = get_the_post_thumbnail_url( get_the_ID(), 'large' );
    echo esc_url( $featured_url );
}

Always pass a size. As my test showed, leaving it out gave me the 2,560px file on a block theme.

The official WordPress developer reference page for get_the_post_thumbnail_url showing its signature and string-or-false return type
The official signature: a post and a size in, a string or false out. Always handle the false.

Using it as a CSS background

The most common reason to want the URL rather than the image tag is a hero background:

php
<?php if ( has_post_thumbnail() ) : ?>
    <div class="hero" style="background-image: url('<?php echo esc_url( get_the_post_thumbnail_url( get_the_ID(), 'large' ) ); ?>');">
        <h1><?php the_title(); ?></h1>
    </div>
<?php endif; ?>

One catch with backgrounds: a CSS background cannot use srcset to pick a smaller file on mobile, and the browser finds it later than an <img> tag. If the hero is your largest element, an <img> tag with srcset usually loads faster. That hurts or helps your Core Web Vitals more than any other choice here.

Method 2

wp_get_attachment_image_src(): when you need width and height

Best for: Open Graph tags, or anything that needs dimensions.

When you need the width and height alongside the URL, wp_get_attachment_image_src() is the one. It takes the attachment ID, which you get from get_post_thumbnail_id(), and returns an array.

php
$thumbnail_id = get_post_thumbnail_id( get_the_ID() );
$image        = wp_get_attachment_image_src( $thumbnail_id, 'large' );

if ( $image ) {
    $url    = $image[0]; // the URL
    $width  = $image[1]; // width in pixels
    $height = $image[2]; // height in pixels
}

In my test, 'large' came back as [ …mountains-1024x683.jpg, 1024, 683, true ]. The last value tells you it is a resized copy.

The Open Graph use case

This is the cleanest way to print og:image tags with correct dimensions, so social platforms can draw the preview without guessing:

php
function tgx_og_image_tags() {
    if ( is_singular() && has_post_thumbnail() ) {
        $img = wp_get_attachment_image_src( get_post_thumbnail_id(), 'large' );
        if ( $img ) {
            printf( '<meta property="og:image" content="%s">' . "\n", esc_url( $img[0] ) );
            printf( '<meta property="og:image:width" content="%s">' . "\n", esc_attr( $img[1] ) );
            printf( '<meta property="og:image:height" content="%s">' . "\n", esc_attr( $img[2] ) );
        }
    }
}
add_action( 'wp_head', 'tgx_og_image_tags' );

Facebook and LinkedIn prefer a 1200 × 630 image, which is wider than large. For a perfect fit, register it with add_image_size( 'og', 1200, 630, true ) and ask for 'og' instead.

If you run an SEO plugin, check it is not printing these tags already. Two sets of og:image tags on one page is a common mess, and my guide to adding meta tags in WordPress shows how to check.

Method 3

wp_get_original_image_url(): the real full-size image

Best for: Downloads, print, or zoom where the -scaled copy is not enough.

If you truly need the file that was uploaded, not the 2,560px copy, use wp_get_original_image_url(). In my test it was the only call that returned the 4032px mountains.jpg.

php
$thumbnail_id = get_post_thumbnail_id( get_the_ID() );
$original_url = wp_get_original_image_url( $thumbnail_id );

if ( $original_url ) {
    echo esc_url( $original_url );
}

For images under the 2,560px limit, nothing was scaled, so it returns the same file as 'full'.

Be careful where you use it. My original weighed 1,446 KB, more than three times the -scaled copy. I only reach for it on a download link or a zoom view, never inside a page layout.

You can also switch scaling off with add_filter( 'big_image_size_threshold', '__return_false' );. It only affects new uploads, and I would not do it. The scaled copy protects you from multi-megabyte phone photos landing on your pages.

Method 4

get_post_meta() with _thumbnail_id: the manual route

Best for: Custom queries and migrations where you want the raw ID.

Under the hood, the featured image is just a post meta value: the attachment ID stored against the _thumbnail_id key. In my test post it read "6", the ID of the uploaded photo.

php
$thumbnail_id = get_post_meta( get_the_ID(), '_thumbnail_id', true );

if ( $thumbnail_id ) {
    $image_url = wp_get_attachment_image_url( $thumbnail_id, 'large' );
    echo esc_url( $image_url );
}

That value lives in the wp_postmeta table, which I explain in where WordPress stores posts and pages. It is handy in raw database queries and migration scripts.

You rarely need this route in a theme. get_post_thumbnail_id() is a friendlier wrapper around the same lookup.

Method 5

The REST API: for headless and JavaScript

Best for: Headless WordPress, a React or Next.js front end, or any external app.

If your front end is not PHP, you fetch the featured image over the REST API. The trick is the _embed parameter, which pulls the image data into the same response, so you skip a second request.

js
async function getFeaturedImageUrl( postId ) {
  const res  = await fetch(
    `https://yoursite.com/wp-json/wp/v2/posts/${postId}?_embed=wp:featuredmedia`
  )
  const post = await res.json()

  const media = post._embedded?.['wp:featuredmedia']?.[0]
  if ( ! media ) return null

  return {
    full:  media.source_url,
    large: media.media_details?.sizes?.large?.source_url ?? media.source_url,
  }
}

Note _embed=wp:featuredmedia, not a bare _embed. The bare version also pulled in the author and categories. On my near-empty test post, naming just the image cut the response from 6,228 to 4,897 bytes. Adding _fields took it down to 4,080. The REST API handbook documents both.

Two things I confirmed in the output. source_url is the -scaled copy, same as in PHP. And a post with no image returns featured_media: 0 with no wp:featuredmedia key at all, which is why the code above checks before reading it.

How do you get the featured image URL by post ID?

The snippets that use get_the_ID() only work inside the loop. That is the mistake I see most often. Outside it, in a widget, a plugin or a custom query, pass the post ID directly:

php
// Featured image URL for post 42, from anywhere
$url = get_the_post_thumbnail_url( 42, 'large' );

For a list of posts, loop over them and call it for each:

php
$recent = get_posts( array( 'numberposts' => 5 ) );

foreach ( $recent as $post ) {
    $img = get_the_post_thumbnail_url( $post->ID, 'medium' );
    if ( $img ) {
        echo '<img src="' . esc_url( $img ) . '" alt="' . esc_attr( get_post_meta( get_post_thumbnail_id( $post->ID ), '_wp_attachment_image_alt', true ) ) . '">';
    }
}

A URL on its own is only half an image. For the alt attribute and the caption, these two calls returned exactly what I typed in the Media Library:

php
$thumb_id = get_post_thumbnail_id( get_the_ID() );

$alt     = get_post_meta( $thumb_id, '_wp_attachment_image_alt', true );
$caption = get_the_post_thumbnail_caption( get_the_ID() );

In my test, $alt came back as "Mountain range at sunrise" and the caption as the text I saved on the attachment. If the alt field is empty, fall back to the post title rather than printing an empty alt.

What if a post has no featured image?

Every method above returns false, 0 or an empty string when there is no image. I checked all three on an image-less post. On a real site, some posts always slip through without one, so plan for it.

The cleanest pattern is a small helper that falls back to a default image in your theme:

php
function tgx_featured_or_fallback( $post_id = null, $size = 'large' ) {
    $url = get_the_post_thumbnail_url( $post_id, $size );

    if ( ! $url ) {
        $url = get_template_directory_uri() . '/assets/default-featured.jpg';
    }

    return esc_url( $url );
}

Call tgx_featured_or_fallback() anywhere, and you never print an empty src again.

Does this work in block themes?

Yes. I ran every PHP snippet here with Twenty Twenty-Five active, and they all behaved as shown. Use them in a plugin, a custom block, or a child theme's functions.php.

If you only need the image on the page, you may not need code at all. The Post Featured Image block shows it in any template. The Cover block can use the post's featured image as its background, which covers the classic hero-with-text layout.

The code route still matters for things blocks do not cover: Open Graph tags, JSON feeds, emails, and headless front ends.

Can you get the URL without code?

If you only need the URL once, skip the PHP. Open Media → Library, click the image, and copy the File URL from the details panel, as in my screenshot above. Remember it will be the -scaled copy for big uploads.

For a published page, right-click the featured image and choose "Copy image address". That gives you whichever size the theme rendered, which is often not the one you expect.

Which method should you use?

For nine cases out of ten: get_the_post_thumbnail_url( $id, 'large' ), wrapped in has_post_thumbnail().

Reach for wp_get_attachment_image_src() when you need width and height, wp_get_original_image_url() only when you truly need the untouched upload, and the REST API with _embed=wp:featuredmedia on headless sites. Add the fallback helper on any site where some posts will not have an image.

Want this kind of WordPress work done right?

Custom theme code, headless builds and Core Web Vitals, handled end to end. Tell us what you are building, and the first reply comes from Sunny, not a sales team.

See WordPress development

Final take

The featured image URL is still one line of PHP. The line I recommend has just changed: ask for 'large', not 'full'.

My test made the reason plain. On a modern WordPress site, "full" is a 2,560px scaled copy weighing five times more than "large". A typo in the size name hands you that same heavy file without a word.

Check the image exists, name the size on purpose, and spell it right. Those three habits prevent the broken layouts and slow pages that the one-liner alone can quietly cause.

Common questions

What is the easiest way to get the featured image URL in WordPress?

Use get_the_post_thumbnail_url( get_the_ID(), 'large' ). It returns the URL as a string, or false when the post has no featured image. Wrap it in has_post_thumbnail() so a post without an image never prints an empty src.

Why does my featured image URL end in -scaled.jpg?

Since WordPress 5.3, any upload wider or taller than 2,560 pixels is scaled down, and that -scaled copy becomes the 'full' size. The original file is still on the server. Get its URL with wp_get_original_image_url( get_post_thumbnail_id() ).

How do I get the featured image URL by post ID, outside the loop?

Pass the ID directly: get_the_post_thumbnail_url( 42, 'large' ) returns the URL for post 42 from anywhere, including a widget, a custom query or a plugin. It does not need to run inside the loop.

How do I get the featured image URL with the WordPress REST API?

Request /wp-json/wp/v2/posts/{id}?_embed=wp:featuredmedia and read _embedded["wp:featuredmedia"][0].source_url. The media_details.sizes object holds the URL for every registered size. If the post has no featured image, featured_media is 0 and the embed key is missing, so check before reading it.

How do I get the width and height of the featured image too?

Use wp_get_attachment_image_src( get_post_thumbnail_id(), 'large' ). It returns an array where index 0 is the URL, 1 is the width and 2 is the height. That is exactly what Open Graph image tags need.

What image sizes can I request?

By default: thumbnail (150x150, cropped), medium (300), medium_large (768 wide), large (1024), 1536x1536, 2048x2048 and full. Register your own with add_image_size() and ask for it by name. A misspelt name silently falls back to full.

Written by
Sunny Kumar
Sunny KumarSEO Specialist & product builder

SEO Specialist and product builder with 10+ years in search. The notes come from the work, not the theory.

Work with TheGuideX

Reading about it is the easy part.

Send us the site and what you are seeing. Your first reply comes from Sunny Kumar, with where he thinks the cause is and whether we are the right fix.