Skip to content

How to lazy load images and videos in WordPress

WordPress already lazy-loads images and iframes with the browser's own loading attribute, so images need no plugin for it. The work is checking that it runs, keeping the main image at the top of the page out of it, and handling background images and video.

By
WP Ministry
Published
Tested on
WordPress 7.1.3, PHP 8.3.35

In short

  • WordPress adds loading="lazy" to images since 5.5 and to iframes since 5.7, as long as the tag has a width and a height.
  • It leaves the first three images or iframes in the content alone, and since 6.3 it marks the likely main image with fetchpriority="high".
  • The main image at the top of the page must not be lazy-loaded. Check it in the page source.
  • The attribute does nothing for an image set as a CSS background.
  • A YouTube or Vimeo embed is an iframe. For a video you host, set preload to none and add a poster image.
  • Run one lazy-loading mechanism. A script-based one can hide the main image from the browser.

Lazy loading means the browser fetches an image, an iframe or a video only when the visitor scrolls near it, not when the page opens. WordPress already does this for images and iframes. It adds the browser's own loading="lazy" attribute to images since version 5.5 and to iframes since 5.7. No plugin and no script is involved.

So the job is rarely to switch lazy loading on. It is to check that it runs, to keep the main image at the top of the page out of it, and to handle what the attribute does not cover well: CSS background images and video.

What WordPress does by itself

  • Images, since 5.5. WordPress adds loading="lazy" to every image tag that has a width and a height. That covers images in post content, excerpts, text widgets, avatars, and images a theme prints through wp_get_attachment_image().
  • Iframes, since 5.7. The same attribute goes on iframes in post content, excerpts and text widgets, again only when the tag has both dimensions. A video embedded by pasting its address is an iframe, and gets the attribute when the service supplies a width and a height.
  • The first images are left alone. Since 5.9, WordPress skips the first image or iframe in the content, featured images included, so that it loads at once. Since 6.3 it skips the first three, and it also leaves alone images in a classic theme's header.
  • The main image is given priority, since 6.3. WordPress adds fetchpriority="high" to the image it judges most likely to be the largest one in view. The image must cover at least 50,000 square pixels, width times height. It never puts this and loading="lazy" on the same image.
  • Your own attribute wins. If a tag already has a loading or fetchpriority attribute, WordPress keeps it as it is.

The dimensions matter for a reason. With a width and a height, the browser can keep a space of the right shape before the file arrives, so the page does not jump as lazy images load.

The browser decides when "near" is. MDN describes it as a calculated distance from the visible part of the page, and web.dev notes that in Chrome the distance changes with the kind of connection. A browser that does not know the attribute ignores it and loads the image the ordinary way.

Because the browser does this itself, web.dev's guidance is that you probably do not need a separate library or script to lazy-load images.

Check that it is working

  1. Step 1: Read the page source

    Open a post with several images, view the page's source, and search for loading="lazy". Images further down should carry it, each with its real address in src. The first image should not carry it. The large image at the top should ideally carry fetchpriority="high".

  2. Step 2: Look at the images that were skipped

    An image tag with no width and height gets no loading attribute from WordPress. That is usual for an image pasted in as HTML or loaded from another site. How to improve Core Web Vitals explains when WordPress fills the dimensions in and when you have to.

  3. Step 3: Watch the requests while you scroll

    Open the browser's developer tools and go to the Network panel. In Chrome, choose the "Img" filter and check "Disable cache", then reload the page and scroll down slowly. New image requests should appear as you go, a little before each image is on screen. If every image on a long page is listed straight after the reload, nothing is being lazy-loaded.

  4. Step 4: Check the main image in a report

    Chrome's performance tools have an insight named "LCP request discovery". For the main image, it checks that lazy loading is not applied, that the image can be found in the page's HTML, and that fetchpriority="high" is set.

Keep the main image out of it

The browser holds a lazy image back until it knows the image is near the screen. For the picture a visitor sees first, that is a delay with nothing gained. web.dev puts it bluntly: never lazy-load the image that counts as the page's Largest Contentful Paint, because doing so always adds delay. How to improve Core Web Vitals covers that measurement.

