
⚡ Contact Form 7 - deferred loading of scripts and styles to speed up WordPress
How CF7 slows down your site and why you can fix it in 5 minutes
Contact Form 7 is installed on 5+ million WordPress sites. The plugin is reliable, flexible and free, and contact forms built with it work on practically every site. But this convenience has a downside: by default, CF7 loads its CSS and JavaScript on every page of your site, even when there's no form anywhere near it.
For the homepage, blog, landing pages and dozens of other pages, this is dead weight: extra requests, increased DOM Content Loaded, bloated page size. In numbers, roughly 10-30 KB of compressed traffic and 1-2 blocking requests for no reason. PageSpeed Insights doesn't forgive such things.
This can be fixed in three ways, from a primitive two-line defer to careful conditional loading "by the book" from the plugin developer. We'll cover each one, with code and no fluff.
💡 Quick overview:
- Disable global CF7 loading via the
WPCF7_LOAD_JSandWPCF7_LOAD_CSSconstants inwp-config.php, the cleanest official method. - Re-enable scripts and styles, but only on pages with a form, via
wpcf7_enqueue_scripts()in the page template. - For custom bundles, the
lazy-cf7-assetspackage, which automatically finds the form on the page and loads JS dynamically.
Method 1: defer-loading the CF7 script via functions.php
The quickest and simplest option is to add the defer attribute to the Contact Form 7 script. It tells the browser: "load the file in the background, but execute it when the DOM is ready". The form continues to work, but the script no longer blocks page rendering.
Add this code to functions.php in your active theme (or via the Code Snippets plugin, safer during updates):
1 if ( ! function_exists( 'add_defer_to_cf7' ) ) { 2 function add_defer_to_cf7( $url ) { 3 if ( 4 false === strpos( $url, 'contact-form-7' ) || 5 false === strpos( $url, '.js' ) 6 ) { 7 return $url; 8 } 9 return "$url' defer='defer"; 10 } 11 add_filter( 'clean_url', 'add_defer_to_cf7', 11, 1 ); 12 }
The function checks the URL of every enqueued script via the clean_url hook. If the address contains contact-form-7 and the .js extension, it adds defer='defer'. All other scripts are left untouched.
Plus: a solution in 10 lines that doesn't require editing templates or plugin configuration. Suitable for themes where there's no separate contact page template.
Minus: the script still loads on every page, you're just removing the render-blocking behavior. Traffic and server requests aren't reduced. This method doesn't affect the plugin's CSS at all, the stylesheet loads as usual.
Method 2: official method, conditional loading via constants
This approach is described in the Contact Form 7 documentation by the plugin developer himself, Takayuki Miyoshi. The idea has two steps: first globally disable CF7 scripts and styles, then enable them again, but only on pages where the form is actually used.
Step 1: disable loading on all pages
Add two constants to wp-config.php:
1 define( 'WPCF7_LOAD_JS', false ); 2 define( 'WPCF7_LOAD_CSS', false );
Alternatively, via your theme's functions.php:
1 add_filter( 'wpcf7_load_js', '__return_false' ); 2 add_filter( 'wpcf7_load_css', '__return_false' );
After this, CF7 won't load a single line of its code on any page of your site, including those where a form exists. Without scripts, the form loses AJAX submission and validation, so step 2 is needed.
Step 2: restore scripts on pages with a form
Let's say your contact page uses the page-contact.php template in your theme folder. Add this to that template before calling wp_head():
1 if ( function_exists( 'wpcf7_enqueue_scripts' ) ) { 2 wpcf7_enqueue_scripts(); 3 } 4 5 if ( function_exists( 'wpcf7_enqueue_styles' ) ) { 6 wpcf7_enqueue_styles(); 7 }
The wpcf7_enqueue_scripts() and wpcf7_enqueue_styles() functions manually enqueue CF7 scripts and styles only on this template. All other pages on your site remain clean.
Plus: the method is "from the manufacturer", guaranteed not to break during plugin updates. Works with CF7 versions 5.x and 6.x, current version 6.1.6 as of June 2026 (release list). Zero unnecessary loading on pages without a form.
Minus: requires editing theme templates. If you have multiple pages with forms, you need to remember to add the calls to each template. If the form is inserted via shortcode in the content (rather than in a template), the method won't work without additional conditions.
Method 3: the lazy-cf7-assets package for JavaScript bundles
If you're building your frontend with a bundler (Webpack, Vite, esbuild) and using a modern theme with a custom JavaScript bundle, there's an npm package called lazy-cf7-assets. It solves the same problem, but on the client side: it scans the DOM, finds the CF7 form and only then dynamically loads the plugin scripts.
Installation:
1 npm install lazy-cf7-assets
Before using it, you need to disable automatic JS loading by the plugin (as in method 2, via wpcf7_load_js):
1 add_filter( 'wpcf7_load_js', '__return_false' );
Then in your JS bundle:
1 import lazyform from 'lazy-cf7-assets'; 2 3 // Initialize after DOM ready 4 lazyform.init();
If scripts load in <head> rather than at the end of <body>, specify an absolute path to the loading GIF image so the form doesn't "flicker" in an empty state:
1 lazyform.init({ 2 gifPath: '/wp-content/themes/my-theme/images/ajax-loader.gif' 3 });

