Wordpress - Understanding add_rewrite_rule

A basic rule that would work for your example:

function wpd_foo_rewrite_rule() {
    add_rewrite_rule(
        '^foo/([^/]*)/?',
        'index.php?pagename=$matches[1]&param=foo',
        'top'
    );
}
add_action( 'init', 'wpd_foo_rewrite_rule' );

This takes whatever comes after foo/ and sets that as pagename for the query, and then param gets the static value foo. If you need different URL patterns, you'll need extra rules for each unique pattern. Refer to WP_Query docs for the various query vars that can be set within rewrite rules. Don't forget to flush rewrite rules after adding new ones. This can be done by visiting the Permalinks Settings page.

Now visiting your example URL:

http://domain.com/foo/my_page

will load the correct page, but it's not going to behave exactly like visiting:

http://domain.com/my_page?param=foo

because when using internal rewrites, param is set within the $wp_query query object, not the $_GET superglobal. If you need to work with code that's looking for a value in $_GET, you'll need an extra step to set that value:

function wpd_foo_get_param() {
    if( false !== get_query_var( 'param' ) ){
        $_GET['param'] = get_query_var( 'param' );
    }
}
add_action( 'parse_query', 'wpd_foo_get_param' );

Another method to consider is using endpoints, so /foo/ would be on the end of URLs rather than as a prefix. The advantage to this is that the API's add_rewrite_endpoint simplifies adding all of the rules you need, including enabling pagination.


Ok, I've gotten working examples for all 3 types of requests. It took a ton of experimenting and messing around in order to get them working. I guess Milo is good at nudging people into answering their own questions.

After countless changes and refreshing the permalinks I realized it was much easier to figure out the urls outside of the add_rewrite_url and once they worked then define the rewrite. Example being index.php?param=foo&post_type=example_type.

Another obvious thing, but adding it here so it might help someone else. You must define the custom post type add_rewrite_rule rules BEFORE you define your page/sub-page wildcard rules. I wasted quite a bit of time with that one and think it's the main thing that was causing me to not understand why the rules didn't work.

Here are the 3 rules which work across all my needs. The Page/Sub-Page rule was combined into a single one.

// Custom Post Archive
add_rewrite_rule(
    '^foo/example_type/?$',
    'index.php?param=foo&post_type=example_type',
    'top'
    );

// Custom Post Individual
add_rewrite_rule(
    '^foo/example_type/([^/]*)/?$',
    'index.php?param=foo&example_type=$matches[1]',
    'top'
    );

// Pages, Top-Level and Sub-Pages
// This MUST be placed in the code AFTER custom post add_rewrite_rule
add_rewrite_rule(
    '^foo/(.+)/?$',    
    'index.php?param=foo&pagename=$matches[1]',
    'top'
    );

Additionally what I've done is set up a loop to add multiple custom post type rules. Remember, you must define the custom post type add_rewrite_rule rules BEFORE you define your page/sub-page wildcard rules.

$custom_types = array('example_type', 'projects', 'people');

foreach($custom_types as $type) {

    // Custom Post Archive
    add_rewrite_rule(
        '^foo/'.$type.'/?$',
        'index.php?param=foo&post_type='.$type,
        'top'
        );

    // Custom Post Individual
    add_rewrite_rule(
        '^foo/'.$type.'/([^/]*)/?$',
        'index.php?param=foo&'.$type.'=$matches[1]',
        'top'
        );

}

The Rewrite Analyzer which Milo passed along was quite helpful when trying to better understand how Wordpress queries for pages/posts.