The Definitive Guide to adding Javascript & CSS to WordPress

Javascript & CSS customization is a staple of every WordPress developer. This article tries to catalog the do’s and don’ts of adding Custom Scripts and Stylesheets to WordPress.

The Naive Approach

The simplest way is to just add script or link tags inline.

For Scripts,

<script type="javascript" src="path/to/js"></script>

And for Stylesheets,

<link rel="stylesheet" href="path/to/css">

This approach bypasses the WordPress API completely.

The biggest problem with injecting Script and Link Tags inline in this manner is incompatibility.

For instance, if you want to inject a jQuery plugin inline into a page you need to ensure that jQuery was included before it.

WordPress already comes bundled with jQuery and most themes use it by default. So you will also need to figure out where your theme adds jQuery and then put your code after it.

Even if you manage to get all this working you’ve effectively nullified any possible optimizations. Optimization plugins work best only if you use standard WordPress APIs.

All in all this is a very bad form. Avoid at all costs!

How To Add Scripts To WordPress

The main WordPress API functions that allow you to add Scripts to WordPress are,

  1. wp_register_script
  2. wp_enqueue_script


The register function takes a unique id argument, a path to the script and some other optional arguments.

The id is a slug that corresponds to your theme or plugin. Avoid using generic slugs like my-script and custom-script.

For instance:- For a plugin that integrates the jQuery Transit library into WordPress, the slug id, jquery-transit is a good choice.

While the other options are not required it is a good idea to also include them.

Of these, version in particular should always be included. It suffixes your version number to the script’s path. This ensures that you don’t have to resort to cache busting when you publish new versions.

The dependencies argument can be left out but it’s a good idea to pass an empty array. This explicitly indicates your script has no dependencies. Otherwise the array must include ids of the scripts/stylesheets you depend on.

The jQuery Transit library from above would list it’s dependencies as, array('jquery').

Scripts should be loaded at the bottom of the page. This allows browsers to continue downloading the content without blocking the rest of the page.

The in_footer argument should also nearly always be provided and be true.

How To Add Stylesheets To WordPress

The WordPress APIs to add Stylesheets to a page follow the same pattern,

  1. wp_register_style
  2. wp_enqueue_style


The wp_register_style function takes similar arguments for id, path, dependencies and version.

The final argument is the media device type that the stylesheet applies to. This defaults to all. It can take values like screen or print for print only styles.

The Path Argument

For Plugins the path parameter should use the WordPress plugins_url helper function to build the path to the script or stylesheet.

If a plugin stores scripts in it’s js sub directory then the path should correspond to,

plugins_url('js/your-script.js', __FILE__);

For plugins not inside the main file you need to store the path to the main file and pass it to plugins_url instead.

For themes the Path parameter has 2 variations. They depend on whether you want Child themes to be able to override your scripts and styles.

To allow child themes to override the style use, get_stylesheet_directory_uri.

get_stylesheet_directory_uri() . '/css/my-style.css';

And to prevent the style being overridden use, get_template_directory_uri,

get_template_directory_uri() . '/css/my-style.css';

How To Add External Scripts And Stylesheets To WordPress

The same API functions are used for adding External Scripts & Stylesheets. However, there are some special considerations for the path argument.

The path must exclude the protocol part from the URL. This ensures that your include works across both HTTP and HTTPS sites.

If you just include a path with the HTTP protocol on a domain that is using HTTPS, your users will get a mixed-content warning message.

Mixed Content Warning


Here script.js will load from or depending on whether your site uses HTTP or HTTPS.

Why Do You Need To Register?

The enqueue functions, wp_enqueue_script and wp_enqueue_style take the same parameters as their register counterparts. They do the work of actually placing the script and stylesheets in your page.

So why use register at all? Couldn’t we just use enqueue directly?

Yes you could. But you should almost always register your scripts before enqueuing them.

This allows anyone wanting to extend your script to do so without needing to go deep into your code to figure out when and where you placed the script.

For instance, if you have a script tied to the presence of a shortcode inside your plugin you will enqueue your script inside your the_content filter.

Now if anyone else wants to extend your script they’ll also need to hook to the the_content filter. While ensuring that their filter fires after your filter.

On the other hand if you register your script before you enqueue it you simplify things significantly for other developers.

Any external script can now merely register itself and declare that it has a dependency on your script. And WordPress will automatically take care of placing the scripts in the right order.

When To Register?