Plus: zero-touch on the PHP side, no need to edit templates for each page with a form. The package automatically detects if a CF7 shortcode exists on the page and loads scripts only then. Suitable for sites where the form is output via shortcode in content (rather than hardcoded in a template).
Minus: only works with JavaScript (the plugin's CSS still needs to be disabled separately). Requires a bundler in the project. The package is minimal (1 star on GitHub), maintained by a single developer, for production you should fork it and check CF7 updates for compatibility.
What to choose: comparing three approaches
Criteria | defer via hook | Constants + template | lazy-cf7-assets |
|---|---|---|---|
Traffic savings | ❌ no | ✅ full | ✅ full (JS) |
Protection from CF7 updates | ✅ yes | ✅ yes | requires checking |
No template editing required | ✅ yes | ❌ no | ✅ yes |
Disables CSS | ❌ no | ✅ yes | ❌ no |
Implementation complexity | low | medium | medium |
Form shortcode in content | ✅ works | ❌ difficulties | ✅ works |
If your form lives on a single page in a separate template, use method 2 (official method). If the site uses a modern bundler and you might have multiple forms in different places, method 3 (lazy-cf7-assets). If you need a solution "right now" without editing templates, method 1 (defer), but remember the limitations.
One important caveat: after any of these changes, be sure to verify that the form submits, validation works, reCAPTCHA isn't broken, and styles haven't shifted. Open the page with the form in incognito mode, fill out and submit a test message before and after.
⁉️🤔 Frequently asked questions
Why does CF7 load scripts on all pages anyway?
The plugin doesn't know at the WordPress loading stage whether a specific page contains a form shortcode. WordPress assembles the page later, when the script queue has already been formed. Developer Takayuki Miyoshi explains this in the official documentation: it's technically impossible to detect the presence of a shortcode before the
wp_headhook. So a conservative approach was chosen, to always load. This is a deliberate architectural decision, not a bug: the plugin sacrifices performance for guaranteed functionality. The burden of optimization is transferred to the site developer.
Will the form break after disabling global loading?
No, if you carefully re-enable scripts on the needed pages. The form will lose AJAX submission and client-side validation only on pages where scripts are not included. That's why step 2 (restoring scripts) is mandatory, don't stop at just
WPCF7_LOAD_JS = false. Check the order of calls:wpcf7_enqueue_scripts()should come beforewp_head(), not after. And make sure reCAPTCHA doesn't conflict with defer loading.
Does the constants method work with CF7 6.x?
Yes, the
WPCF7_LOAD_JSandWPCF7_LOAD_CSSconstants are fully supported in the current version 6.1.6 (see official version log). Throughout the entire history of the plugin, from version 3.9 to the current 6.x, these constants have never been declared deprecated. This is the most stable and documented method for managing loading.
What if I have multiple forms in different places?
If forms are scattered across different pages via shortcodes in content (rather than in templates), the official method with templates is inconvenient. Use either
lazy-cf7-assets(method 3), or the Conditionally Load CF7 plugin: it adds a setting in the admin panel for which pages/post types should load scripts, and works without code editing.
Three lines of code against dozens of requests
The problem of "CF7 loads scripts everywhere" has existed for exactly as long as the plugin itself, and over 10+ years the developer hasn't changed the default behavior, because it's a compromise between simplicity and performance. But a compromise, not a verdict.
The safest path is the official method with constants and templates. It removes the plugin's scripts and styles from all pages except those where they're needed, and doesn't break during updates. If your site uses a modern stack with a bundler, take a look at lazy-cf7-assets. If you need something quick and without editing templates, defer via the clean_url hook will give you metric improvements today.
Check your site via PageSpeed Insights before and after, reducing the number of blocking requests by 1-2 units and saving 10-30 KB per page can boost the Performance score by 2-5 points, especially on mobile devices.



