The story behind rewrite API plays out almost like a Disney movie. You have WordPress and the human (played by you!). Two different worlds. Two different languages.

WordPress is a machine and only understands if you talk to it as such. You need to use a cold language of numbers and code so that it can understand you. Do you need to see a WordPress post? You need to ask him using query variables like p=123 .

On the other hand, humans are friendly and talkative. They expect to talk to someone using words. They want to see URLs with the post-title and not query variables.

The rewrite API is the translator that brought these two unlikely friends together. Rewrite rules convert your post-title into the p=123 query variable WordPress wants. Both sides get their happy ending (yay!).

How does the rewrite API do this?

At its core, the rewrite API is about rewrite rules. WordPress uses rewrite rules to convert your human-friendly URLs into ones it can use. There’re two components to a rule: a regular expression and a redirect URL.

The regular expression is what makes the rewrite API so complicated (and also intimidating). WordPress attempts to match any URL it receives to a regular expression. If it finds a match, it’ll convert it to the registered redirect URL. This redirect URL is the WordPress-friendly URL that it wants to see.

The rewrite API keeps track of all the rewrite rules. Whenever WordPress receives a request, it’ll cycle through them attempting to find a match. If it doesn’t find one, it’ll return a 404 page.

It’s also important to mention that the rewrite API isn’t on by default. You need to have permalinks enabled. How do you know if you enabled permalinks? You just need to go to Settings > Permalinks and make sure you aren’t using the default setting.

Meanwhile on your web server

Your web server isn’t idle during this either. In fact, you need to configure it for the rewrite API to even work. Otherwise, your rewrite rules won’t do anything.

The goal of the web server configuration is two-fold. First, it needs to return files or directories when requested. The second is to send any other requests to WordPress. The rewrite API then gobbles them all up (nom nom nom) for processing.

For a lot of WordPress installations, this all happens in a file called .htaccess . Apache, IIS and as well as other web servers support the use of it.

Dissecting .htaccess

By default, the WordPress .htaccess file looks like this:

# BEGIN WordPress <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> # END WordPress 1 2 3 4 5 6 7 8 9 10 # BEGIN WordPress < IfModule mod_rewrite.c > RewriteEngine On RewriteBase / RewriteRule ^ index \ . php $ - [L] RewriteCond %{REQUEST_FILENAME} ! -f RewriteCond %{REQUEST_FILENAME} ! -d RewriteRule . / index . php [L] < /IfModule > # END WordPress

These are directives for the web server to follow. It’ll go through them in sequence so the order is important. Let’s take a look at each of them.

RewriteEngine on tells the web server to use its rewrite engine. This directive is necessary because the default is off. Without it, the web server would ignore all following directives.

RewriteBase / configures the prefix for all rewrite directives. In our case, we’re saying that the root path is the base of our all rewrite directives. This allows the web server to use the rewrite directives on any URL it receives.

RewriteRule ^index\.php$ - [L] tells the web server to process any request for index.php file right away. When that happens, it’ll ignore the rest of the directives.

RewriteCond %{REQUEST_FILENAME} !-f and RewriteCond %{REQUEST_FILENAME} !-d are the two following directives. They’re conditions that the web server needs to check. Otherwise, it can’t proceed with the next rule. The first one asks to check if the URL isn’t requesting a regular file that exists. The second one does the same thing, but for a directory. In other words, the web server needs to return files and directories when requested. That was the first goal of the web server configuration.

RewriteRule . /index.php [L] is the final directive. It says to send any URL to index.php . At this point, the request isn’t a file or a directory. The two previous conditions made sure of that. All that’s left are URLs that we want the rewrite API to handle. This covers the second goal of the web server configuration.

What about Nginx?

The short answer: you don’t need to do anything with Nginx (huzzah!). It doesn’t need a .htaccess file to support the rewrite API. Nginx handles it all with the default WordPress configuration.

The configuration line in question is try_files $uri $uri/ /index.php?$args; . This line is equivalent to the .htaccess file we just dissected. The lack of .htaccess file used to throw WordPress for a loop, but not anymore.

