WordPress – Relative Stylesheets and Scripts, Plus wp-config.php Order

  • report
    Click for Disclaimer
    This Post is over a year old (first published about 4 years ago). As such, please keep in mind that some of the information may no longer be accurate, best practice, or a reflection of how I would approach the same thing today.
  • infoFull Post Details
    info_outlineClick for Full Post Details
    Date Posted:
    Aug. 18, 2019
    Last Updated:
    Nov. 29, 2019
  • classTags
    classClick for Tags

I recently wanted to force WordPress to make all my <script> and stylesheet (<link>) tags load relative URLs instead of absolute URLs, and ended up learning a bit about the internals of WordPress (probably more than I wanted to know). For reference, I wanted to turn this:

<link rel=’stylesheet’ id=’joshuatzwp-style-css’ href=’http://joshuatz-wp.test/wp-content/themes/joshuatzwp/style.css‘ type=’text/css’ media=’all’ />

Into this:

<link rel=’stylesheet’ id=’joshuatzwp-style-css’ href=’/wp-content/themes/joshuatzwp/style.css‘ type=’text/css’ media=’all’ />

I wanted to do this because I was working with a local development environment, using Laragon on Windows as an Apache host, and proxying through Ngrok or something similar was not working with absolute domain-tied links, for a number of reasons. Furthermore, relative paths that were being passed to wp_enqueue_style were not being preserved.

Click here to jump right to the solution, or read on if you are interested in the details of how WordPress forces absolute paths.

First wrinkle: WP forces absolute links

The first issue I ran into was WordPress does not respect the format you use to pass links into the enqueue system, even if you make 100% sure you pass in a relative link. For example, look at this code:


You would expect that the use of wp_make_link_relative would ensure a relative link, but the style tag that this generates actually is absolute with the domain:

<link rel=’stylesheet’ id=’joshuatzwp-style-css’ href=’http://joshuatz-wp.test/wp-content/themes/joshuatzwp/style.css’ type=’text/css’ media=’all’ />

Why is this happening? Well, it is not a fun answer:

WordPress do_item complexity

To figure out what was going on, I ended up tracing backwards from where the <script> and <link> tags are actually echoed out into the page, to see how that HTML is generated. Here is a rough overview of what that looks like:

None of this knowledge was strictly necessary to force relative paths, but it gives some insight into how the whole process works. I wouldn’t actually want to change any of these files, since they are part of the core of WP, and would be overwitten on a system update anyways.

The solution for relative paths:

The solution here is to hook into a WordPress filter. I’ve written about this before, when trying to add async/defer attributes to scripts and style tags, in this post. In fact, our solution is going to look pretty similar.

Basically, we will hook into the filter for ‘script_loader_tag’, which is the filter for <script> tags, and ‘style_loader_tag’, which is the filter for stylesheet <link> tags. In our hook, we will take the source that WordPress has prefixed and forced into an absolute URL, and remove the domain part that they added.

Here is the code:

function makeInternalLinkRelative($src){
    return str_replace(get_option('siteurl'),'',$src);

add_filter('style_loader_src',function($src, $handle){
    return makeInternalLinkRelative($src);
add_filter('script_loader_src',function($src, $handle){
    return makeInternalLinkRelative($src);

Pretty simple, huh! Just add this code to your theme’s (or child theme’s) function.php file, and you should be good to go!

The Solution for Dynamic Domains

If you don’t want to use relative paths, but just want WordPress to know when you are serving it through a different domain (such as when using Ngrok or swapping domains) and automatically adjust all the links, there is another solution we can employ. We can modify the wp-config.php file in the root of our WordPress install to dynamically set the “siteurl”, which as my earlier research pointed out, is used internally by WP to auto-prefix relative links.

Here is what I have added to my wp-config.php file:

$host = $_SERVER['HTTP_HOST'];
$protocol = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT']===443)) ? 'https://' : 'http://';
define('WP_SITEURL', $protocol . $host);
define('WP_HOME', $protocol . $host);

Now, here is the really important thing. Make sure this code comes before this line of code that should already be in the file:

require_once(ABSPATH . 'wp-settings.php');

This matters because wp-settings.php is responsible for the chain of events that fills in the $base_url variable we were looking at earlier, and is ultimately responsible for prefixing URLs. If that runs before you set the “siteurl” global, it will have the domain from your admin settings, not from our dynamic override. I found this out the hard way!

4 thoughts on “WordPress – Relative Stylesheets and Scripts, Plus wp-config.php Order”

  1. Matt B says:

    Thanks Joshua, that worked for me.

    I have since swapped it for this plugin, https://wordpress.org/plugins/make-paths-relative/ as I need all the permalinks to be relative too.

    For security and performance I’m making my site static, as it’s basic text blog. I’m planning to run a local copy of wordpress, then export the files with https://wordpress.org/plugins/simply-static/. From reading it looks like the permalinks are there for feeds mainly. The plugin allows me to export all the links as absolute, so I won’t have a problem there.

    1. joshuatz says:

      Hi Matt,

      Thanks for the tips! The `make-paths-relative` plugin looks like a really nice configurable option. I took a peek at their code, and it’s nice to see their code seems to take some similar approaches as well.

      Thanks for the static site tips too! I’ve been experimenting a little with static sites myself for cheatsheets.joshuatz.com, but I’m using a pure Markdown repository (hosted out of Github) as the content source, and GatsbyJS to transform it into static files. That looks like an interesting plugin!

  2. Mike Wilson says:

    “Now, here is the really important thing. Make sure this code comes before this line of code that should already be in the file”

    LIFE SAVER. I was banging my head on my desk trying to figure out why my site kept loading resources from my internal admin URL and not my CDN. THANK YOU!!

    1. joshuatz says:

      You’re welcome! It definitely took me a while to find that issue when I ran into it haha

Leave a Reply

Your email address will not be published.