WordPress’s featured image (also called post thumbnail) system stores the attachment ID as post meta under the key _thumbnail_id. The display functions — the_post_thumbnail() and get_the_post_thumbnail() — retrieve that ID and generate a complete <img> tag with the correct src, srcset, sizes, width, height, and alt attributes. What is less well known is that every part of this system is accessible programmatically: you can set a featured image by attachment ID, check whether one exists, retrieve the URL directly without an HTML tag, and use the same attachment in multiple output contexts with different sizes. This is particularly useful for import scripts, WooCommerce product creation, REST API endpoints, or any workflow where images are assigned to posts without user interaction. This article covers the main functions, how to set a featured image from a URL (including the upload step), and how to remove it.
Problem: You need to programmatically set, check, retrieve, or remove a featured image on a post — for example in an import script, a REST API handler, or a WooCommerce product creation routine.
Solution: Use set_post_thumbnail() to assign an attachment ID, get_the_post_thumbnail_url() to retrieve the URL, and delete_post_thumbnail() to remove it. To set a featured image from a remote URL, use media_sideload_image().
<?php
// ── Check and retrieve ───────────────────────────────────────────────────
$post_id = 42;
if ( has_post_thumbnail( $post_id ) ) {
$thumbnail_id = get_post_thumbnail_id( $post_id );
$thumbnail_url = get_the_post_thumbnail_url( $post_id, 'large' ); // any registered size
$thumbnail_tag = get_the_post_thumbnail( $post_id, 'medium', [ 'class' => 'hero-image' ] );
echo $thumbnail_tag; // outputs
}
// ── Set by attachment ID ─────────────────────────────────────────────────
set_post_thumbnail( $post_id, $attachment_id );
// ── Remove ───────────────────────────────────────────────────────────────
delete_post_thumbnail( $post_id );
// ── Set from a remote URL ────────────────────────────────────────────────
function set_featured_image_from_url( $post_id, $image_url ) {
if ( ! function_exists( 'media_sideload_image' ) ) {
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
}
// media_sideload_image downloads the URL, uploads it to the Media Library,
// and returns the attachment ID when called with 'id' as the 4th argument
$attachment_id = media_sideload_image(
esc_url_raw( $image_url ),
$post_id,
'', // description (empty = use filename)
'id' // return the attachment ID
);
if ( is_wp_error( $attachment_id ) ) {
return $attachment_id;
}
return set_post_thumbnail( $post_id, $attachment_id );
}
Rendering a featured image with a fallback in a template:
<?php
if ( has_post_thumbnail() ) {
the_post_thumbnail( 'medium_large', [
'loading' => 'lazy',
'class' => 'card__image',
'alt' => get_the_title(), // explicit alt overrides the attachment alt
] );
} else {
echo '<img src="' . esc_url( get_template_directory_uri() . '/img/placeholder.jpg' ) . '"
alt="" class="card__image" loading="lazy">';
}
NOTE: media_sideload_image() makes an HTTP request to the remote URL and stores the image in wp-content/uploads/, so it should never be called on a front-end page load. Run it in background processes, WP-CLI commands, or admin-only import routines. Also, calling it without requiring the three admin include files first will result in a fatal error — the function is not auto-loaded on the front end.