Knowing the above APIs is only half the job. You also need to be aware of when to call these functions.

There is a caveat to registering scripts before you enqueue them. If you register too early you will see this warning,

wp_register_script was called incorrectly. Scripts and styles should not be registered or enqueued until the wp_enqueue_scripts, admin_enqueue_scripts, or login_enqueue_scripts hooks.

The simplest place to do the registration is in the init hook. By the time the init hook has fired, the WordPress asset manager, is ready to accept registration.

add_action('init', array($this, 'onInit'));

function onInit() {
  // register here

    array($this, 'onEnqueueScripts')

function onEnqueueScripts() {
  // enqueue here

When to Enqueue?

After you have registered the script, you still need to enqueue it. Enqueuing tells WordPress to actually place any previously registered Script or Stylesheet on the page.

WordPress provides action hooks to do just this.

  1. wp_enqueue_scripts
  2. admin_enqueue_scripts
  3. login_enqueue_scripts

For the public facing site use, wp_enqueue_scripts and for admin only pages use admin_enqueue_scripts.

Although the name suggests that these hooks are for scripts, they should also be used for Stylesheets.

The login hook is a special hook for adding scripts to wp-login.php.

Conditionally Adding Scripts

A common task when adding scripts to a page is to do so conditionally. The specific condition usually depends on the presence of some element on the page.

The simplest example is Shortcodes.

The wp_enqueue_script function is smart enough to work from within filters like the_content. Any scripts or styles you enqueue from within this filter will get placed on the page correctly.

If all you need to do is to place a script connected to a shortcode into a page, enqueuing it from the the_content filter is sufficient. It is also ok to enqueue the script multiple times. WordPress will ensure that it is placed only once on the page.

Sometimes you need to do conditional enqueuing based on whether something was placed on the page, the type of the current page, and so on. Since you are dependent on the page being processed first you need to do this as late as possible.

The wp_footer hook is a good place to do this. This hook is theme dependent but it is universally present in nearly all themes. You will only find it missing in poorly written themes.

Use it to enqueue your previously registered scripts, conditionally.

Enqueuing and Stylesheets

Stylesheets should be placed in the head of a document. This ensures that the styles are available as the body of the document is received by the browser. This prevents the brief Flash-of-unstyled-content if the content was loaded before the styles.

Using the wp_enqueue_scripts hook to enqueue styles will ensure that styles are placed in the head of the document automatically.

However things aren’t as straight forward if you want to conditionally include styles. For instance if you need to include custom styles only on the presence of a shortcode in a the_content filter.

By the time this filter is called it is too late to enqueue styles in the head. You can still call wp_enqueue_style from inside the the_content hook, but the style with get placed along side the footer scripts.

This means your page won’t pass validation. The HTML Spec only allows link tags in the head of a document.

However nearly all browsers are pragmatic with their interpretation of this part of the HTML spec. So placing styles in the footer is not likely to break anything.

The only issue you might run into is a slight FOUC if the page you are rendering depends on the styles being in the head.

Try to load theme stylesheets in the head and conditional stylesheet customizations in the footer.

Adding Scripts To The WordPress Admin

Adding scripts to Options pages in the wp-admin is a little trickier. If you just use the admin_enqueue_scripts hook you will end up adding your custom script to all pages in the admin!

At best this is very poor form. And at worst you could end up breaking other plugin’s Options Pages. Your scripts & styles should only be added to your own Options pages.

WordPress provides a load-{page} hook whenever an admin page is loaded in WordPress. This is the ideal place to setup the eventual admin_enqueue_scripts hook that enqueues the script.

Further to prevent conflicts, you should also move Registration to inside the load-{page} callback.

The id of the page is returned by the method you call to register your admin menu page. For instance, add_options_page would return load-settings_page_my_slug.

$page = add_options_page(...);
add_action("load-$page", array($this, 'onLoadPage'));

function onLoadPage() {
  // register here

    array($this, 'onAdminScripts')

function onAdminScripts() {
  // enqueue here

When Not to Enqueue Scripts

Another alternative to conditionally enqueuing your scripts are the printing hooks like wp_print_scripts. These hooks fire just before WordPress writes the scripts to the page. This is the final call to make modifications to the queue.

This hook is really meant for Optimization plugins. It is the perfect place to concatenate and minify the assets enqueued.

But if this hook is also used for adding new scripts to the page, Optimization becomes a cumbersome process where you need to maintain custom rules about what scripts to move, which not to move, etc.

Another complication is that there are multiple phases involved in how WordPress adds scripts. Both head and footer scripts are processed separately.

Try to avoid these hooks if possible.

Passing Options to Scripts

Barring some some very basic scripts, you will also need to pass some configuration options to your scripts. These options will usually be based on values that users select in your corresponding Options Page.

The WordPress API to do this is misleadingly named wp_localize_script. While you could also use it for localization, it is very useful for passing configuration parameters to your scripts.

The function takes an option variable name and it’s corresponding data. The option variable will correspond to a global Javascript variable that will contain the JSONified data.

For example, Consider a script that adds social sharing buttons to a page. The script could be placed on the page with,


If the user were allowed to changed the networks to display, you could pass this configuration to the script using,

  'social_buttons', 'social_buttons_options',
  array('networks' => array('twitter', 'facebook', 'google+'))

Then in your social_button script you will have access to the social_buttons_options Javascript global variable with the data,

  "social_buttons_options": {
    "networks": ['twitter', 'facebook', 'google+']

One thing to consider when using wp_localize_script is that the values are converted to strings. For instance if you pass a boolean to it like so,

  'social_buttons', 'social_buttons_options',
  array('enabled' => true)

You will end up with enabled equal to the string 1.

  "social_buttons_options": {
    "enabled": "1"

Make sure to check and convert the data types of options in the localized Javascript global variable before using it’s values.

Replacing Libraries Bundled With WordPress

The wp_deregister_script function can be used to remove a previously registered library. If you re-add another library with the same slug id, you will have replaced the existing library with your custom one.

However there are very few reasons to do this. WordPress ships with recent stable versions of most libraries by default.

For instance, if you replace the bundled jQuery library, you have no way of knowing if your version is compatible with every plugin or theme in your WordPress installation.

Further the WordPress Admin also uses jQuery extensively. You can’t even be certain that the Admin will work as expected after you do this.

The only valid reason to do this is if there are Vulnerabilities in any library used by WordPress. And you can’t wait until WordPress pushes a security fix.

The wp_deregister_style does the same for Stylesheets.

Avoid replacing libraries bundled with WordPress!


If you’ve done all this correctly you will end up with a site that adds scripts in the correct order and in the right place. Now it’s time to optimize the Javascript & CSS.

Why optimize in the first place?

If you use stock WordPress with the TwentyFourteen theme you will have the following scripts & stylesheets on your page.

Before Autoptimize

Each additional Javascript & CSS file you add to the page increases the weight of your page and hence the time it takes for your page to load.

And Javascript requests block the rest of your page from loading. So every additional script on your site incrementally adds to your page load time.

Reducing Requests is a simple way to reduce the roundtrips between the user’s browser and your server.

An excellent plugin that does this is Autoptimize. It takes care of minification and concatanation of both Javascript and CSS. All you need to do is install it and enable CSS & Javascript optimization.

After using Autoptimize with the stock twentyfourteen theme the number of Requests reduce down to just 2. One CSS file & One Javascript file!

After Autoptimize

Bear in mind this is just on a stock theme with no plugins! The benefits of Autoptimize will be massive on a site with custom Themes with lots of Javascript, and many plugins.


Use the provided WordPress APIs to add Scripts & Stylesheets to your Pages. You’ll have an easier time optimizing your site. And you’ll make the job of developers wanting to extend you code much simpler.

  • titaniumbones

    I know this is a pretty old post, but I would love to hear your thoughts on enqueueing plugin/shortcode CSS in a way that allows it to be overridden by theme authors. I am writing a shortcode that embeds an iframe, and need to provide some CSS to get its proportions and placement right, but I think theme authors may well want to override the default values. Do they need to use the !important keyword, or can I place my CSS above the theme’s CSS so that it gets overridden?


    • Darshan Sawardekar

      There are a few different approaches that plugin developers follow. My preferred approach is to use the dependencies parameter of the wp_enqueue_ functions.

      For instance, If the plugin enqueues the javascript file ‘my-plugin’, then you can use ‘my-plugin’ as a dependency in the theme’s wp_enqueue_script call.

      WordPress will see the dependency and make sure to add my-plugin and the custom script after it.

  • Ron Bray

    First of all congratulations on your awesome blog, I’ve learned a lot just from reading it and I’m already planning on taking my very first trip as soon as I’m done with college. I have a question that’s not related specifically to this article though, which camera do you use? Your shots are great!