WordPress adds loading=“lazy” to eligible images and iframes on its own, so your first job is verifying that your hero image, the likely Largest Contentful Paint (LCP) element, is not being deferred. Keep one lazy-loading system running, not two. Prefer the native browser feature unless you need background images or video facades.
TL;DR:
- WordPress core adds native lazy loading to eligible images and iframes only when dimensions are present, but custom themes can still defer the wrong image.
- Set the hero image to eager loading and set fetchpriority to high; never pair it with lazy loading, because those instructions conflict.
- Add a plugin only for CSS background images, video facades, or detailed exclusions, and disable other lazy loading tools to prevent conflicts.
- After changes, purge plugin, server, and CDN caches, then test logged out on mobile; use Lighthouse and source HTML to verify the LCP image.
- Use your browser’s Network tab to confirm that images farther down the page stay unloaded initially and begin downloading as visitors scroll toward them.
Table of Contents
- How native lazy loading works in WordPress
- How to enable, disable, or override lazy loading
- Plugins versus native lazy loading: which should you use?
- Lazy loading videos, iframes, and background images
- Common pitfalls and troubleshooting
- How to test and verify lazy loading
- Developer controls and safe code patterns
- My view: keep it native, protect the hero, and bring in help for the awkward cases
- Need a hand with lazy loading and Core Web Vitals?
- FAQ
- Sources
How native lazy loading works in WordPress
Since version 5.5, WordPress has added the native loading attribute to images that core outputs, but only when width and height are present. The same release gave developers filters and template-level controls to skip lazy loading for the first content image or for any specific element. That history matters because most of what you see in your page source today is the result of those server-side rules, not of a plugin.
Here is what core decides for you:
- Eligibility: images and iframes rendered through core functions get the attribute, as long as dimensions are known.
- Global switch: the wp_lazy_loading_enabled function and its filter let you turn the behavior on or off by context, such as images in post content versus iframes.
- Position awareness: WordPress keeps count of media in your content, and the first one or few images can be left without the lazy attribute because they are likely above the fold.
- Priority hints: for likely LCP elements, core can add fetchpriority=“high” and decoding=“async” through wp_get_loading_optimization_attributes.
The browser, not WordPress, decides the exact moment a deferred image starts downloading. Chrome, Firefox, Safari and Edge each use their own distance thresholds from the viewport, which is why two browsers can fetch the same image at slightly different scroll positions. That is normal and nothing you need to tune.
Native loading is also the lightweight choice. web.dev recommends preferring browser-level lazy loading where possible because JavaScript libraries add dependency weight and can introduce conflicts that a standardized browser feature avoids.
There is one caveat that shapes the rest of this guide. WordPress core developers themselves note that lazy loading is not one-size-fits-all, because its performance impact depends on theme layout. Theme authors are expected to control which images are deferred, so a custom theme can still lazy-load the wrong image even on a current WordPress install.
Core principle: WordPress core developers state that the impact of lazy loading depends on theme layout, so theme authors must control which images are deferred to avoid delaying important content.
How to enable, disable, or override lazy loading
Start by finding out what your site is actually doing. Open a key page, view the source, and search for loading=. Note which images carry it and, just as important, which do not. Then work out where the attribute comes from: core, your theme templates, a page builder, or a plugin.
Once you know the source, pick the lightest fix that works:
- Hero image: set loading=“eager” on the tag, or omit the attribute entirely, so the browser fetches it immediately.
- Images you output in PHP: pass a loading value of false through the attributes of wp_get_attachment_image() so core does not add the attribute, a pattern the WordPress core announcement describes for template-level control.
- Site-wide off switch: return false from the wp_lazy_loading_enabled filter when you want core to stop adding the attribute everywhere, or only for a given tag name or context.
- Per-tag control: use wp_img_tag_add_loading_attr for images and wp_iframe_tag_add_loading_attr for iframes. The iframe filter defaults to lazy when appropriate and lets you override that decision for individual embeds.
- Manual markup: in the HTML editor or a custom block, write loading=“eager” to force an immediate load or loading=“lazy” to defer one.
After any change, purge every cache layer, including your caching plugin, server cache and CDN, and reload the page logged out. A logged-in admin view can differ from what visitors and crawlers receive. If you are also reviewing caching, our rundown of the best WordPress caching plugins covers settings worth rechecking after you touch lazy loading.
Pro Tip: Make the first image on every template eager by design, not by luck. Checking the homepage, a post and a landing page separately can catch the template that quietly lazy-loads its banner.
Plugins versus native lazy loading: which should you use?
For plain images and iframes, native loading is usually enough, and it keeps your page free of extra scripts. A plugin earns its place when you need things the browser attribute cannot do.
Where each approach fits:
- Native strengths: no added JavaScript, broad browser support, and no configuration to maintain. This is the safe default.
- Plugin strengths: background images set in CSS, YouTube thumbnail facades, and fine-grained exclusions. Smart LazyLoad covers images, videos, iframes and background images, and offers native, hybrid and JavaScript modes along with exclusions by class or ID.
- Other options: LazyLoad, the rocket-lazy-load plugin on WordPress.org, is a commonly referenced free plugin that lazy loads images, videos and iframes.
Plugin modes are easier to choose once you know what they mean:
- Native-only: the plugin leans on the browser attribute and adds almost nothing else. Pick this when your content is mostly images and iframes.
- Hybrid: the browser handles lazy loading where supported, and JavaScript fills the gaps. It suits sites with a mix of older visitors and modern ones.
- Full JavaScript: a script controls everything, which is what you need for background images and custom markup, at the price of extra weight and more ways to break.
Before you switch anything on, run through this checklist:
- Protect your LCP image. Smart LazyLoad can protect the first N important images from being deferred, so set that number to cover your banner or hero.
- Add exclusions by class or ID for logos, sliders and anything above the fold.
- Run one lazy-loading system only. Smart LazyLoad’s own guidance advises against stacking several, and community threads on WordPress.org describe double-processing and broken loading when two plugins both manage lazy loading at once.
- Clear every cache after saving settings.
- Check your theme and page builder. Many already include a lazy-loading switch, and an optimization suite you installed for minification may have its own.
Pro Tip: If you inherit a site and cannot tell which tool is lazy-loading what, deactivate optimization plugins one at a time and recheck the page source after each. The attribute that survives belongs to core or the theme.
Lazy loading videos, iframes, and background images
Embeds and CSS backgrounds are where native lazy loading runs out of road.
A YouTube embed pulls in a full player before anyone presses play. The better pattern is a facade: a lightweight thumbnail with a play button that loads the real player only after a click. Smart LazyLoad supports YouTube thumbnail facades for exactly this reason. On a page with several embeds, the savings add up quickly, because every deferred player is a script bundle the visitor never downloads unless they want the video.
How to think about each case:
- Iframes in post content: core adds the loading attribute to iframes when dimensions are present, controllable through wp_iframe_tag_add_loading_attr. That helps, but a facade is still the stronger choice for player-heavy embeds such as video, maps and social feeds.
- Videos you host yourself: the browser can defer fetching with the preload attribute, and a poster image gives visitors something to see while the file waits.
- Background images: the loading attribute does not apply to CSS backgrounds, so they load as soon as the stylesheet applies. To defer them, swap the image in with an Intersection Observer script or use a plugin that supports background images.
The Intersection Observer API is the browser feature that tells your code when an element is approaching the viewport. It is the building block behind most JavaScript lazy loaders, and a few lines of it can replace a whole plugin for one narrow job.
Pro Tip: Never put a facade on a video that sits at the very top of the page and is the main reason people visit. Keep that one as a normal embed or a poster image, and facade the rest further down.