Let’s dive into rewrite API

Now that you’ve seen what’s going on on the web server, let’s bring it back to the rewrite API. What you associate with it is a set of helper functions. Behind them, there’s also the WP_Rewrite class where the heavy lifting takes place.

These helper functions hide most of its inner workings from you. This is what we’ll focus on. We’ll go over them all and how they’re used. We’ll keep nitty-gritty details of what happens in the WP_Rewrite class for another article.

add_rewrite_rule

add_rewrite_rule is the bread and butter of the rewrite API. Most of your interaction with it is through that function.

It does exactly as its name suggests. It lets you add a new rewrite rule. The function has three parameters: regex , redirect and after .

function add_rewrite_rule($regex, $redirect, $after = 'bottom') 1 function add_rewrite_rule ( $regex , $redirect , $after = 'bottom' )

We saw earlier that a rewrite rule has two components. There’s a regular expression which is the regex parameter. And there’s the redirect URL used when WordPress matches regex . That’s the redirect parameter.

The WP_Rewrite class stores all rules in arrays. after determines where to insert the rule. It has two options: bottom and top .

Creating a “welcome” rewrite rule

Let’s look at a small example. We’re going to create a rewrite rule that displays the “Hello World!” post.

function welcome_rewrite_rule() { add_rewrite_rule('^welcome$', 'index.php?p=1', 'top'); } add_action('init', 'welcome_rewrite_rule'); 1 2 3 4 5 function welcome_rewrite_rule ( ) { add_rewrite_rule ( '^welcome$' , 'index.php?p=1' , 'top' ) ; } add_action ( 'init' , 'welcome_rewrite_rule' ) ;

The first argument is our regular expression. We’re saying that we want to match welcome exactly. That’s why there’s a ^ at the beginning and a $ at the end.

index.php?p=1 is our redirect URL. It tells WordPress to load the post with ID = 1 . That’s the “Hello World!” post.

We didn’t use the default value of after . By using top , we’re sure that WordPress will process our rewrite rule first. This reduces the risk of conflicts.

add_rewrite_tag

add_rewrite_tag is the other important function of the rewrite API. You’ll almost always see it used with add_rewrite_rule . Once again, the name makes its purpose straightforward (it adds a rewrite tag duh). That said, what do tags do though?

Tags act as placeholders for query variables. These allow information to pass from rewrite URLs back to WordPress. It’s a critical aspect of the rewrite API. add_rewrite_tag is one way to create this link between the two.

function add_rewrite_tag($tag, $regex, $query = '') 1 function add_rewrite_tag ( $tag , $regex , $query = '' )

The function uses three parameters: tag , regex and query . It’s important that every tag start and end with a % . If it doesn’t, add_rewrite_tag won’t do anything. regex is another regular expression. The rewrite API will replace every occurrence of our tag with our regex .

query is an optional parameter. It lets you specify the query variable to use with your tag. You need to keep a few things in mind if you decide to use it. The query parameter that you pass needs to end with = . You also need to make sure that you’ve added that query variable to WordPress. It isn’t done for you in that situation.

Now, let’s say you don’t use query . You have to call add_rewrite_tag early during the WordPress loading process. The last possible hook that you can use is init . The query variable for the tag won’t work otherwise.

Creating the %postname% tag

Let’s look at an example using a predefined rewrite tag. Let’s say %postname% . How would use add_rewrite_tag to add it to WordPress?

function postname_tag() { add_rewrite_tag('%postname%', '([^/]+)', 'name='); } add_action('init', 'postname_tag'); 1 2 3 4 5 function postname_tag ( ) { add_rewrite_tag ( '%postname%' , '([^/]+)' , 'name=' ) ; } add_action ( 'init' , 'postname_tag' ) ;

The first argument is our tag %postname% (Simple enough!). The second argument is the regular expression that the rewrite API replaces %postname% with. The regular expression captures everything before the next / .

The last argument is name= . That’s a public query variable that %postname% gets mapped to. It’s one of the default query variable found in the WP class (check the public_query_vars variable).

