Rewrite rules are how WordPress creates clean / pretty URIs from URL query parameters. When your new page or blog post automatically gets a human-friendly URL, this is provided by a rewrite rule, which itself is using WordPress’s Rewrite API.

In this post I hope to cover the basics of the Rewrite API, create a few new rewrite rules as examples, and make use of the data the Rewrite API provides the global WP_Query object when it matches a rewrite tag. Let’s get started!

How does URL rewriting work?

You’ve probably heard of “redirects” before. This is when one URL sends you to another URL. In your browser, the URL in the location bar will actually change to reflect the new URL you have been redirected to.

Rewrites are similar to redirects, but they are done behind the scenes so that the URL in your browser’s location bar does not change. This should become more clear by examining how you can configure your web server to handle rewrites.

The web server does the rewrite

For rewrites to work in any system, the web server must be configured correctly. For Apache web servers, this is most-easily achieved in the form of an .htaccess file. This file tells Apache to take the requested URL and serve it as a URI to the index.php file.

Let’s see what this looks like:

First, the configuration is checking to make sure the Apache rewrite module is enabled. Then, it makes sure that the requested URL is not a real file or directory within the codebase. And finally, it rewrites the requested URI to the index.php file.

Example: If you were to visit, behind the scenes Apache would deliver that URI to the index.php file, resulting in (behind the scenes).

For all intents and purposes, this is now acting as a Front Controller. Meaning that you now have a single point of entry for requests into your application. That single point of entry is your index.php file.

index.php handles the request

Now that your requests are all going through index.php, you can write a script within that file to look for the request, and handle it appropriately. Here is a very simple example of how that might be done using a pattern matching strategy similar to that you’ll find in WordPress:

Note that the global $_SERVER variable contains a key called ‘REQUEST_URI’. This is how you get the URI that was requested by the visitor: $_SERVER['REQUEST_URI']. Simple huh?

Next, this script attempts to match the requested URI against a pattern of post/(some value), and if it is found, returns a query-like string with the found key-value pair.

For example: If we were to visit, the pattern would find a match and return a string of post=1234.

Then the script parses that query-like string into an array named $query_vars.

And finally, if the ‘post’ key is found in $query_vars, the script alters the response accordingly.

Rewrites in WordPress

These examples should not be used as-is. They are purely for illustrative purposes.

The WordPress rewrite mechanisms work in much the same way. They provide an API through the use of simple functions, and those functions tend to expect either a regex (regular expression) pattern, rewrite string, or both. Let’s take a look at 3 of those API functions.

Function add_rewrite_rule

Adding rewrite rules requires both a regex pattern, and resulting rewrite string. Just like our above example, WordPress will use the regex pattern to attempt to match the requested URI; and when a match is found it will convert the found values into the defined rewrite string.

Let’s look at a simple example:

Using the above example along with a real post slug from your site, you could visit the URL and see the content from the default “Hello World” post that comes with a fresh WordPress install.

It achieves this by rewriting the data found in the url after /post-by-slug/ to the core “name” rewrite tag, so that post-by-slug/hello-world is rewritten to index.php?name=hello-world. Later in the page load, when WordPress finds that the “name” query variable has a value, core mechanisms take over and serve the post associated with that name (aka, post_name or slug).

Function add_rewrite_tag

Adding a rewrite tag allows you to provide custom variables to the global $wp_query object. When you add a rewrite tag to WordPress, you are informing the system that this variable is to be expected, and available through the common query API functions such as get_query_var('some-tag'), or directly from the object $wp_query->query_vars['some-tag'].

The main reason to use a rewrite tag is to allow a URL variable to be stored in the global $wp_query object. This lets you access it from any part of the site using get_query_var().

One example use case might be that of tracking visits to your site. For example, if you wanted to track affiliate links to products on your site:

In this example a cookie will be set for any user who visits a url with an ?affiliate= query.

The reason to use a rewrite_tag alone is that it does not impose a structure on the URL. It only tells WP_Query to expect a variable of the given name.

Next, let’s combine the use of add_rewrite_rule and add_rewrite_tag to a new reliable application route!

URL Longerer

You may have heard of URL shorteners before. They’re great because they make URLs easier to share online through sites and services that have strict character limits.

