Adding JavaScript to an HTML page is straightforward: you add a script tag into the <head> section of the HTML code. But in WordPress, adding JavaScript is more complex, because you do not directly change the HTML templates. Instead you rely on a dedicated WordPress API for handling JavaScript code.

In this article, we will have an in-depth look at this fundamental API, and learn how to add JavaScript to WordPress.

Together we will:

Understanding the inner workings of the Scripts API

In WordPress, JavaScript is never directly added to a page. Instead you tell WordPress which scripts you want to use, and how. The Scripts API will transform this information into well formed HTML script tags and output them on in the header or footer HTML of the page.

There are two steps for adding a script to WordPress:

  1. Registering the script: Adding it to the central registry of all JavaScript files used on a site.
  2. Enqueuing the script: Adding it to the list of scripts to output on the page.

This is done through the wp_register_script and wp_enqueue_script functions.

To control where the scripts are output, specific hooks are used. There are hooks for:

  • The frontend: the theme visible to the user.
  • The admin area: used by logged-in users to edit content
  • Gutenberg screens and blocks: allows scripts to be used along with the Gutenberg block editor, both on the frontend and in the admin area.

We will look at this individual pieces in detail, starting with how WordPress identifies scripts.

Using names to designate scripts with wp_register_script

To register a script with WordPress, two pieces of data need to be provided:

  1. A handle, which is a text string that identifies a particular JavaScript file. This handle must be unique among all registered scripts.
  2. A URL to the JavaScript file.

If we wanted to register a script used in a theme to be output on the frontend, this is the code that you would use:

function wpdc_register_sample_script() { wp_register_script( 'wpdc-sample-script', get_template_directory_uri() . '/assets/js/sample-script.js' ); } add_action( 'wp_enqueue_script', 'wpdc_register_sample_script' );

To explain the code:

  • Script registration is done in a callback function.
  • The script registration function is added to an action.
  • The first argument of wp_register_script() is the handle.
  • The script handle is prefixed with a unique project slug to prevent name conflicts.
  • The second argument of wp_register_script() is the URL.
  • An absolute URL is used to load the script.
  • The script is loaded from the parent theme using get_template_directory_uri().

Now the we have registered our script, we can enqueue it.

Enqueuing scripts with wp_enqueue_script

If we now wanted to enqueue this script on the frontend, we would use this code:

function wpdc_enqueue_registered_sample_script() { wp_enqueue_script( 'wpdc-sample-script' ); } add_action( 'wp_enqueue_script', 'wpdc_enqueue_registered_sample_script' );

As WordPress knows about our script, we just need to use its handle, which is the first argument used by wp_enqueue_script().

It is not necessary to register scripts before they can be enqueued. The wp_register_script() and wp_enqueue_script() functions use the same arguments.

This is because enqueuing a script will also register the script if the handle does not already exist.

So a shorter way to write our code would be the following:

function wpdc_enqueue_sample_script() { wp_enqueue_script( 'wpdc-sample-script', get_template_directory_uri() . '/assets/js/sample-script.js' ); } add_action( 'wp_enqueue_script', 'wpdc_enqueue_registered_sample_script' );

When should you register a script before enqueuing?

The default approach is to enqueue scripts directly, skipping the registration step. But there are two scenarios in which registering scripts first makes sense:

  1. You want to provide a set of JavaScript libraries to developers. This is what WordPress Core does with its set of default scripts.
  2. You want to use the same script in different contexts. An example might be a script that is used both on the frontend, and in the admin.

Handling dependencies

Often you will write JavaScript code that relies on one or more libraries. A common example is code using jQuery. In this case, the code needs jQuery to work, it is therefore a dependency of the code.

Scripts that depend on jQuery have to be loaded after the library. WordPress assists developers in cases such as this by allowing them to indicate script dependencies.

Dependencies are the third argument passed to wp_register_script or wp_enqueue_script. It must an array with the script handles of the dependencies.

So to add the jQuery library bundled with WordPress, we would use the following code:

function wpdc_enqueue_sample_script_with_dependency() { wp_enqueue_script( 'wpdc-sample-script', get_template_directory_uri() . '/assets/js/sample-script.js', [ 'jquery' ] ); } add_action( 'wp_enqueue_scripts', 'wpdc_enqueue_sample_script_with_dependency' );

Of course you can use more than one dependency. The order of the handles in the dependencies array does not matter.

function wpdc_enqueue_sample_script_with_dependency() { wp_enqueue_script( 'wpdc-sample-script', get_template_directory_uri() . '/assets/js/sample-script.js', [ 'jquery', 'lodash', ] ); } add_action( 'wp_enqueue_scripts', 'wpdc_enqueue_sample_script_with_dependency' );

You are not limited to using the JavaScript libraries bundled with WordPress as dependencies. Any script can be a dependency of another. However any dependency that you use needs to be registered or enqueued before it can be used.

function wpdc_enqueue_sample_scripts_with_dependencies() { wp_enqueue_script( 'wpdc-first sample-script', get_template_directory_uri() . '/assets/js/sample-script.js', ); wp_enqueue_script( 'wpdc-second-sample-script', get_template_directory_uri() . '/assets/js/sample-script.js', [ 'jquery', 'wpdc-first sample-script', ] ); } add_action( 'wp_enqueue_scripts', 'wpdc_enqueue_sample_scripts_with_dependencies' );

Browser cache busting

Loading script and stylesheet files takes time, during which the rendering of a page is blocked. Browsers therefore try to avoid loading files which they have already loaded before. They do so by storing them in their cache.

And this makes sense: if a file hasn’t changed, and it is available in the browser cache, there is no need to get it again from the server. But if the file changes, the browser needs to go and fetch the file again. Forcing the browser to use the live version instead of the cached version is called browser cache busting.

Browsers use the file name to find out which assets have new version available. One approach to change this file name is to append a query string, like ?this=that.

By default WordPress uses its version number as the query string. If we enqueue a script as seen in the previous section, WordPress would output the following script tag on version 5.3.0:

<script src='https://example.com/wp-content/sample-theme/assets/js/sample-script.js?ver=5.3.0'></script>

In your projects, you want to do achieve two things:

  1. Prevent WordPress updates from busting the browser cache of all the scripts you have enqueued.
  2. Ensure that visitors see the latest version of the script that were changed.

To do so, you can pass a version string to the wp_register_script and wp_enqueue_script functions as the fourth argument.

function wpdc_enqueue_sample_script_with_version() { wp_enqueue_script( 'wpdc-sample-cript', get_template_directory_uri() . '/assets/js/sample-script.js', [], '1.1.2' ); ); add_action( 'wp_enqueue_scripts', 'wpdc_enqueue_sample_script_with_version' );

The code sample uses a version number, which makes sense for libraries. For custom scripts that do not follow strict versioning, a date can be used: 2019-12-07.

The default TwentyTwenty theme uses the theme version (code simplified for readability):

function twentytwenty_register_scripts() { $theme_version = wp_get_theme()->get( 'Version' ); wp_enqueue_script( 'twentytwenty-js', get_template_directory_uri() . '/assets/js/index.js', array(), $theme_version ); } add_action( 'wp_enqueue_scripts', 'twentytwenty_register_scripts' );

This means that every time a new theme version is released, the browser cache for all scripts is busted. In practice this means that browsers end up reloading scripts that were not changed, which is a downside. The upside is that this ensures that there are never any issues with visitors using stale versions of a script.

In addition this requires little maintenance effort from the developers. All they have to do is update the theme version for a new release, and the cache busting takes effect.

By default the browser stops all other work while loading and executing a JavaScript file. This means that the visual output seen by visitors in their browser does not change during that time. Scripts that block the rendering of a page are called render-blocking.

We’ll see below how you can avoid this type of script. But for now we want to focus on another performance optimisation: adding scripts in the footer.

