This website uses cookies to ensure you get the best possible experience. See our Cookies Policy.

PMG Digital Made for Humans

A (Mostly) Complete Guide to the WordPress Rewrite API

8 MINUTE READ | February 13, 2012

A (Mostly) Complete Guide to the WordPress Rewrite API

WordPress Rewrite API

Last updated: November 2021

The WordPress Rewrite API is like a mythical creature. Not many know it exists, and few still know how to tame it. In this article, we’ll cover the basics of the Rewrite API and how to make it work for you.

The Rewrite API includes three commonly used functions (and a fourth function that doesn’t get much love).

How WordPress Permalinks Work

When you request a page on a WordPress site, assuming it’s using pretty permalinks, the rules in htaccess file (or webconfig, if you’re into windows) first test to see if the request file and or directory exists on the server. In other words, if someone requests yoursite.com/some-folder/ and you’ve created some-folder in the root of your site (via FTP or whatever else), Apache will serve that directory. Same deal for static files. NginX works in a similar way, but does so with a try_files directive: try_files $uri $uri/ index.php.

If the folder or file doesn’t exist, the server directs the request to index.php, which is, of course, where the magic happens. From here WordPress loads. During this process, WordPress tries to match the request’s URL (the stuff after yourdomain.com) with a series of rewrite rules, which are just regular expressions. If it finds a match, WP will translate the URI into a database query, render the correct template file and serve up the page.

When you add a rewrite rule, you’re augmenting the built-in rewrite rules with your own. There’s all kinds of stuff you can do with this. Like creating custom shortlinks or giving your WordPress site an API.

Adding a Rewrite Rule

add_rewrite_rule is the thing to add your own custom rules. It tags three arguments: the regex, the index.php + query string URL you want to rewrite the regex matching URI to, and the priority. Generally $priority is always set to “top” to push your rewrite rules before the built-in WP rules.

Here’s an example. We’re going to rewrite yoursite.com/p/some_number to a post that has the ID, some_number. We’ll hook into

init
to add the rule.