WordPress cannot see your layout. It counts images in the order they come. That guess goes wrong in a few usual ways:

  • A theme or page builder writes its own image tags. If it adds loading="lazy" to all of them, WordPress keeps what it finds.
  • More than three images share the first screen, such as a grid of products or posts. The fourth is lazy-loaded.
  • A slider or a script puts the image on the page after it loads. The browser cannot find it in the HTML until the script has run.
  • A plugin's own lazy loading catches the first image, or the banner is a CSS background. Both have sections below.

There are four ways to exempt an image. Code goes in a child theme's functions.php or a small plugin of your own, not in the parent theme. How to create a WordPress child theme shows how.

Set the attribute on the tag

Where you write the image tag yourself, in a block of custom HTML or a template, say what you want. WordPress keeps it.

html
<img src="https://example.com/wp-content/uploads/2026/10/storefront.jpg" alt="The storefront on Main Street" width="1600" height="900" loading="eager" fetchpriority="high">

In a theme template, wp_get_attachment_image() takes the same two attributes. Here 42 stands for the image's ID in the media library.

php
echo wp_get_attachment_image(
	42,
	'full',
	false,
	array(
		'loading'       => 'eager',
		'fetchpriority' => 'high',
	)
);

Use fetchpriority="high" on one image per page, the main one.

Raise the number of images WordPress skips

The wp_omit_loading_attr_threshold filter sets how many of the first images and iframes in the content are left alone. The default is 3. This raises it to 6 on the blog page and on archives, for a theme that shows posts in a wide grid.

functions.php
function example_lazy_threshold( $omit_threshold ) {
	if ( is_home() || is_archive() ) {
		return 6;
	}
	return $omit_threshold;
}
add_filter( 'wp_omit_loading_attr_threshold', 'example_lazy_threshold' );

Exempt one image in the content

The wp_img_tag_add_loading_attr filter is given each image tag in the content. Returning false leaves the attribute off. This matches an image by part of its file name.

functions.php
function example_skip_lazy_for_banner( $value, $image, $context ) {
	if ( false !== strpos( $image, 'storefront-banner' ) ) {
		return false;
	}
	return $value;
}
add_filter( 'wp_img_tag_add_loading_attr', 'example_skip_lazy_for_banner', 10, 3 );

For an iframe, the filter is wp_iframe_tag_add_loading_attr, and it works the same way.

Use the plugin's exclusion list

If a plugin does the lazy loading, exempt the image in the plugin. WP Rocket has a field named "Excluded images or iframes" that takes a file name, a class or another attribute from the tag, one per line. LiteSpeed Cache has a Media Excludes tab with "Lazy Load Image Excludes" and matching lists for class names.

After any of these, clear your page cache and read the page source again.

Background images set in CSS

The loading attribute cannot be used on a CSS background image. A picture that a theme or page builder sets as a background has no image tag, so it is not lazy-loaded this way, and none of WordPress's handling above applies.

For a banner at the top, the trouble is the opposite of lazy loading. The browser looks ahead through the HTML for files to fetch early, and a background image is not in the HTML. It is found only once the stylesheet has been read. If your theme or builder can show that banner as a real image, choose that. If not, tell the browser about the file in the head of the page:

functions.php
function example_preload_banner() {
	if ( is_front_page() ) {
		echo '<link rel="preload" fetchpriority="high" as="image" href="https://example.com/wp-content/uploads/2026/10/banner.webp" type="image/webp">' . "\n";
	}
}
add_action( 'wp_head', 'example_preload_banner', 1 );

Preload only a file that page really shows. Anything else is downloaded for nothing.

The attribute cannot delay background images further down either. WP Rocket's documentation describes a separate feature of its own for CSS background images. If a plugin does this for you, check that the banner at the top is on its exclusion list.

Video

YouTube and Vimeo embeds

An embedded video is an iframe, so the iframe rules above apply. The 5.7 release note points out that only some services supply a width and a height with their embed, and WordPress adds loading="lazy" only when both are there. Check the page source for the attribute on the <iframe> tag. An embed that comes first in the content is among the three WordPress skips.

Lazy loading only delays the player. When it loads, it brings its scripts with it. web.dev's guidance on embeds says many popular ones include over 100 KB of JavaScript, and some as much as 2 MB.

A facade avoids that for visitors who never press play. It is a still picture that looks like the player and loads the real one on a click. In the YouTube example in Chrome's documentation, the facade weighs 3 KB and the player it stands in for weighs 540 KB.

On WordPress, a plugin can provide it. WP Rocket's option is named "Replace YouTube iframe with preview image". Its documentation says it swaps the iframe for an image of the video's thumbnail and loads the iframe when a visitor clicks.