By default, WordPress adds scripts in the header, meaning before the closing </head> HTML tag. All the visible content of a page is part of the <body> section. Meaning that until the browser has finished the head section, visitors will see a white page.

To avoid this, scripts can be added to the footer instead, meaning before the closing </body> tag. This way the page content is already visible, and the visitor experience is better.

To do so, pass true as the fifth argument of wp_register_script or wp_enqueue_script:

function wpdc_enqueue_sample_script_in footer() { wp_enqueue_script( 'wpdc-sample-script', get_template_directory_uri() . '/assets/js/sample-script.js', [], false, true ); ); add_action( 'wp_enqueue_scripts', 'wpdc_enqueue_sample_script_in_footer' );

Careful though with when using scripts with dependencies. The script itself, and all dependencies need to be enqueued in the footer. Else all scripts will be placed in the header.

Adding JavaScript on the frontend

To add JavaScript to the frontend, meaning the part of the site visible to visitors, use the wp_enqueue_scripts hook.

This hook is fired on every single page on a site. You only need a script on a specific type of page. There are several approaches to achieve this, depending on the scenario.

The first is to use Conditional Tags, which return true or false depending on whether the current page fulfills certain conditions. To only add a script to archive pages, you would use is_archive(), like this:

function wpdc_enqueue_script_on_archive_pages() { if ( ! is_archive() { return; } wp_enqueue_script( 'wpdc-sample-script', get_template_directory_uri() . '/assets/js/archive-script.js', [], false, true ); ); add_action( 'wp_enqueue_scripts', 'wpdc_enqueue_sample_script_on_archive_pages' );

If a page is not an archive, is_archive() returns false. By using the ! (called the Not operator in PHP) this will be change to true. By adding the return, the function will return early. This means that the code for enqueuing the script will not be reached.

But instead of looking for a condition that is false, you can of course also use for conditions that are true. You can use more than one condition in the check, and are not limited to just using conditional tags.

function wpdc_enqueue_threaded_comments_script_on_singular_pages() { if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) { wp_enqueue_script( 'comment-reply' ); } }

The code snippet above adds the script that WordPress needs for handling thread comments. The script is only enqueued if:

  1. The page is singular: a single post, page, or custom post type.
  2. If visitors can comment: this is a global and per post setting in WordPress.
  3. If the threaded comments are enabled: this is a setting, so the value of the corresponding option is retrieved.

Adding JavaScript in the admin area

To add a script to the admin area, meaning the part visible to logged-in users, use the admin_enqueue_scripts hook.

This hook is fired on every single admin page. If you only need to add a script to a specific admin screen, you can use the $hook_suffix that is passed by this action.

The hook suffix is the name of the PHP file that is currently displayed. If you go to Posts > Add New, the browser will load the following URL: https://example.com/wp-admin/post-new.php. The $hook_suffix variable is therefore post-new.php.

Keep in mind that admin screens might look the same, but they load a different file. If you edit a post for example, the file being loaded is post.php. Another are example are the Categories and Tags pages, which both use the edit-tags.php file.

Scripts and the Gutenberg Block Editor

When the Gutenberg Block Editor was merged into WordPress Core in version 5.0, two new hooks were added:

  • enqueue_block_editor_assets: Can be used to enqueue bock scripts on Block Editor screen in the admin.
  • enqueue_block_assets: Can be used to enqueue block scripts in the admin and on the frontend.

The hooks that we previously saw continue to work with Gutenberg, so they can also be used.

Debugging Scripts

It’s recommended that you use a developer tool like the Query Monitor plugin to assist you with development. It has a panel that shows all enqueued scripts.

The scripts panel of the Query Monitor plugin.

Common pitfalls

Here is a list of common scenarios.

  • Reusing the same handle: If you register two scripts with the same handle, only the script registered first will be taken into account. WordPress will silently fail on the second enqueue call.

Conclusion

This was a long article, but as you have seen yourself, adding JavaScript to WordPress is a complex topic. But I do hope that this in-depth guide has answered all the questions you had.

If something is missing, please let me know. I’d be glad to update this article with further information.