Order of rewrite rules when calling add_rewrite_rule

Is that correct?

It’s correct that rules with a $after value of top (or anything other than bottom) will be placed above all other rules (that have been registered by the time your code runs).

However, the rules would actually be in the same order add_rewrite_rule() is called, so as for the ones in question, you would see them in the following order in the generated rewrite rules (that are saved in the database, although could also be written to the .htaccess file), hence rule1 has the highest priority:

/rule1/
/rule2/
/rule3/

And that’s because “top” and “bottom” rules (which are both extra/custom rules) are grouped and merged using array_merge() like so: (see WP_Rewrite::add_rule() on Trac)

if ( 'bottom' === $after ) {
    $this->extra_rules = array_merge( $this->extra_rules, array( $regex => $query ) );
} else {
    $this->extra_rules_top = array_merge( $this->extra_rules_top, array( $regex => $query ) );
}

PS: add_rewrite_rule() calls WP_Rewrite::add_rule().

Example 1

  • PHP:

    add_action( 'init', function () {
        add_rewrite_rule( '^rule1/?$', 'index.php?pagename=foo&bar=rule1', 'top' );
        add_rewrite_rule( '^rule2/?$', 'index.php?pagename=foo&bar=rule2', 'top' );
        add_rewrite_rule( '^rule3/?$', 'index.php?pagename=foo&bar=rule3', 'top' );
    } );
    
    add_action( 'init', function () {
        add_rewrite_rule( '^rule1b/?$', 'index.php?pagename=foo&bar=rule1b', 'bottom' );
        add_rewrite_rule( '^rule2b/?$', 'index.php?pagename=foo&bar=rule2b', 'bottom' );
    } );
    
  • A var_dump() of the generated/saved rules:

    array(101) {
      ["^wp-json/?$"]=>
      string(22) "index.php?rest_route=/"
      ... other core REST API rules.
      ["^wp-sitemap\.xml$"]=>
      string(23) "index.php?sitemap=index"
      ... other core sitemaps rules.
      ["^rule1/?$"]=>
      string(32) "index.php?pagename=foo&bar=rule1"
      ["^rule2/?$"]=>
      string(32) "index.php?pagename=foo&bar=rule2"
      ["^rule3/?$"]=>
      string(32) "index.php?pagename=foo&bar=rule3"
      ... category, tag, etc. rules.
      ["(.?.+?)(?:/([0-9]+))?/?$"]=>
      string(29) "index.php?pagename=$1&page=$2"
      ["^rule1b/?$"]=>
      string(33) "index.php?pagename=foo&bar=rule1b"
      ["^rule2b/?$"]=>
      string(33) "index.php?pagename=foo&bar=rule2b"
    }
    

Example 2

  • PHP:

    add_action( 'init', function () {
        add_rewrite_rule( '^rule1/?$', 'index.php?pagename=foo&bar=rule1', 'bottom' );
        add_rewrite_rule( '^rule2/?$', 'index.php?pagename=foo&bar=rule2', 'bottom' );
        add_rewrite_rule( '^rule3/?$', 'index.php?pagename=foo&bar=rule3', 'bottom' );
    } );
    
    add_action( 'init', function () {
        add_rewrite_rule( '^rule1b/?$', 'index.php?pagename=foo&bar=rule1b', 'top' );
        add_rewrite_rule( '^rule2b/?$', 'index.php?pagename=foo&bar=rule2b', 'top' );
    } );
    
  • A var_dump() of the generated/saved rules:

    array(101) {
      ["^wp-json/?$"]=>
      string(22) "index.php?rest_route=/"
      ... other core REST API rules.
      ["^wp-sitemap\.xml$"]=>
      string(23) "index.php?sitemap=index"
      ... other core sitemaps rules.
      ["^rule1b/?$"]=>
      string(33) "index.php?pagename=foo&bar=rule1b"
      ["^rule2b/?$"]=>
      string(33) "index.php?pagename=foo&bar=rule2b"
      ... category, tag, etc. rules.
      ["(.?.+?)(?:/([0-9]+))?/?$"]=>
      string(29) "index.php?pagename=$1&page=$2"
      ["^rule1/?$"]=>
      string(32) "index.php?pagename=foo&bar=rule1"
      ["^rule2/?$"]=>
      string(32) "index.php?pagename=foo&bar=rule2"
      ["^rule3/?$"]=>
      string(32) "index.php?pagename=foo&bar=rule3"
    }
    

So as you could see, in both examples, rule1 is always before rule2 and rule3, and rule1b is also always before rule2b.