Lazy Loading Images in WordPress
WordPress lazy-loads images natively since 5.5 and leaves the first few eager since 6.3. Most sites need no plugin — and the most common problem is lazy loading the one image that must load first.
Some links on this page are affiliate links: if you buy through one, we may earn a commission at no extra cost to you. It does not change what we recommend; see the editorial policy.
WordPress already lazy-loads images — you probably do not need a plugin. Since 5.5 core adds loading="lazy" to images and iframes in content, and since 6.3 it leaves the first few eager and marks the likely Largest Contentful Paint image with fetchpriority="high". Most lazy-loading work on a WordPress site today is undoing lazy loading where it hurts.
What core does
| Version | Behaviour |
|---|---|
| 5.5 | loading="lazy" on content images, featured images and avatars |
| 5.7 | Same for iframes in content |
| 5.9 | First content image left eager |
| 6.1 | decoding="async" on images |
| 6.3 | First few images eager (threshold 3), fetchpriority="high" on the probable LCP image |
The threshold is adjustable:
<?php
add_filter( 'wp_omit_loading_attr_threshold', fn () => 1 );
Lower it when the theme shows one large hero and then a long list; raise it when several images sit above the fold on a typical screen.
Where native lazy loading does not reach
- CSS background images — hero sections and builder backgrounds set via
background-imageload immediately, lazy or not. - Images printed by theme templates or builders without going through core's functions.
- Videos and third-party embeds that inject their own markup with scripts.
These are where a lazy-load plugin, or the lazy-load feature of a performance plugin, still adds value. A plugin that replaces every src with a JavaScript loader for ordinary images adds work for no gain.
The one image you must not lazy-load
The LCP element on most WordPress pages is an image: the hero, the featured image or the first product photo. If it carries loading="lazy", the browser waits until layout is complete before fetching it, and PageSpeed reports "Largest Contentful Paint image was lazily loaded". The full LCP breakdown is in WordPress Core Web Vitals.
In a template, output it eagerly and prioritised:
<?php
echo wp_get_attachment_image(
get_post_thumbnail_id(),
'large',
false,
array(
'loading' => 'eager',
'fetchpriority' => 'high',
)
);
For an image in post content, stop core adding the attribute:
<?php
add_filter( 'wp_img_tag_add_loading_attr', function ( $value, $image, $context ) {
return str_contains( $image, 'hero-image' ) ? false : $value;
}, 10, 3 );
Here hero-image is a class you add to the image; returning false leaves loading off that image. For fetchpriority, output the image with wp_get_attachment_image() as above, or keep it within the first three content images, where core adds it automatically. If a performance plugin does the lazy loading, add the image to its exclusion list instead.
Disabling lazy loading entirely
<?php
add_filter( 'wp_lazy_loading_enabled', '__return_false' );
The filter receives the tag name and context, so you can disable it only for iframes or only in one context. Turning it off site-wide is rarely right; it is mostly useful when a plugin handles lazy loading and the two systems conflict.
Iframes, videos and embeds
Embedded YouTube videos and maps are often heavier than every image on the page combined. Core lazy-loads iframes in content, but the player still loads its scripts once the iframe comes into view. Two refinements:
- Facade — show a static thumbnail with a play button and load the real player only on click. Many performance plugins offer this for YouTube; it removes hundreds of kilobytes of JavaScript from the first load.
- Maps — a map below the fold is a good candidate for a static image linking to the full map; the approach is in adding Google Maps to WordPress.
Never lazy-load an iframe that is the first thing on the page, such as a hero video; it becomes the LCP problem described above.
Two lazy loaders conflict
Running core lazy loading and a JavaScript lazy loader on the same images is the classic cause of images that never appear or appear only after scrolling back — one of the causes in WordPress images not showing. Pick one system:
- Core only — the default, adequate for most sites.
- A plugin only — disable core with the filter above, and make sure the plugin excludes the LCP image.
An all-in-one plugin such as WP Rocket includes lazy loading for images, iframes and background images with an exclusion list for above-the-fold images, which is useful when backgrounds and embeds are the heavy part of the page. What it covers in full is in the WP Rocket overview.
Lazy loading is not optimisation
Lazy loading defers bytes; it does not remove them. A long page of 2 MB photos still downloads 2 MB per image as the visitor scrolls, on a phone, over mobile data. Resize and compress first — how to optimise images in WordPress — then lazy loading is a refinement rather than a bandage.
Common mistakes
- Lazy-loading the hero or featured image, the single worst setting for LCP.
- Stacking a plugin loader on top of core.
- Missing width and height on lazy images, so content jumps as they arrive — see cumulative layout shift.
- Lazy-loading everything in a slider, so slides two and onwards appear blank for a moment.
Verify
- View the page source and find the hero image: it should have no
loading="lazy"and should havefetchpriority="high". - Images further down should carry
loading="lazy"exactly once, with nodata-srcplaceholders from a second loader. - In DevTools → Network, reload and confirm below-the-fold images load only as you scroll.
- Re-run PageSpeed and confirm the lazily-loaded LCP warning is gone.
Frequently asked
- Usually not. Core adds loading="lazy" to content images, iframes and featured images, and leaves the first ones eager. A plugin helps mainly with CSS background images and embeds that core cannot reach.
- Site-wide, add_filter( 'wp_lazy_loading_enabled', '__return_false' ). For one image, return false from the wp_img_tag_add_loading_attr filter, or add loading="eager" and fetchpriority="high" in the template that outputs it.
- The image measured as LCP had loading="lazy", so the browser delayed fetching it until layout was done. Remove the attribute from that image, usually the hero or featured image, and give it fetchpriority="high".