How to Use get_the_post_thumbnail in WordPress
Learn how to return featured-image HTML with get_the_post_thumbnail(), choose sizes, handle missing images, and customize output safely.
get_the_post_thumbnail() retrieves a post’s featured image as an HTML string. Use it when PHP needs to store, inspect, modify, or conditionally render the markup. If you only want to print the image immediately, use the_post_thumbnail(), which echoes the same generated HTML.
<?php
$thumbnail_html = get_the_post_thumbnail(
$post_id,
'medium',
array( 'class' => 'article-card__image' )
);
echo $thumbnail_html;
?>
1. Enable featured images in the theme
Your theme must declare post-thumbnail support. Put this in functions.php or an included setup file on after_setup_theme, before WordPress reaches init.
<?php
function acme_theme_setup() {
add_theme_support( 'post-thumbnails' );
}
add_action( 'after_setup_theme', 'acme_theme_setup' );
?>
You can restrict support to selected post types:
<?php
function acme_theme_setup() {
add_theme_support(
'post-thumbnails',
array( 'post', 'page', 'product' )
);
}
add_action( 'after_setup_theme', 'acme_theme_setup' );
?>
This enables the featured-image control in the editor and allows thumbnail functions to resolve an attachment.
2. Understand the function signature
get_the_post_thumbnail( $post = null, $size = 'post-thumbnail', $attr = '' )
| Argument | Accepted values | Purpose |
|---|---|---|
$post |
Post ID, WP_Post object, or null |
Selects the post. null uses the global post. |
$size |
Registered size name or width/height array | Chooses the image derivative. |
$attr |
Attribute array or query-string | Adds HTML attributes such as class, alt, or loading. |
The return value is an HTML string. It is empty when the post cannot be resolved or has no featured image.
3. Return markup instead of echoing it
Use the getter when markup must be retained for a card, JSON preparation, conditional wrapper, or later processing.
<?php
$post_id = 42;
if ( has_post_thumbnail( $post_id ) ) {
$thumbnail_html = get_the_post_thumbnail(
$post_id,
'medium',
array(
'class' => 'article-card__image',
'loading' => 'lazy',
)
);
echo '<figure class="article-card__media">';
echo $thumbnail_html;
echo '</figure>';
}
?>
Use the_post_thumbnail() when the template should output the image at that point:
<?php
if ( has_post_thumbnail() ) {
the_post_thumbnail( 'large', array( 'class' => 'hero-image' ) );
}
?>
the_post_thumbnail() echoes the value returned by get_the_post_thumbnail(); it does not provide a separate rendering format.
4. Choose an image size
The default is post-thumbnail. WordPress distinguishes this special theme size from the thumbnail size configured under Media Settings. Available names depend on the site’s registered sizes.
Use a registered size
<?php
echo get_the_post_thumbnail( get_the_ID(), 'large' );
?>
Common names include thumbnail, medium, medium_large, large, and full, but their dimensions are configurable and should not be assumed.
Register a named size
<?php
function acme_register_image_sizes() {
add_image_size( 'article-card', 640, 360, true );
}
add_action( 'after_setup_theme', 'acme_register_image_sizes' );
// In a template:
echo get_the_post_thumbnail( get_the_ID(), 'article-card' );
?>
The fourth argument controls cropping. A value of true performs a hard crop; false preserves the aspect ratio while fitting within the requested dimensions.
Configure the special post-thumbnail size
<?php
function acme_theme_setup() {
add_theme_support( 'post-thumbnails' );
set_post_thumbnail_size( 1200, 630, true );
}
add_action( 'after_setup_theme', 'acme_theme_setup' );
?>
Crop positions can be specified as array( 'left', 'top' ), array( 'center', 'center' ), or another horizontal and vertical combination. Changing a registered size does not resize existing uploads; regenerate derivatives when older media must receive the new dimensions.
Request one-off dimensions
<?php
$thumbnail_html = get_the_post_thumbnail(
get_the_ID(),
array( 640, 360 ),
array( 'class' => 'ratio-16x9' )
);
echo $thumbnail_html;
?>
5. Pass attributes safely
An associative array is easiest to read and maintain:
<?php
$attrs = array(
'class' => 'post-thumbnail rounded',
'alt' => get_the_title(),
'loading' => 'lazy',
'decoding' => 'async',
'data-context' => 'archive-card',
);
echo get_the_post_thumbnail( get_the_ID(), 'medium', $attrs );
?>
The function passes the selected attachment, size, and attributes to wp_get_attachment_image(). WordPress generates the image element and responsive attributes from the attachment metadata. Avoid concatenating unescaped user input into an attribute value; sanitize values before passing them.
6. Handle missing posts and thumbnails
Do not emit an empty wrapper when a featured image is optional.
<?php
$post_id = 42;
if ( ! get_post( $post_id ) || ! has_post_thumbnail( $post_id ) ) {
echo '<div class="article-card__placeholder">';
echo esc_html__( 'No image available', 'acme' );
echo '</div>';
} else {
echo get_the_post_thumbnail(
$post_id,
'article-card',
array( 'class' => 'article-card__image' )
);
}
?>
You can also rely on the empty-string return and test it directly:
<?php
$html = get_the_post_thumbnail( $post_id, 'medium' );
if ( '' !== $html ) {
echo $html;
}
?>
When only the source URL is needed, use get_the_post_thumbnail_url( $post, $size ) instead of parsing an image element:
<?php
$url = get_the_post_thumbnail_url( get_the_ID(), 'large' );
if ( $url ) {
echo '<meta property="og:image" content="' . esc_url( $url ) . '">';
}
?>
7. Use it outside the main loop
Pass an explicit post ID or object when working in a widget, related-post query, REST callback, or custom loop. Passing no post argument depends on the global post.
<?php
$related = new WP_Query(
array(
'post_type' => 'post',
'posts_per_page' => 3,
'post__not_in' => array( get_the_ID() ),
)
);
if ( $related->have_posts() ) {
while ( $related->have_posts() ) {
$related->the_post();
echo get_the_post_thumbnail( get_the_ID(), 'medium' );
}
}
wp_reset_postdata();
?>
8. Customize size and HTML with hooks
The function applies the requested size through post_thumbnail_size, fires begin_fetch_post_thumbnail_html and end_fetch_post_thumbnail_html around retrieval, and filters the final markup through post_thumbnail_html.
Change the requested size
<?php
function acme_card_thumbnail_size( $size, $post_id ) {
if ( is_admin() ) {
return $size;
}
if ( 'post' === get_post_type( $post_id ) ) {
return 'article-card';
}
return $size;
}
add_filter( 'post_thumbnail_size', 'acme_card_thumbnail_size', 10, 2 );
?>
Adjust the generated HTML
<?php
function acme_filter_thumbnail_html( $html, $post_id, $post_thumbnail_id, $size, $attr ) {
if ( '' === $html ) {
return $html;
}
return '<div class="image-frame">' . $html . '</div>';
}
add_filter( 'post_thumbnail_html', 'acme_filter_thumbnail_html', 10, 5 );
?>
Keep filters narrow. Check the post ID, size, or an attribute before changing every featured image on the site.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Function returns an empty string | The post ID is invalid or no featured image is assigned. | Verify get_post( $post_id ) and has_post_thumbnail( $post_id ). |
| Featured-image box is missing in the editor | The theme has not declared support, or support is restricted to another post type. | Add add_theme_support( 'post-thumbnails' ) on after_setup_theme. |
| Support appears inconsistently | The declaration runs after init. |
Move setup earlier, normally to after_setup_theme. |
| Custom size outputs the original dimensions | The size was registered after uploads were created. | Regenerate image derivatives, then clear relevant caches. |
| Wrong image is selected | The code relies on the global post inside a custom query. | Pass the intended post ID or WP_Post object explicitly. |
| Wrapper appears with no image | The template prints surrounding markup without checking the return. | Guard with has_post_thumbnail() or test for an empty string. |
| Only a URL is needed | Image HTML is being parsed unnecessarily. | Use get_the_post_thumbnail_url(). |
| Filter changes unrelated images | post_thumbnail_html runs globally. |
Limit the callback by post type, ID, size, or attributes. |
10. Performance, reliability, and accessibility
- Request the smallest registered derivative that meets the display size so WordPress can emit an appropriate responsive source set.
- Register stable named sizes for repeated layouts instead of creating many one-off dimensions.
- Use
loading="lazy"for below-the-fold cards; keep the primary hero image eager when it is part of the initial view. - Regenerate derivatives after changing dimensions so production requests do not encounter missing files.
- Always provide meaningful alternative text. The attachment’s stored alt text is preferable; override it only when the image meaning changes in context.
- Cache repeated template output at the page or fragment level when a page renders the same thumbnail many times.
11. Or skip the browser setup
If the goal is to capture a rendered page rather than retrieve a WordPress attachment, ScreenshotNeo provides a website screenshot API. It removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status.
See the ScreenshotNeo API documentation for the full option list.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
It also supports full-page capture, element selectors, dark mode, device presets, custom CSS and JavaScript, waits, request blocking, headers and cookies, PDF output, caching, signed links, asynchronous jobs, bulk capture, and an MCP server with take_screenshot, get_page_info, and capture_pdf for AI clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
12. FAQ
Does get_the_post_thumbnail() return an image URL?
No. It returns an HTML image element. Use get_the_post_thumbnail_url() when you need only the URL.
Can I pass a WP_Post object?
Yes. The first argument accepts a post ID, a WP_Post object, or null for the global post.
Why is post-thumbnail different from thumbnail?
post-thumbnail is the special theme size associated with featured-image support. thumbnail is the Media Settings size; both can be registered or configured differently.
Do size changes update old uploads?
No. Existing files need regenerated derivatives before the new size is available for older media.
Should I escape the returned HTML?
Do not run the complete image HTML through esc_html(), because that would display tags as text. Sanitize values before passing attributes and output the trusted WordPress-generated markup in the intended HTML context.


