How To Remove Unused WordPress Scripts And Styles Without Plugins

Have you ever opened Chrome DevTools and thought, “Why is my simple blog loading a slider script?” I’ve been there. The first time I tried to remove unused WordPress scripts, I broke a contact form and had to roll back fast.

The good news is you can slim down WordPress assets without installing another plugin. You just need the right handles, the right hook, and a safe way to test.

Set this up safely (so one change doesn’t take your site down)

Before I touch scripts and styles, I set up three guardrails.

First, I work on a staging site. Asset changes can fail in quiet ways, like a button that stops opening a modal. If your host doesn’t offer staging, at least take a full backup.

Second, I avoid editing a parent theme. Use a child theme, or better, a tiny custom “utility” plugin. If you want this to always load (even if you switch themes), an MU-plugin is perfect.

Third, I test logged-out and logged-in views. Some plugins enqueue different assets for admins.

Here’s the MU-plugin approach I use most because it stays theme-independent. Create a file at wp-content/mu-plugins/asset-unloads.php (make the mu-plugins folder if it doesn’t exist):

<?php
/**
 * Plugin Name: Asset Unloads (No Plugin Plugin)
 * Description: Dequeue unused scripts/styles safely, per page.
 */


if ( ! defined( 'ABSPATH' ) ) {
    exit;
}


add_action( 'wp_enqueue_scripts', function () {
    // I only unload on the front end.
    if ( is_admin() ) {
        return;
    }


    // I'll add dequeues here once I know the right handles.
}, 100 );

That 100 priority matters. It runs after most themes and plugins enqueue their assets.

How I find the correct script/style handle (handles are everything)

Dequeueing only works when you target the real handle. Guessing the handle name is the fastest path to frustration.

I usually find handles in one of three ways:

  1. View Source: Open the page source and search for part of the filename (like swiper or contact). WordPress often prints tags like id="some-handle-js" or id="some-handle-css", and that some-handle is what you need.
  2. DevTools Network tab: Filter by “JS” or “CSS”, reload, click a file, then search the HTML for that URL to find the tag (and the id attribute).
  3. Print the queued handles: This is my favorite because it shows what WordPress thinks is queued right now.

Add this temporary helper to the same MU-plugin file. It prints a comment in the page HTML, visible only to admins:

add_action( 'wp_footer', function () {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }


    global $wp_scripts, $wp_styles;


    if ( $wp_scripts instanceof WP_Scripts ) {
        echo "n<!-- Queued script handles: " . esc_html( implode( ', ', $wp_scripts->queue ) ) . " -->n";
    }


    if ( $wp_styles instanceof WP_Styles ) {
        echo "n<!-- Queued style handles: " . esc_html( implode( ', ', $wp_styles->queue ) ) . " -->n";
    }
}, 999 );

Then I view the page source, search for “Queued script handles”, and copy the names into my unload function.

If you want a common example, Gutenberg’s front-end CSS often shows up as wp-block-library. I’ve removed it on Classic Editor sites, but I’m careful, because block themes and blocks need it. If that’s your situation, this guide on SmartWP helps: remove Gutenberg block library CSS.

My rule: I don’t unload anything until I can explain what feature it powers, and where that feature appears.

Dequeue unused scripts and styles with conditional logic (the safe way)

Once I have handles, I unload them only where they aren’t needed. I almost never do site-wide removals unless I’m 100 percent sure.

Here’s the pattern I copy and paste, then adjust:

add_action( 'wp_enqueue_scripts', function () {
    if ( is_admin() ) {
        return;
    }


    /**
     * Example 1: Only load a contact form script on the contact page.
     * Replace 'my-form-script' with the real handle you found.
     */
    if ( ! is_page( 'contact' ) ) {
        wp_dequeue_script( 'my-form-script' );
        wp_deregister_script( 'my-form-script' );
    }


    /**
     * Example 2: Unload a slider stylesheet everywhere except the homepage.
     */
    if ( ! is_front_page() ) {
        wp_dequeue_style( 'my-slider-styles' );
        wp_deregister_style( 'my-slider-styles' );
    }


    /**
     * Example 3: Gutenberg block CSS unload (Classic sites only).
     * If you use blocks on the front end, don't do this.
     */
    if ( ! is_singular() ) {
        // Keep defaults here if you want.
    }
    // wp_dequeue_style( 'wp-block-library' );
    // wp_dequeue_style( 'wp-block-library-theme' );
    // wp_dequeue_style( 'wc-block-style' );


}, 100 );

A quick note on wp_dequeue_* vs wp_deregister_*:

  • I start with dequeue. It prevents output on that request.
  • If a plugin tries to enqueue it again later, deregister can help, but it’s also easier to break things. I only add deregister when dequeue alone doesn’t stick.

If you’re thinking about performance beyond unloads, I keep it separate. Asset unloading solves “why is this here?” problems, while render-blocking is a different issue. If you’re curious, SmartWP’s guide on eliminate render-blocking JavaScript and CSS explains the concept well.

Troubleshooting when dequeue doesn’t work (what I check first)

When a dequeue fails, it’s usually one of a few causes. The fix is almost always boring, but reliable.

Here’s the quick diagnostic table I keep in my notes:

SymptomMost common causeWhat I do
Handle never disappearsWrong handle namePrint queued handles in the footer comment again
Works on some pages onlyDifferent templates enqueue different assetsTest the exact URL, logged out, in an incognito window
Dequeue “does nothing”Plugin enqueues later than your codeIncrease priority to 999
Site breaks after dequeueAnother script depends on itCheck dependencies in $wp_scripts->registered[$handle]->deps

If I suspect “enqueued later,” I bump priority:

add_action( 'wp_enqueue_scripts', 'smartwp_unload_assets', 999 );
function smartwp_unload_assets() {
    if ( is_admin() ) {
        return;
    }
    wp_dequeue_script( 'some-handle' );
}

Dependencies are the other big one. A plugin might register some-handle, then another script lists it as a dependency. If you rip it out, you can break click handlers, menus, or checkout updates. For a practical discussion of dequeue and deregister behavior, I’ve referenced this thread more than once: dequeue and deregister theme assets.

If you’re trying to remove unused CSS warnings in general, these two references give helpful context on why the audit triggers (even if you still prefer manual unloads): remove unused CSS on WordPress and reduce unused CSS documentation.

Accessibility and UX checks (don’t “speed up” by breaking the site)

This part isn’t optional for me. Unloading CSS and JS can hurt usability in ways that speed tests won’t catch.

I pay extra attention to:

  • Forms: validation messages, required field indicators, and input masks.
  • Checkout and cart flows: price updates, shipping calculators, and coupon fields (even if you only unload on “non-store” pages).
  • Menus and dialogs: keyboard focus, Escape-to-close, and visible focus outlines.
  • Consent banners and embeds: removing a script might remove controls people rely on.

After I unload anything, I do a quick keyboard pass. I tab through links, open the menu, and submit at least one form. That takes two minutes and saves a lot of emails later.

Speed wins don’t count if the page becomes harder to use, especially on mobile or with a keyboard.

Conclusion

When I want to remove unused WordPress scripts, I treat it like pruning a plant, not mowing the whole yard. I find the exact handle, unload it only where it’s truly unused, then test the pages that matter most. If dequeueing doesn’t work, I check priority and dependencies before I try anything drastic. Do that, and you’ll ship faster pages without adding another plugin to maintain.

Leave a Reply

Your email address will not be published. Required fields are marked *