But have you ever considered a URL lengthener? I know I haven’t. But none-the-less, I find great joy in creating impractical examples, so here we are.

In this example we create a new rewrite rule and rewrite tag.

The rewrite rule tells WordPress, “Expect a URI that starts with longerer/, and rewrite it to the URL variable named longerer.”

The rewrite tag tells WordPress, “Keep a lookout for a URL variable named longerer. If you find it, please put it along with its value into the global $wp_query.”

Then using the template_redirect hook, we look for our new tag as a query_var (get_query_var( 'longerer' )), decode it to find the post ID. If the post ID is legitimate, we redirect to that post; otherwise redirect to the home page.

Function add_rewrite_endpoint

An “endpoint” is a partial URI that can be appended to another URI to produce an alternate result or provide additional data. For example, if you had a post located at and wanted to allow other parties to retrieve that post data as json, you could create an endpoint located at and return json when that endpoint is visited.

Adding endpoints to your WordPress install might be the easiest way to use the Rewrite API, as it doesn’t require any regex or fancy formatting.

Endpoints can also receive values appended to them in the form of another path argument in the URL. Let’s create a few endpoints.

Debug endpoint

This snippet provides an endpoint named “debug” that expects a value of “post” or “query”, and shows debugging information about the global $post or $wp_query objects accordingly.

Note that I have used the E_ALL bitmask because I want this endpoint to be available on all pages of my WordPress site.

JSON endpoint for posts and pages

Considering the previously mentioned example of a JSON endpoint, let’s see what this would look like in WordPress:

This is as simple as it gets. We use add_rewrite_endpoint to create a new expectation for an endpoint named “json”, and we tell it to only work on permalinks (individual posts) and pages.

Using the template_include hook, we look to see if our endpoint is being accessed, and return the $post object as json to the visitor.

To see the available EP_* masks, see the add_rewrite_endpoint codex page.

Rewrite Tag for Permalinks

This final example is to show how you can use a rewrite tag to provide a new permalink structure for your site. Similar to the URL Longerer example, this uses both a rewrite rule and a rewrite tag to set expectations for both URIs and query variables.

Additionally this task will need to do a few other things, let’s take a look:

Using both the post_link and post_type_link hooks, it alters permalinks as they are presented on the site by replacing our rewrite tag with the value found in the query variables. It also uses pre_get_posts to provide a fallback mechanism for any post that does not have a meta data with the key custom_folder.

I am not really a big fan of this example because it seems a bit hacky for my tastes. Ideally, if you need to provide a new permalink structure using a custom rewrite tag, you provide it for a piece of data you can reliably expect each post to have.

Refreshing rewrite rules

It’s important to note that WordPress caches rewrite rules. This means that when you change your plugin’s rewrite code, you probably need to refresh that cache to pick up your changes.

One way to do that is to visit your site’s Dashboard > Settings > Permalinks page, and “Save Changes”.

Programmatically, you can use the flush_rewrite_rules function. If you’re writing a plugin that implements the Rewrite API, chances are you want to flush rewrite rules on plugin activation.

One last thing

I’d like to reiterate again that these examples are not complete and should not be used as-is. That being said, I hope you learned something useful; and if you see any mistakes in this post, please let me know in the comments below.

Happy rewriting!

About the Author

Jonathan Daggerhart

Long time Drupal and WordPress developer. I like to write modules and plugins, and I dabble in frontend and design.

3 thoughts on “WordPress Rewrite API Examples

  • Michael Hull

    Thanks for the post, and for the talk you put together on this. I just had a cool use case come up and I came straight here to revisit what you presented.

    One goal I have on a particular site is to create a kind of “combo page,” where we have a Therapy (post type) that is paired with an Ailment (another post type). And on this combo page, it’s supposed to show some bbPress topics that are associated with both the Therapy and the Ailment.

    Your sharing about the Rewrite API opened up my eyes to the fact that I don’t have to rely on post types/taxonomies to create my URL’s for me all the time. So…thank you!

    I’m now able to have URL’s that look really nice, like /acupuncture/for/headaches. Pretty cool!!

Leave a Reply

Your email address will not be published. Required fields are marked *