Common pitfalls and troubleshooting
Most “broken” lazy loading comes down to a handful of causes. Work through them in this order:
- The hero image is lazy-loaded. This is the most damaging mistake. Browser-level guidance says never lazy-load the hero or LCP image, because doing so delays Largest Contentful Paint. WordPress core tries to skip the likely LCP image, but custom themes and page builders can still output it with loading=“lazy”. Set it to eager and add fetchpriority=“high”.
- Images are missing width and height. Without dimensions, core will not add the attribute, and the browser cannot reserve space, so the layout jumps as images arrive. That jump is Cumulative Layout Shift (CLS). Make sure your theme and builder output dimensions, or regenerate the markup so they are filled in.
- Two systems are processing the same tags. An SEO suite, a caching plugin, a theme option and a dedicated lazy-load plugin may all claim the job. Turn off all but one. Support threads such as this one about disabling lazy load show how server and plugin interactions can make a setting appear ignored, and they recommend testing with other plugins disabled.
- Caches serve stale markup. You fixed the template, but the old HTML is still being delivered. Purge the page cache, object cache and CDN, then retest logged out.
- A slider or carousel loads late. Sliders often set images through JavaScript, which collides with deferred loading. Exclude the slider’s class or container.
- The fix works for you but not for others. Check the page as a visitor on mobile width, since many themes serve different images and markup there.
To isolate a conflict, deactivate every optimization plugin, confirm that core’s behavior looks right, and then turn them back on one at a time, rechecking the source after each. The plugin that changes things is the one to configure or remove.
If this reads like a lot of detective work, our posts tagged fast WordPress cover related performance fixes you can apply along the way.
Pro Tip: Keep a short written note of which tool owns lazy loading on each site you manage. Most conflicts start months later, when someone installs a second optimizer and nobody remembers the first.
How to test and verify lazy loading
You are checking two things: that below-the-fold media is deferred, and that nothing above the fold is. Use this routine after every change:
- Run PageSpeed Insights or Lighthouse on the exact URL. Look at the LCP element it reports and confirm it is not marked as lazy-loaded. Lighthouse can flag a lazy-loaded LCP image, and the diagnostics also list offscreen images that could still be deferred.
- Check the Network tab in your browser’s developer tools. Reload with the page scrolled to the top, and confirm that images further down the page do not load until you scroll toward them.
- Read the source HTML. Search for loading=, and confirm that your hero image is either eager or has no attribute, and that it has width and height.
- Use URL Inspection in Google Search Console. Run a live test and review the rendered page to confirm Googlebot sees your lazy-loaded images and iframes. If a key image is missing from the render, a script-based loader is probably hiding it from crawlers.
- Test logged out, on mobile width, and after purging caches. Different markup and stale caches are the most common causes of confusing results.
- Compare before and after. Note your LCP and CLS values on a few key pages before changing anything, so you can tell a real improvement from noise. Our posts tagged page speed explain how to read those metrics.
Guidance from web.dev: Never lazy-load the hero or LCP image. It delays the Largest Contentful Paint metric and harms the experience, so use eager loading or fetchpriority for critical images.
Run the tests more than once. Lab results vary from run to run, so look for a consistent pattern rather than a single score.
Developer controls and safe code patterns
If you maintain a theme or custom plugin, these are the controls worth knowing:
- wp_lazy_loading_enabled: toggles lazy loading globally or by tag name and context. This is the broadest switch, documented in the function reference.
- wp_img_tag_add_loading_attr and wp_iframe_tag_add_loading_attr: per-element control. Return false to omit the attribute, or return a specific value such as eager.
- fetchpriority and decoding: respect the attributes core adds. WordPress can add loading=“lazy”, fetchpriority=“high” and decoding=“async” through wp_get_loading_optimization_attributes, and it warns that one element should not carry both loading=“lazy” and fetchpriority=“high”. The two instructions contradict each other.
- Intersection Observer with a margin: for background images and custom markup that core cannot reach, observe the element and set a rootMargin that starts loading before it enters the viewport. The image then starts loading slightly before it enters the viewport, which avoids blank flashes while scrolling.
- Theme templates: if you print images through core functions such as wp_get_attachment_image(), pass explicit attributes for the hero and let core handle the rest.
- Background history: the original lazy-loading proposal in core explains the reasoning behind the defaults and the filters available to theme authors.
Keep custom code small. Every line you add is another thing to retest when WordPress changes its defaults.
My view: keep it native, protect the hero, and bring in help for the awkward cases
Most sites do not need a lazy-loading plugin. Native behavior, eager loading for the hero image and correct image dimensions solve the large majority of problems I see, and a plugin only earns its keep for facades, background images or special exclusions. The real danger is not too little lazy loading but too many systems fighting over the same tags, so pick one owner and write it down. When a theme template or a plugin conflict resists the usual checks, a targeted audit or a developer fix is usually quicker than another round of guesswork.
Need a hand with lazy loading and Core Web Vitals?
If you would rather have this checked than debugged, we can do it for you. Our team audits your theme and plugins, fixes templates that lazy-load the wrong images, configures one clean lazy-loading setup, and confirms the results in PageSpeed Insights. You can start with our WordPress help and support service or a website audit, or claim a free website audit and tell us what you are seeing.

FAQ
Is lazy loading good or bad for my WordPress site?
It is good when it defers images and iframes below the fold, and bad when it delays the hero image. web.dev prefers native browser-level lazy loading because it adds no JavaScript weight, so keep it on and exclude only your LCP image.
Which is better, lazy loading or eager loading?
Use both, each where it fits. Eager loading belongs on anything visible at the top of the page, and lazy loading belongs on everything further down. A single hero image set to eager with fetchpriority high, with the rest deferred, is the typical balance.
Why is WordPress taking so long to load?
Slow loading usually comes from several causes at once: oversized images, heavy plugins, no caching and slow hosting. Lazy loading helps with offscreen media, but it will not fix the rest. Test with PageSpeed Insights to see which factor costs you most.
Do I need a plugin to lazy load images in WordPress?
No. WordPress adds the loading attribute to eligible images and iframes on its own. A plugin is worth adding only for background images, video facades or detailed exclusions, and you should run just one lazy-loading system at a time.
Sources
- wp_lazy_loading_enabled( string context ): bool
- Make
- Web
- Refining WordPress core’s lazy-loading implementation
- Web