WordPress PHP: Adding defer or async attributes to enqueued JavaScript files for performance optimization

I’m working on optimizing my WordPress site’s loading speed and need help with JavaScript loading strategies.

Currently I have several plugin scripts that are loaded through wp_enqueue_script() in my theme files. Page speed analysis tools keep suggesting that I should load these scripts asynchronously or defer them to improve performance.

Here’s an example of how one of my plugins loads its JavaScript:

function load_social_scripts() {
    wp_enqueue_script( 'jquery' );
    wp_enqueue_script( 'social-tabs', plugin_dir_url(__FILE__) . 'assets/social-tabs.min.js' );
}

I understand that for better performance, scripts should ideally be loaded like this in HTML:

<script async defer src="path/to/script.js"></script>

However, I’m not sure how to add these attributes when using WordPress’s wp_enqueue_script() function in PHP files. I want to apply this optimization to multiple JavaScript files across my site.

What’s the proper way to add async or defer attributes to scripts enqueued through WordPress functions? Which approach would give better cross-browser performance?

Hit the same bottleneck a few months back and tested tons of approaches. The filter method works, but there’s a cleaner way using WordPress’s newer approach directly in your enqueue function. Register the script first with wp_register_script(), then use wp_script_add_data() to set async or defer. This worked way better for me:

function load_social_scripts() {
    wp_register_script('social-tabs', plugin_dir_url(__FILE__) . 'assets/social-tabs.min.js', array('jquery'), '1.0', true);
    wp_script_add_data('social-tabs', 'async', true);
    wp_enqueue_script('social-tabs');
}

Keeps everything together instead of scattered across filter functions. Just heads up - async can mess with jQuery dependencies, so test it thoroughly.

The script_loader_tag filter is definitely the way to go here. I’ve used this approach for years and it works reliably across different WordPress versions. Just be careful about which scripts you defer or async. For your social tabs script, defer’s probably better since it keeps execution order while letting HTML parsing continue. Async can break dependencies if your script needs jQuery or other libraries loaded first. I usually create a more scalable solution by defining an array of script handles that should be deferred:

function modify_script_attributes($tag, $handle, $src) {
    $defer_scripts = array('social-tabs', 'my-custom-script', 'another-plugin');
    
    if (in_array($handle, $defer_scripts)) {
        return str_replace('<script', '<script defer', $tag);
    }
    return $tag;
}
add_filter('script_loader_tag', 'modify_script_attributes', 10, 3);

This makes it easier to manage multiple scripts without writing separate conditions for each one.

use the script_loader_tag filter to modify your enqueued scripts. hook into it and add the async or defer attributes you want. check this out:

add_filter('script_loader_tag', 'add_async_defer', 10, 3);
function add_async_defer($tag, $handle, $src) {
    if ('social-tabs' === $handle) {
        return str_replace('<script', '<script defer', $tag);
    }
    return $tag;
}