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 example.com/some-pretty-url
, behind the scenes Apache would deliver that URI to the index.php file, resulting in example.com/index.php/some-pretty-url
(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 example.com/post/1234
, 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
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 example.com/post-by-slug/hello-world
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 example.com/post/hello-world
and wanted to allow other parties to retrieve that post data as json, you could create an endpoint located at example.com/post/hello-world/json
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 EP_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!
References
- Gist for all these examples
- A (Mostly) Complete Guide to the WordPress Rewrite API
- add_rewrite_endpoint – codex (includes list of bitwise masks)
- flush_rewrite_rules – How to refresh rewrite rules programmatically
- Helper Class To Add Custom Taxonomy To Post Permalinks
- Query Monitor – a plugin for debugging all things query related
- Rewrite API codex
- WordPress for the Adventurous: Rewrite API
Discussion
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!!
Thats a very cool use-case! Thanks for sharing it. You’ll have to send me a link when it’s ready ?
Thanks
In your Endpoint section, you mention
E_ALL
where I think you meantEP_ALL
.Good catch, I’ve updated the post. Thanks!
thanks for this post!
i would like to avoid using WPML and do my own simple multilanguage solution.
so i need to figure out the language from a url like that: /en/path/to/my/page or /de/path/to/my/page
so if i understand this correctly i simply could use your url longerer example and then use get_page_by_path() instead of your url_longerer_decode_in_some_way(), right?
and the regex would have to be changed from:
add_rewrite_rule( ‘^longerer/(.*)/?’, ‘index.php?longerer=$matches[1]’, ‘top’ );
to:
add_rewrite_rule( ‘^en/(.*)?’, ‘index.php?en=$matches[1]’, ‘top’ );
or something like that?
Hi Horace! Appreciate it was 2019 but where did you end up with this? I am doing something similar. Did you modify the query in pre_get_posts to grab the actual page you wanted?
Need Some help to generate SEO friendly url of my custom post
I have implement the following code. Please check it
http://localhost/wptest/news/economic-development/testing-news-content
I have created link like this
I am adding this code but the URL is not rewriting. what should I do I have seen the exact code here and implemented it following these steps https://www.wpblog.com/wordpress-url-rewrite-for-custom-page-template/