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.

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.
On this page
- TL;DR
- What does each function actually return?
- Which image size should you ask for?
- Does your theme support featured images?
- get_the_post_thumbnail_url(): the everyday one
- wp_get_attachment_image_src(): when you need width and height
- wp_get_original_image_url(): the real full-size image
- get_post_meta() with _thumbnail_id: the manual route
- The REST API: for headless and JavaScript
- How do you get the featured image URL by post ID?
- How do you get the featured image alt text and caption?
- What if a post has no featured image?
- Does this work in block themes?
- Can you get the URL without code?
- Which method should you use?
- Final take
- Common questions
| Method | Returns | Best for |
|---|---|---|
| get_the_post_thumbnail_url() | URL string, or false | The simple, everyday answer |
| wp_get_attachment_image_src() | URL + width + height | When you need dimensions |
| wp_get_original_image_url() | URL of the untouched upload | When 'full' is not big enough |
| get_post_meta() + _thumbnail_id | Attachment ID | Custom and advanced workflows |
| REST API (_embed) | JSON with every size | Headless 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:
$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.
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 call | What came back |
|---|---|
get_the_post_thumbnail_url( $id ), no size | mountains-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 typo | mountains-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 image | false |
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.

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.

| Size name | Dimensions in my test | File size | Use it for |
|---|---|---|---|
thumbnail | 150 × 150, cropped | 4.6 KB | Small grids, related posts |
medium | 300 × 200 | 10.5 KB | Cards, sidebars |
medium_large | 768 × 512 | 50.7 KB | Mobile-width content |
large | 1024 × 683 | 85.3 KB | Content images, most heroes |
1536x1536 | 1536 × 1024 | 174.4 KB | Wide heroes on big screens |
2048x2048 | 2048 × 1365 | 295.7 KB | Retina full-width banners |
full | 2560 × 1707 | 444.9 KB | Downloads, 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.
Does your theme support featured images?
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:
add_theme_support( 'post-thumbnails' );With that in place, the "Featured image" panel appears in the editor, and the functions have something to read.

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.
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.

Using it as a CSS background
The most common reason to want the URL rather than the image tag is a hero background:
<?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.
$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:
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.
$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.
$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.
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:
// 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:
$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 ) ) . '">';
}
}How do you get the featured image alt text and caption?
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:
$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:
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 developmentFinal 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.

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