Your WordPress site is probably loading every image before your visitor even scrolls — and 16% of mobile pages make a worse mistake: lazy-loading the hero image, adding 624 milliseconds to their Largest Contentful Paint.
The good news: no plugin is needed. Here’s how to lazy load images in WordPress without a plugin — including background images and iframes core doesn’t handle.
What WordPress Already Does for You
If your site runs WordPress 5.5 or newer, your images are probably already lazy loading: since 5.5 (August 2020), core adds loading="lazy" to content images with width and height attributes, and 5.7 extended it to iframes. That attribute tells the browser to wait until the visitor scrolls near an image.
Browser support is settled: loading="lazy" works in Chrome 77+, Edge 79+, Firefox 75+, and Safari 15.4+ — roughly 95% of users. Older browsers ignore it harmlessly. MDN’s lazy loading guide confirms it’s now a web standard.
WordPress 6.3 also skips lazy loading for the first three content images and adds fetchpriority="high" to the likely LCP image, typically improving LCP by 5 to 10 percent.
The catch: WordPress can only guess which image matters most, so a hero from a slider, page builder, or CSS background may not be protected. Even Jetpack quit the feature: Jetpack announced in November 2023 that it would deprecate lazy loading, because core and browsers had made it redundant.
Check Whether Lazy Loading Is Already Working
Take 30 seconds to see where you stand:
- Open an image-heavy post, right-click an image halfway down, and choose Inspect.
- If its
<img>tag showsloading="lazy", native lazy loading is already active. - Now inspect your hero image: if it says
loading="lazy"too, jump to the hero section below.
No loading attribute? Your theme may strip it, or WordPress is outdated — update first.
Force Lazy Load on Content Images With functions.php
Core covers most images, but older themes and shortcode images can slip through. This loading lazy WordPress functions.php snippet forces lazy loading on every content image:
Important: add this to your child theme’s functions.php — never edit the parent theme directly, or your changes vanish at the next update. No child theme? Create one, or hold the code in a snippets plugin.
// Force lazy loading on all images in post content
add_filter( 'the_content', 'tc_force_lazy_load_images' );
function tc_force_lazy_load_images( $content ) {
$content = preg_replace(
'/<img(?![^>]*loading=)[^>]*>/i',
function( $m ) { return str_replace( '<img', '<img loading="lazy"', $m[0] ); },
$content
);
return $content;
}
This is the simplest lazy load images WordPress code: it adds loading="lazy" to images missing the attribute and leaves loading="eager" images alone. For finer control, see the wp_lazy_loading_enabled and wp_img_tag_add_loading_attr filters.
Never Lazy Load the Hero Image
This is the most important section in this guide. Your hero image is usually your Largest Contentful Paint element. Lazy-load it, and the browser waits before even starting the download — costing roughly 624ms of median LCP.
WordPress 6.3’s protection only counts images in your main content — a hero from your header, slider, or Elementor may not be covered. If yours is lazy-loaded, fix it:
// Never lazy-load the featured image — load it eagerly with high priority
add_filter( 'wp_img_tag_add_loading_attr', 'tc_eager_featured_image', 10, 3 );
function tc_eager_featured_image( $value, $image, $context ) {
if ( 'the_post_thumbnail' === $context ) {
return 'eager';
}
return $value;
}
// Add fetchpriority="high" to load the featured image first
add_filter( 'wp_get_attachment_image_attributes', 'tc_hero_priority', 10, 3 );
function tc_hero_priority( $attr, $attachment, $size ) {
if ( is_singular() && get_post_thumbnail_id() === $attachment->ID ) {
$attr['fetchpriority'] = 'high';
}
return $attr;
}
The rule is simple: above the fold loads eagerly, below the fold loads lazily — including your logo and first images. It’s one lever: see our guide on how to speed up your WordPress site in 2026.
How to Lazy Load Iframes in WordPress
One YouTube embed can pull in hundreds of kilobytes before your visitor presses play. WordPress 5.7 lazy-loads iframes automatically, but page-builder or custom HTML embeds can miss out — for any iframe you paste by hand, just add the attribute:
<iframe src="https://www.youtube.com/embed/VIDEO_ID"
width="560" height="315" loading="lazy"
title="Video title" allowfullscreen></iframe>
For embeds already in your content, this snippet adds it to every iframe missing one:
// Lazy load all iframes (YouTube embeds, maps) in post content
add_filter( 'the_content', 'tc_lazy_load_iframes' );
function tc_lazy_load_iframes( $content ) {
$content = preg_replace(
'/<iframe(?![^>]*loading=)[^>]*>/i',
function( $m ) { return str_replace( '<iframe', '<iframe loading="lazy"', $m[0] ); },
$content
);
return $content;
}
For the biggest win, use a video facade: replace the player with a thumbnail that loads the real player only when clicked, cutting several hundred kilobytes from your initial load — the same principle as reducing image size without losing quality.
How to Lazy Load Background Images in WordPress
Here’s native lazy loading’s limit: the loading attribute only works on <img> and <iframe> tags, so CSS backgrounds never get it.
The fix is a small Intersection Observer script that applies the background only when the section nears the viewport:
document.addEventListener( 'DOMContentLoaded', function() {
const lazyBackgrounds = document.querySelectorAll( '.lazy-bg' );
if ( 'IntersectionObserver' in window ) {
const observer = new IntersectionObserver( function( entries ) {
entries.forEach( function( entry ) {
if ( entry.isIntersecting ) {
const el = entry.target;
el.style.backgroundImage = 'url(' + el.dataset.bg + ')';
el.classList.remove( 'lazy-bg' );
observer.unobserve( el );
}
});
});
lazyBackgrounds.forEach( function( bg ) { observer.observe( bg ); });
}
});
Then mark background sections in HTML, moving the image URL into a data-bg attribute:
<section class="lazy-bg" data-bg="https://techcoff.com/wp-content/uploads/banner.jpg">
<!-- Your section content here -->
</section>
Simpler alternative: if a background sits below the fold, use a regular <img> with object-fit: cover — native lazy loading for free, no JavaScript. Google’s web.dev lazy loading guide covers the same approach.
Verify Lazy Loading Is Actually Working
Here’s the 2-minute check in Chrome DevTools:
- Open your page, press F12, and go to the Network tab.
- Filter by Img so you only see image requests.
- Reload the page without scrolling. You should see only the above-the-fold images.
- Scroll down slowly: new image requests should appear as each section enters the viewport.
- If every image loads at once, a second lazy loader from a theme or plugin is likely fighting core. Keep only one active.
Then run PageSpeed Insights: if “Defer offscreen images” is gone and LCP is green, you’re done. Add browser caching without a plugin too.
Native Lazy Loading vs. a Lazy Load Plugin
| Feature | Native (WordPress core) | Lazy load plugin |
|---|---|---|
| Cost | Free, built in | Free or $30–$100/year |
| Extra JavaScript | None | Usually adds a script |
| Images and iframes | Yes, automatic | Yes, with more options |
| Background images | No | Some plugins handle them |
| Exclude hero image | Needs code (shown above) | Usually a toggle |
The honest answer: for most sites, native lazy loading plus the snippets above beats a plugin. A plugin only earns its place for extras like fade-in effects or slider images. Never run both — double lazy loading is a classic cause of images that never appear.
FAQ
Do I need a plugin to lazy load in WordPress?
No — a WordPress lazy load without plugin setup is the default since version 5.5: content images get loading="lazy", iframes since 5.7. A plugin is only useful for extras like background images or fade-in effects.
How do I lazy load background images in WordPress?
CSS backgrounds can’t use the loading attribute, so they need JavaScript. Use the Intersection Observer snippet above: move the image URL into a data-bg attribute and let the script apply it when the section scrolls into view. Or use a regular <img> with object-fit: cover for free native lazy loading.
Does lazy loading hurt SEO?
No — done correctly, it helps. Google recommends lazy loading, and its crawler renders loading="lazy" images normally, so they still get indexed. The only risk is lazy-loading your hero or LCP image, which slows the page and hurts Core Web Vitals. Keep above-the-fold images eager.
Should I lazy load the hero image?
Never. The hero is usually your Largest Contentful Paint element — lazy-loading it adds about 624ms to median LCP, per Google’s research. Load it with loading="eager" and fetchpriority="high" instead.
The Bottom Line
You don’t need another plugin or subscription to lazy load images in WordPress without a plugin — WordPress already does it. Your job: eager-load the hero, lazy-load everything below the fold, and handle backgrounds and iframes with the code above. Do that, and your pages get lighter and your LCP gets faster.
Prefer a professional to handle your site speed? See our WordPress services — or get in touch and tell us what your site needs.