How to Properly Include CSS and JS in your WordPress Themes and Plugins

Gosh, guys, I understand that this kind of beginners guide but in all my other tutorials I very often talk about including JavaScript and CSS, so since now I am able to give you a link to this tutorial. And to be honest, I didn’t find any good and fresh tutorial on this topic.

Before we proceed to the guide I want you to know that you should never add external CSS and JS directly to WordPress template files like header.php, footer.php etc. In some cases it could be OK to add some internal code there using <style> and <script> tags, but also not recommended.

Internal CSS and JavaScript

Just to make it clear for you – internal CSS is what is between <style> tag directly in HTML and internal JS is what is in between <script> tags.

WordPress has 4 action hook that can be useful for internal CSS and JavaScript:

1. Add Custom CSS to WordPress Admin Dashboard using admin_head action hook.

Let me show you a quick example:

add_action( 'admin_head', 'misha_custom_internal_css' );
function misha_custom_internal_css(){
	echo '<style>
		#adminmenu li.wp-has-current-submenu a.wp-has-current-submenu {
			background: #a73fe0;

Of course echo is not necessary here, you could better use opening and closing <?php tags instead.

And here is the result of this code:

How to add custom CSS to WordPress admin dashboard using admin_head action hook.

It is worth mentioning in the first place that it is not necessary to use admin_head (or wp_head) only for CSS and admin_footer (or wp_footer only for JavaScript).

This code is very similar to the previous example, the only change is that we use JavaScript instead of CSS:

add_action( 'admin_footer', 'misha_custom_internal_javascript' );
function misha_custom_internal_javascript(){
	echo '<script>
		jQuery( function( $ ) {
			alert( \'hello\' );
		} );

By the way, attributes type="text/css" for the <style> tag and type="text/javascript" for the <script> tag are obsolete.

External CSS and JavaScript Files

External CSS/JS is when there is a file .js or .css and it is not necessarily on your domain.

When you have to include some external CSS or JavaScript files, you must use these action hooks, always:

But this is not all! You can not just print <script> or <link> tags inside these hooks, you have to use special functions, here is the list which I personally use quite often:

There are more functions. But using these functions is usually more than enough! I will show you how to deal with them in the examples below:

But how the files are added to the HTML templates of a theme? It will be done automatically via wp_head() and wp_footer() functions of course, which must be in your template files before closing </head> and </body> tags accordingly.

1. Including the main theme style.css

As simple as this:

add_action( 'wp_enqueue_scripts', 'misha_main_theme_css' );
function misha_main_theme_css() {
	wp_enqueue_script( 'misha-css', get_stylesheet_uri() );

Actually there is 3 ways how you can get the url of style.css of a theme:

Ok, but what is the first parameter which I set as misha-css? In this very example it is quite useless, but you need it when you use wp_register_script() or wp_register_style() functions and when you set up file depenedencies, about dependecies – in the next example.

The second parameter can also contain the full URL of a file on another domain, like for example Google Fonts.

2. Using Pre-registered Scripts

Please open wp-includes/js directory. Do you see, a lot of files are there. You can find jQuery there and some fimiliar JS plugins as well. What all that means?

It means that most of the scripts are already registered and if you’re going to use them, you can do it this way:

add_action( 'wp_enqueue_scripts', 'misha_include_jquery' );
function misha_include_jquery() {
	wp_enqueue_script( 'jquery' ); // you just have to know a script handle

You can find the full list of pre-registered scripts in the official WordPress documentation or in wp-includes/script-loader.php.

It is also possible for you to pre-register your own scripts and styles, for example in some themes or plugins you can find this implementation:

add_action( 'wp_enqueue_scripts', 'misha_register_and_enqueue' );
function misha_register_and_enqueue() {
	wp_register_script( 'some-script', $url );
	wp_enqueue_script( 'some-script' );

Function wp_register_script() has the same parameters like wp_enqueue_script() by the way.

3. Dependencies

Do you know that the order of styles and especially scripts matters? I hope yes. So, if you’re using jQuery library, you have to include your .js files after it. Sometimes the order of CSS files matters as well.

I want to make it clear that if you’re using the only hook wp_enqueue_scripts and all the scripts are listed there, you can skip the dependency parameter. But if you create a custom plugin and this plugin requires jQuery library, it is a must.

add_action( 'wp_enqueue_scripts', 'misha_custom_js_with_dependency' );
function misha_custom_js_with_dependency() {
		plugin_dir_url( __FILE__ ) . 'assets/script.js',
		array( 'jquery', 'misha-script-1' )

So, it doesn’t matter what is the hook priority and in what file of a plugin or theme you are going to use this code – in all cases misha-script-2 will be added to HTML document only after jquery and misha-script-1.

4. Prevent CSS and JavaScript files from being cached

This is what the 4th parameter of wp_register_script(), wp_register_style(), wp_enqueue_script(), wp_enqueue_style() is for.

Ages ago I used time() function there, it allows to prevent the files from being cached completely. But isn’t it better to refresh the cache only if the files have been changed?

Here is how:

add_action( 'wp_enqueue_scripts', 'misha_main_theme_css_cache_refresh' );
function misha_main_theme_css_cache_refresh() {
		filemtime( dirname( __FILE__ ) . '/style.css' )

PHP function filemtime() is quite interesting, because you have to pass the absolute server path to it, which can be easily done with dirname(), which adds one more layer of complexity here, because the path returned by dirname() is relative to the file where it is used. In this example file style.css must be in the same directory.

Let’s take a look at another example for custom plugins. We have a plugin file misha-plugin.php, directory assets/ and script.js in it.

add_action( 'wp_enqueue_scripts', 'misha_plugin_javascript_cache_refresh' );
function misha_plugin_javascript_cache_refresh() {
		plugin_dir_url( __FILE__ ) . 'assets/script.js', // no slashes
		array( 'jquery' ),
		filemtime( dirname( __FILE__ ) . '/assets/script.js )

Bottom line:

We have two approaches to prevent included CSS and JavaScript files from being cached like forever:

We can set this parameter to null to remove query string at all because by default it displays the current version of WordPress installation!

If you have ever scanned your website with Google PageSpeed, you know, that it always recommends to move all your JavaScript and even CSS files to the website footer, before the closing </body> tag. In my experience I can say, that it is not a big deal for website performance. But sometimes moving jQuery and your custom heavy .js file to the website footer can be a good idea.

add_action( 'wp_enqueue_scripts', 'misha_jquery_in_footer' );
function misha_jquery_in_footer() {
	wp_deregister_script( 'jquery' );
		includes_url( '/js/jquery/jquery.js' ), 
		false, // no dependencies
		NULL, // no version to show
		true // in footer? yes
	wp_enqueue_script( 'jquery' );

Parameter on line 11 allows you to control where you are going to display the script – in website header – false (default) or footer – true. Functions wp_register_style() and wp_enqueue_style() don’t have this parameter.

6. Pass PHP parameters to JS

In this part of the tutorial I would like to talk mostly about wp_localize_script() function but you can use for this purpose brand new wp_add_inline_script() which appeared in WP 4.5.

The key idea is that sometimes you have to pass some dynamically generated values into your JavaScript code without creating additional AJAX calls or using internal JS. You can also guess from the function name “localize script” that it was created for translating strings in JavaScript. No more words, let’s take a look at the examples:

add_action('wp_enqueue_scripts', 'misha_pass_php_args_to_js');
function misha_pass_php_args_to_js(){
	wp_register_script( 'misha-js', $script_url );
		'mishaParams', // it is the name of JavaScript variable (object)
			'parameter_1' => site_url(), // for example
			'translated_string' => __( 'Settings saved' ), 
	wp_enqueue_script( 'misha-js' );

So, function wp_localize_script() has 3 parameters:

As the result, the function will print something like this before your external javascript misha-js:

<script type='text/javascript'>
/* <![CDATA[ */
var mishaParams = {"parameter_1":"", 'translated_string' => 'Inställningar Sparade'};
/* ]]> */

You can use any of these parameters this way:

console.log( mishaParams.parameter_1 );

7. Deregister Scripts and Stylesheets you Do Not Need

Good example here is a plugin Contact Form 7 which adds its own stylesheet on your website. In some cases you may not need it, so let me show you how to deactivate it easily:

add_action( 'wp_enqueue_scripts', 'misha_remove_contact_form_7_css', 9999 );
function misha_remove_contact_form_7_css() {
	wp_deregister_style( 'contact-form-7' );

You may wonder – how did I guess the handle of the css file, which is contact-form-7 on line 4. Actually it is easily to get, just open the source code of your page in browser, find a script or stylesheet your would like to remove and look at its id tag attribute. In this example it was contact-form-7-css, in other words {HANDLE}-css. But.. script tags don’t have id attributes! Well, this you have to figured out yorself. I recommend to perform a search in files.

8. How to Add Script on Specific Pages Only

Using conditional tags are OK. The list of tags you can find here, in official WordPress documentation.

add_action( 'wp_enqueue_scripts', 'misha_js_for_homepage_only' );
function misha_js_for_homepage_only() {
	if( is_front_page() ) { // if on a website homepage
		wp_enqueue_script( 'homepage-js', $url, 'jquery', null, true );

The situation with admin scrips is different, because admin_enqueue_scripts action hook accepts $hook_suffix parameter which is an identifier of an admin page:

add_action( 'admin_enqueue_scripts', 'misha_admin_js_for_certain_pages' );
function misha_admin_js_for_certain_pages( $hook_suffix ) {
	// Dashboard, Posts, Pages pages
	if( $hook_suffix == 'index.php' || $hook_suffix == 'edit.php' ) {
		wp_enqueue_script( 'admin-js', $url, false, null, true );

If you want to check what is a $hook_suffix of a certain admin page, you can just echo $hook_suffix directly from the hook when you are on that page.

That’s all for this basic tut 🙌 If you have any questions, welcome to comments below.

Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X