How do you find those values? They’re all stored in internal arrays in the WP_Rewrite class. We used the init hook here, but that wasn’t necessary. name was already an existing query variable.

add_rewrite_endpoint

add_rewrite_endpoint allows you to create endpoints for types of WordPress URLs. Endpoints are a powerful feature of the rewrite API. They’re a rewrite rule that’s applied to the end of a URL as opposed to the beginning. The function takes in three parameters: name , places and query_var .

function add_rewrite_endpoint($name, $places, $query_var = null) 1 function add_rewrite_endpoint ( $name , $places , $query_var = null )

name is the name of the endpoint you want to use. query_var behaves the same way as the query parameter for add_rewrite_tag . It lets you specify the the query variable to use with your endpoint. Unlike query , you shouldn’t put a = at the end.

places is a mask used to determine which types of URL to apply our endpoint to. This gives you a precise control on the types of WordPress URLs your endpoint will work on. It’s just more complicated than the function parameters you’re used to seeing.

For a deeper dive into endpoints and masks, you should take a look at this post by Jon Cave. Let’s still take a moment to look at a small usage example.

Creating a print endpoint

Le’s say you want to create links to printer-friendly versions of posts and pages (What? Some people still use printers!). You want to do this in a clean way though. Every post URL where you append /print sends you to the printer-friendly page. That page uses a custom template.

function print_endpoint() { add_rewrite_endpoint('print', EP_PERMALINK | EP_PAGES); } add_action('init', 'print_endpoint'); 1 2 3 4 5 function print_endpoint ( ) { add_rewrite_endpoint ( 'print' , EP_PERMALINK | EP_PAGES ) ; } add_action ( 'init' , 'print_endpoint' ) ;

This creates our endpoint. We use print for the name . EP_PERMALINK | EP_PAGES is the places mask. It says that our endpoint works for both post permalinks and pages. All that’s left is to make sure that we include our print template. We use the template_include filter to do that.

function include_print_template($template) { if (false === get_query_var('print', false)) { return $template; } return get_query_template('print'); } add_filter('template_include', 'include_print_template'); 1 2 3 4 5 6 7 8 9 function include_print_template ( $template ) { if ( false === get_query_var ( 'print' , false ) ) { return $template ; } return get_query_template ( 'print' ) ; } add_filter ( 'template_include' , 'include_print_template' ) ;

We start by checking if our print query variable is present. It’s print because we didn’t specify anything else for query_var . We use get_query_var to fetch it. We used false as the default value here There’s a reason for that.

We’re looking to know when print isn’t in the query variables. The empty string default does a poor job here. print can (and will) return an empty string. This prevents us from knowing when print is there, but it’s an empty string. So we use false as a default and the stricter === operator as well.

The last line fetches our template using get_query_template . At that point, we’re sure that we have a request for a print page.

add_permastruct

add_permastruct is another interesting function that you don’t hear much about. It lets you create a permalink structure. It’s a set of rewrite rules that uses a base rewrite rule. It’s how WordPress builds all the rewrite rules for your custom post types and custom taxonomies.

function add_permastruct($name, $struct, $args = array()) 1 function add_permastruct ( $name , $struct , $args = array ( ) )

Let’s take a look at the function and its three parameters: name , struct and args . The WP_Rewrite class stores permastructs in an array. It only uses name as the array key. It then passes it to a filter when it creates all the rewrite rules.

struct is the base rewrite rule that the permalink structure uses. It’s important that struct contain at least one rewrite tag. That tag allows you to identify the rewrite rules created for the permalink structure.

args is an array of configuration options for the creation of the rewrite rules. A lot of them are boolean flags to control the types of rewrite rules created. Most of these are straightforward. The option that needs a bit of an explanation is with_front .

When it’s active, your permalink structure gets prepended by the rewrite API. It’ll prepend it using the static part of the permalink structure used by posts. In most cases, that’s / . However, let’s say you’re using numeric permalinks. The post permalink structure would be /archive/%post_id% . In that case, /archive gets prepended to your permalink structure.