A facade has a cost. Chrome's documentation warns that autoplay may not work consistently for a video loaded this way.

Videos you host yourself

The Video block has two settings for this in its sidebar.

  • Preload. The choices are Auto, Metadata and None. With None, WordPress's documentation says nothing about the file is downloaded until the visitor presses play. With Auto, the whole file can be downloaded whether or not anyone plays it.
  • Poster image. A picture shown before the video plays. Without one, MDN notes, nothing is displayed until the first frame is available.

If you write the tag yourself, it looks like this:

html
<video controls preload="none" poster="https://example.com/wp-content/uploads/2026/10/tour-poster.jpg" width="1280" height="720">
  <source src="https://example.com/wp-content/uploads/2026/10/tour.mp4" type="video/mp4">
</video>

WordPress keeps this markup as written only for a user who is allowed to post unfiltered HTML: on a single site, an administrator or an editor. For an author or a contributor it removes the <source> line when the post is saved, and the video is left with no file to play.

Two limits apply. preload is a hint the browser is not forced to follow. And autoplay takes precedence over it: a video set to play by itself has to start downloading.

For a muted, looping video that plays by itself, web.dev describes putting loading="lazy" on the <video> tag. Browsers have started to support it, and one that does not simply ignores it. Do not lazy-load a video that sits at the top of the page.

When a plugin's lazy loading gets in the way

Some caching and optimization plugins have their own lazy loading, and it can work differently from the browser's. A script-based loader takes the real address out of src, puts a placeholder there, keeps the address in a data attribute, and swaps it back with JavaScript as the visitor scrolls. WP Rocket's documentation describes its image lazy loading this way, with the address held in data-lazy-src and a <noscript> copy of the image. It also says WordPress's own lazy loading is switched off automatically while WP Rocket's is active. In LiteSpeed Cache, "Lazy Load Images" is off by default.

To see which kind a page uses, read the source. loading="lazy" beside a real src is the browser's own. A placeholder in src, with the address in a data- attribute, is a script.

A script-based loader causes trouble in three ways:

  • It hides the main image. With the address moved out of src, the browser's early look through the HTML cannot find the image, and it starts loading only after the script has run. Exclude the first images.
  • Images never appear. WP Rocket's own list of known problems includes grid layouts with fixed widths, scripts that work out spacing from an image's size, and particular themes whose logo or thumbnails stop showing. For the themes, its fix is to exclude those images or switch lazy loading off on the affected pages.
  • Two loaders work on the same image. A theme or page builder may have lazy loading of its own. Keep one and switch the other off.

To test whether a plugin's loader is the cause, switch off its lazy-load setting, clear the cache and reload the page. Turn it back on to undo. How to find and fix WordPress plugin conflicts has the wider method.

What not to rely on

  • Lazy loading does not make an image smaller. A visitor who scrolls still downloads every file at its full weight. See how to optimize WordPress images.
  • It does nothing for the first screen. What a visitor sees first depends on the server, the stylesheet and the main image. How to speed up WordPress covers those.

If a page is still slow after all this, our speed optimization service is a one-time job that measures the pages you name before the work and again after it.

Common questions

Do I need a lazy loading plugin?

Not for images or iframes. WordPress adds the attribute and the browser does the rest. A plugin earns its place for what the attribute cannot do: a facade for video embeds, or delaying background images.

How do I turn off WordPress's own lazy loading?

Add add_filter( 'wp_lazy_loading_enabled', '__return_false' ); to a child theme's functions.php or a small plugin. WordPress then stops adding loading="lazy" to images and iframes. Exempting the one image that should not be lazy is usually the better fix.

Does lazy loading hide images from Google?

Not when it is done the browser's way. Google's guidance lists the browser's built-in lazy loading among the methods that work with Search. What it warns about is content that loads only after a scroll or a click, because Google does neither. To check, use the URL Inspection tool in Search Console and look for the image's address in a src attribute in the rendered HTML.

What happens if a visitor has JavaScript switched off?

With the browser's own lazy loading, the images simply load the ordinary way. MDN notes that loading is deferred only when JavaScript is enabled. A script-based loader depends on its <noscript> copy of each image, so check that yours writes one.

More on this subject

Speed optimization, done for you

Speed optimization is $149. Before-and-after measurements on named pages. It starts with a free diagnosis.