I'm trying to dynamically add a rewrite rule that allows one to access any page under an alternate permalink structure of my choosing.

So for example, if the alternate permalink structure includes the slug foo/ in the first postion, I want to add a rewrite rule such that:

example.com/foo/my-page rewrites to example.com/my-page

example.com/foo/my-cool/blog/post rewrites to example.com/my-cool/blog/post

So basically, no matter whether the query is for a page, a post, or any other type of custom content, I want this content to be accessible under the /foo/ slug as well as the original non-foo URL structure.

I have written a working function that accomplishes this by..

  1. reading the URI
  2. removing the slug from he URI
  3. looping through all the existing rewrite rules
  4. matching the URI with the slug removed to an existing rewrite rule
  5. grabbing the matching groups for matched the rewrite rule
  6. compiling a new rewrite rule with the slugged URI as the source and the matched rewrite rule endpoint with matching groups replaced with their values as the destination.

Here is the code:

// functions.php

add_filter('rewrite_rules_array', function($rules) {

    $slug = 'foo';

    // get the request URI
    $uri = $_SERVER['REQUEST_URI'];
    // determine whether URI has slug in the first position
    if(preg_match('/^(\/' . $slug . ')/', $uri)){

        // get the base URI by removing the slug
        $base_uri = str_replace('/'.$slug,'',$uri);

        // loop through existing rewrite rules
        foreach($rules as $src => $dest){

            // find the rewrite rule for which the base URI would have matched
            $regex_to_match = '/' . str_replace('/','\/',$src) . '/';
            preg_match_all($regex_to_match, $base_uri, $matches, PREG_SET_ORDER);

            if(count($matches) > 0){

                // get the specific matching groups
                $matches = $matches[0]; 

                // compile valid regex from URI with slug to create new rewrite source
                $new_src = ($uri[0] == '/' ? substr($uri, 1) : $uri) . '?$';
                // replace match variables with their string values to create new rewrite destination
                for($i=1; $i<count($matches)+1; $i++){
                    $replacement = isset($matches[$i]) ? $matches[$i] : '';
                    $dest = str_replace('$matches[' . $i . ']', $replacement, $dest);
                $new_dest = $dest;

                // add new rewrite rule to $wp_rewrite rules array
                $rules[$new_src] = $new_dest;

                // add the write rule


    return $rules;



This works but only for URLs with multiple slugs.

example.com/foo/my-page/123 rewrites correctly!

example.com/foo/abc/defg/bar rewrites correctly!

example.com/foo/abc DOES NOT rewrite correctly. Instead it redirects to example.com/abc

same for example.com/foo/my-page -- it redirects to example.com/my-page

  • I don't think you need to return anything since it's an action not a filter. (And the example here looks wrong to me too: if it were a filter you'd want to return $rewrite for the next filter anyway.)
    – Rup
    2 days ago
  • I'd start with the routing code in WP::parse_request(). There really isn't a lot to hook there though except maybe option_rewrite_rules, to filter the value WP_Rewrite::wp_rewrite_rules() reads from the database, and I don't like that.
    – Rup
    2 days ago

I wouldn't hook into generate_rewrite_rules for this. You can just use the regular rewrite rules API.

Tested the below, and it works for redirecting http://example.com/foo/test/123 to https://example.com/test/123. Can customize it to your needs. Drop it in functions.php or a functionality plugin.

add_action( 'init',  function() {
    add_rewrite_rule( 'foo/([a-z0-9-/]+)[/]?$', 'index.php?foo_redirect_to=$matches[1]', 'top' );
} );

add_filter( 'query_vars', function( $query_vars ) {
    $query_vars[] = 'foo_redirect_to';
    return $query_vars;
} );

add_action('template_redirect', function() {
    if ( ( $redirect_to = get_query_var( 'foo_redirect_to' ) ) == false ) {

  • but this must be a URL rewrite, not a redirect. A redirect would return a 301 for the original "foo" endpoint and forward the request to the non-foo page. I need example.com/foo/test/123 to return 200 and load the content the content as if the request was exmaple.com/test/123. In other words, "example.com/foo/test/123" remains in the URL bar, returns 200, and the content shown on the page is that of example.com/test/123.
    – yevg
    11 hours ago
  • I have updated my code and issue in the original post to an almost working solution. If you can help me debug the problem stated in the updated question, this will take the bounty
    – yevg
    11 hours ago

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.