add_feed

add_feed allows you to add a new feed type (like atom and RSS). Feeds work a lot like a customizable endpoint. The function lets you change it using two parameters: feedname and function .

function add_feed($feedname, $function) 1 function add_feed ( $feedname , $function )

feedname is the name of the feed added. You should view the endpoint as /feed/%feedname% . feedname is a new potential value for that endpoint. There’s also a feed specific hook that makes use of it. This brings us to the second parameter.

The function is the callback used to display your new feed. add_feed attaches it to the do_feed_$feedname hook. That’s the feed specific hook mentioned earlier. The WordPress template loader will call it when it detects a request for a feed.

flush_rewrite_rules

flush_rewrite_rules recreates all the rewrite rules registered with the rewrite API. Any time you make changes to it, you need to call this function. If you don’t, your changes won’t take effect.

The obvious thing to do is to call it all the time. That way, you don’t have to worry if your rules are taking effect or not. Awesome! Except you can’t do that (awwww).

The issue is that flush_rewrite_rules can slow down WordPress quite a bit. It’s an expensive operation to recreate all the rewrite rules. You have to be careful about when you call it.

function flush_rewrite_rules($hard = true) 1 function flush_rewrite_rules ( $hard = true )

It can also recreate the .htaccess file. That’s what its only parameter controls. hard is a flag used to determine if it’ll recreate the .htaccess file or not. By default, it does.

It’s worth noting that the function isn’t expensive because it’s recreating the .htaccess file. It’s because it’s recreating the rewrite rules. That means the value of hard won’t have an impact on the slowdown caused by the function.

How do call flush_rewrite_rules?

That’s the tricky question. You can’t use a hook that fires all the time like init . You need a plan for when to call it.

A common recommendation is to use register_activation_hook and register_deactivation_hook . The issue with those two hooks is that they don’t work with updates. Let’s say you add new rewrite rules in a plugin update. WordPress won’t call the activation hook to flush them.

It also doesn’t work if your plugin settings change the rewrite rules. That’s another condition to think about. That’s why the activation hook (deactivation is fine) isn’t an ideal candidate.

For themes, it’s more straightforward. You can use the after_switch_theme hook. You still need to consider what happens during updates or setting changes though.

The last thing to keep in mind is the timing of the call. You need to make sure you call flush_rewrite_rules after you added your rewrite rules. It’s a silly thing, but it’s frustrating to debug the first time it happens to you!

An interesting solution is to use the init hook with a flag to trigger the flush.

function myplugin_flush_rewrite_rules() { if (get_option('myplugin_flush_rewrite_rules')) { flush_rewrite_rules(); delete_option('myplugin_flush_rewrite_rules'); } } add_action('init', 'myplugin_flush_rewrite_rules', 99); 1 2 3 4 5 6 7 8 function myplugin_flush_rewrite_rules ( ) { if ( get_option ( 'myplugin_flush_rewrite_rules' ) ) { flush_rewrite_rules ( ) ; delete_option ( 'myplugin_flush_rewrite_rules' ) ; } } add_action ( 'init' , 'myplugin_flush_rewrite_rules' , 99 ) ;

This way flush_rewrite_rules only gets called if the myplugin_flush_rewrite_rules option is set. This lets you control when to call the function. You can set the option whenever you need to. WordPress activated your plugin (or theme), your settings changed or something else happened. We set the priority to a high number so WordPress calls the hook last.

You’ll also notice that there’s no call to add_rewrite_rule . You want WordPress to add your rewrite rules all the time. Otherwise, another plugin might call flush_rewrite_rules and erase them. So they’re left inside their own separate function.

A powerful ability

In the hands of a WordPress expert, the rewrite API is one hell of a tool to wield. It’s not Thor’s hammer (wouldn’t that be sweet!?), but it’s still powerful. You can use it to shape WordPress so that it can handle any URL it receives.

Everyone praises custom post types for allowing you to build anything with WordPress. Well, the rewrite API is one of the reasons why.