<?php
add_action( 'init', 'pmg_rewrite_add_rewrites' );
function pmg_rewrite_add_rewrites()
{
    add_rewrite_rule(
        '^p/(d+)/?

The first argument says, “match p followed by a slash, then a series of one or more numbers (and maybe another slash).” The second argument tells WordPress to rewrite the rule to index.php?p=the_group_of_numbers. $matches[1] refers to the first group of parentheses inside the rewrite regex (the first argument).

Before this will work for you, however, you’ll need to flush your rewrite rules. You can do this pro grammatically, with an activation hook in a plugin, or manually by visiting the Settings > Permalinks page in your WordPress admin and hitting save.

Here’s how to do it with an activation hook:

<?php
register_activation_hook( __FILE__, 'pmg_rewrite_activation' );
function pmg_rewrite_activation()
{
    pmg_rewrite_add_rewrites();
    flush_rewrite_rules();
}

The reason the above rewrite rule works is because WordPress recognizes what the p query string key means — it knows to make it to a post id. What if you want to create something like a URL endpoint for a form submission? None of the built-in query variables would do for this, so we need to roll our own. Here’s the rewrite rule:

<?php
add_rewrite_rule(
    '^submit/?

If you flush your rewrite rules and visit yoursite.com/submit/, it’s going to show the blog/index page. WordPress doesn’t know what to do with the form query variable. And if you try to grab the variable with get_query_var, it’s not going to work. Why? WP didn’t recognize the form variable, so it stripped it out.

There’s one of two ways you can add a query variable. We’ll cover one here, and one in the next section of this article. To add the variable you add a filter to query_vars and push form onto the query variable array.

<?php
add_filter( 'query_vars', 'pmg_rewrite_add_var' );
function pmg_rewrite_add_var( $vars )
{
    $vars[] = 'form';
    return $vars;
}

Catching Custom Query Variables

You can get a query variable with the get_query_var function. Like this.

<?php
if( get_query_var( 'form' ) )
{
    // do stuff
}

get_query_var can be used any time after the query is set up — any time after init. It’s safe to use inside a template, in other words. This is a fairly common practice for me: hook into template redirect, catch a custom query variable, do stuff inside the WordPress environment, then exit(); to stop the theme from loading.

<?php
add_action( 'template_redirect', 'pmg_rewrite_catch_form' );
function pmg_rewrite_catch_form()
{
    if( get_query_var( 'form' ) )
    {
        // do stuff
        exit();
    }
}

Adding Rewrite Tags

add_rewrite_tag lets you add custom tags similar to %postname% or any of the permalink structure tags. It can be used in place of filtering query_vars.

In other words, instead of this:

<?php
add_rewrite_tag( '%form%', '[^/]' );
add_rewrite_rule(
    '^submit/?

Is a complete unit. No need to filter query_vars to make sure WordPress recognizes the form variable. That said, is this the right approach? Rewrite tags are more helpful if you’re going to use them as part of, say, a custom permlink structure for a custom post type. In our example, they probably aren’t necessary. add_rewrite_tag is also misleading. It sort of implies that, with registration, you somehow tell WordPress to know how to replace the tag with the correct value. WP does not do that. You’ll have to do that manually, but it is possible.

Adding Rewrite Endpoints

Rewrite endpoints are things that get tacked onto the end of URLs on your site. Trackbacks are a good example: any post on a WordPress side has the endpoint /trackback/ that alters the behavior of the page to, you guessed it, add pingbacks/trackbacks.

We’ll add an endpoint to posts to create an “API”. Whenever you visit yoursite.com/some-permalink/json/ WP will spit out a nice JSON version of the post. add_rewrite_endpoint tags two arguments: (1) the endpoint itself and (2) where you want the endpoint to live ($place). $place is going to be one of the many EP_* constants (view them here). If you want to place an endpoint in more than one place, you can combine EP constants with the bitwise OR (the pipe: |)

Like our other rewrites, we’ll hook into init to add our endpoint.

<?php
add_action( 'init', 'pmg_rewrite_add_rewrites' );
function pmg_rewrite_add_rewrites()
{    
    add_rewrite_endpoint( 'json', EP_PERMALINK );
}

add_rewrite_endpoint takes care of adding the rewrite rules and adding the query variable for us! Here’s the code we’ll use to catch our /json/ endpoint.

<?php
add_action( 'template_redirect', 'pmg_rewrite_catch_form' );
function pmg_rewrite_catch_form()
{
    if( is_singular() && get_query_var( 'json' ) )
    {
        exit();
    }
}

If you visit yoursite.com/some-permalink/json/ (after flushing rewrite rules, of course) it’s not going to work, but yoursite.com/some-permalink/json/asdf will! That’s because add_rewrite_endpoint is going to set the JSON query variable equal to the stuff that comes after the /json/ endpoint. If it’s an empty string (as it is with yoursite.com/some-permalink/json/) php will evaluate it as false. if( is_singular() && get_query_var( ‘json’ ) !== false ) won’t work either, as it will stop all permalinks from loading. Instead, we need to hook into request and give our json query variable a value if it’s set.

<?php
add_filter( 'request', 'pmg_rewrite_filter_request' );
function pmg_rewrite_filter_request( $vars )
{
    if( isset( $vars['json'] ) ) $vars['json'] = true;
    return $vars;
}

Now if you visit yoursite.com/some-permalink/json/, it will work. Next up we just need to enhance our handler function (hooked into template_redirect a bit).

<?php
add_action( 'template_redirect', 'pmg_rewrite_catch_json' );
function pmg_rewrite_catch_json()
{
    if( is_singular() && get_query_var( 'json' ) )
    {
        $post = get_queried_object();
        $out = array(
            'title'     => $post->post_title,
            'content'   => $post->post_content
        );
        header('Content-Type: text/plain');
        echo json_encode( $out );
        exit();
    }
}

Now if you visit yoursite.com/some-permalink/json/ a nice JSON will be there for consumption.

Adding Custom Feeds

The little-used (or talked about) add_feed function is used to — well, to add feeds to WordPress. Like yoursite.com/feed/rss. add_feed takes to arguments: the feed name and the function you wish to fire when the feed is loaded. Like our other rewrite, writes, we’ll hook into init. Since we’re into JSON in this article, let’s make a feed that displays our list of posts as a JSON string.

<?php
add_action( 'init', 'pmg_rewrite_add_rewrites' );
function pmg_rewrite_add_rewrites()
{    
    add_feed( 'json', 'pmg_rewrite_json_feed' );
}

The function pmg_rewrite_json_feed is going to receive one argument: whether or not this is a comments feed. Since we don’t really need this, we’ll leave it out. Our hooked function is what you’d expect: get some posts, turn them into a JSON.

<?php
function pmg_rewrite_json_feed()
{
    $posts = get_posts();
    $out = array();
    foreach( $posts as $p )
    {
        $out[] = array(
            'title' => $p->post_title,
            'content' => $p->post_content
        );
    }
    header('Content-Type: text/plain');
    echo json_encode( $out );
}

Demystifying the WordPress Rewrite API

Insights meet inbox

Sign up for weekly articles & resources.

Hopefully this article cleared some things up for you with regard to the rewrite API. It’s very powerful and allows you to use WordPress in ways that are well outside its usual scope. All of this code is available here.


Posted by Christopher Davis

Related Content

thumbnail image

Get Informed

PMG Innovation Challenge Inspires New Alli Technology Solutions

4 MINUTES READ | November 2, 2021

Get Informed

Applying Function Options to Domain Entities in Go

11 MINUTES READ | October 21, 2019

thumbnail image

Get Informed

My Experience Teaching Through Jupyter Notebooks

4 MINUTES READ | September 21, 2019

Get Informed

Trading Symfony’s Form Component for Data Transfer Objects

8 MINUTES READ | September 3, 2019

Get Inspired

Working with an Automation Mindset

5 MINUTES READ | August 22, 2019

Get Informed

Parsing Redshift Logs to Understand Data Usage

7 MINUTES READ | May 6, 2019

Get Inspired

3 Tips for Showing Value in the Tech You Build

5 MINUTES READ | April 24, 2019

thumbnail image

Get Informed

Testing React

13 MINUTES READ | March 12, 2019

Get Inspired

Tips for Designing & Testing Software Without a UX Specialist

4 MINUTES READ | March 6, 2019

Get Informed

A Beginner’s Experience with Terraform

4 MINUTES READ | December 20, 2018

BACK TO ALL POST