Skip to content

Early-bird lifetime licence: $199 once, for the first 200 buyers only. See the offer

Use a YouTube thumbnail as the featured image (no plugin)

If you post a lot of videos, your blog list and related posts are full of empty boxes, unless you download every thumbnail and upload it as a featured image. WordPress itself has never done this for you. When thumbnails “just appeared” before, they came from a plugin (Jetpack’s Related Posts does it, for example). When that plugin changes, they disappear.

How it works

YouTube serves a thumbnail for every video at a predictable address: https://i.ytimg.com/vi/VIDEO_ID/hqdefault.jpg. The snippet finds the first YouTube video in the post (a YouTube embed, a youtu.be link or a Short) and, only when the post has no featured image, uses that thumbnail instead.

<?php

// The ID of the first YouTube video in a post, or ''.
$video_id = static function ( $post_id ) {
	$content = (string) get_post_field( 'post_content', $post_id );
	return preg_match( '~(?:youtube\.com/(?:watch\?(?:[^"\s<]*&(?:amp;)?)?v=|embed/|shorts/|live/)|youtu\.be/)([A-Za-z0-9_-]{11})~', $content, $match ) ? $match[1] : '';
};

// Classic themes ask "is there a featured image?" first.
add_filter(
	'has_post_thumbnail',
	static function ( $has, $post ) use ( $video_id ) {
		$post = get_post( $post );
		if ( $has || ! $post || is_admin() || ( is_singular() && get_queried_object_id() === $post->ID ) ) {
			return $has;
		}
		return '' !== $video_id( $post->ID );
	},
	10,
	2
);

add_filter(
	'post_thumbnail_html',
	static function ( $html, $post_id, $thumbnail_id, $size, $attr ) use ( $video_id ) {
		if ( '' !== $html || is_admin() || ( is_singular() && get_queried_object_id() === (int) $post_id ) ) {
			return $html; // A real featured image wins; on the post itself the video is already there.
		}
		$id = $video_id( $post_id );
		if ( '' === $id ) {
			return $html;
		}
		$attr  = is_array( $attr ) ? $attr : wp_parse_args( $attr );
		$style = empty( $attr['style'] ) ? '' : ' style="' . esc_attr( $attr['style'] ) . '"';
		return '<img src="' . esc_url( 'https://i.ytimg.com/vi/' . $id . '/hqdefault.jpg' ) . '" alt="' . esc_attr( get_the_title( $post_id ) ) . '" class="wp-post-image" width="480" height="360" loading="lazy" decoding="async"' . $style . '>';
	},
	10,
	5
);

Add it to your child theme’s functions.php or a code snippets plugin.

Details that matter

  • A real featured image always wins. The snippet only fills the gap.
  • Not on the post itself. There the video is already playing, so a thumbnail above it would be a duplicate. It shows in lists, related posts and query loops.
  • Block and classic themes. Block themes use the Featured Image block, which goes through post_thumbnail_html. Classic themes usually ask has_post_thumbnail() first, so the snippet answers that too.
  • CSS backgrounds won’t work. Themes that use the featured image’s URL as a background image read it in another way, so they won’t pick it up.
  • Privacy: the image loads from YouTube’s servers, so the visitor’s browser contacts Google. In the EU, mention it in your privacy policy.

Tested with

WordPress 7.1 with Twenty Twenty-Five (a query loop) and Storefront (the blog list): YouTube embeds and Shorts get thumbnails, posts without a video are untouched, and the video post’s own page doesn’t show the thumbnail.

In Snipfire, this is “YouTube thumbnail when there’s no featured image” in the Images and media library.

Move your snippets over this afternoon.

Importing changes nothing until you switch over, and your old shortcodes keep working.