Go to home page
Get a quotation

Backtobasics Blog


Permanent Custom Rewrite Rules In WordPress

Posted on: July 4th, 2018

Here at Backtobasics we code a lot of websites using WordPress. Of course, ultimately we will develop a website that requires custom rewrite rules, especially for custom posts. However, managing these rewrite rules can prove troublesome. In this article, I am going to present a means of properly managing custom rewrite rules that will remain permanently set for your website, even when an administrator saves the permalinks setting.

WordPress provides some relatively good support for rewrite rules that allows you to handle the custom URLs within WordPress. This is important, because you cannot always be sure which platform the website will be deployed on, causing rewrite rules coded in Apache’s htaccess to stop working when the website is  deployed on nginx. Or worse, your hosting service does not allow any rewrite rules.

WordPress provides the very useful function add_rewrite_rule() to provide the developer with a way to add an internal rewrite rule to handle URLs required by your website. However, the implementation leaves a bit to be desired.

First, if you add a rewrite rule using add_rewrite_rule, the rule lives only for this invocation of WordPress. That is not the worst thing, but it means that your code must run with every page request, and any additional code is a performance hit. Especially if your code is complicated.

To solve this problem, you can call flush_rewrite_rules(), which will cause WordPress to save your rewrite rule to the database. However, you definitely do not want to call flush_rewrite_rules() on every page load. It is a very heavyweight function, as it must re-save all of the rewrite rules to the database. So most developers will add this code into their functions.php, invoke it once, then comment it out so it does not run on every page load.

While this is an improvement, it inevitably causes a problem. At some point in the future, either the website admin or another developer is going to go to the Settings -> Permalinks page, and save the Permalink settings. When this occurs, WordPress will save the rewrite rules to the database – without your custom rewrite rule!

This is a serous problem for websites defining rewrite_rules, and the cause of many unexplained problems when critical pages disappear from a website because their rewrite rules have disappeared and those pages no longer display.

After running into this problem many times, I finally decided to sit down and solve it once and for all.

What had hoped to find a WordPress hook for flush_rewrite_rules(). This would have been perfect, since saving the permalinks calls this function, we could hook into the call and re-save our rewrite rules anytime the permalinks were saved. Unfortunately, WordPress does not provide any hooks that allow us to run after permalinks are saved. This seems like an oversight by the WordPress developers, and we hope that someday it is added.

Since there is no permalink save hook, we had to dig deeper into the WordPress code. There, we found that the WordPress code calls the filter rewrite_rules_array() anytime it modifies it’s rewrite rules, specifically when the user saves the Permalink settings. Thus, the rewrite_rules_array() filter gives us just the opportunity we need to ensure our rewrite rules are not erased.

On one of our websites, we wanted to present a “portfolio” page. For this page, we want to be able to specify a “filter”. In other words, we wanted to present a subset of the items in the portfolio, depending on the selected filter. This is easily accomplished with a url that looks like this:

/portfolio?filter=latest

However, that is not a a pretty url, and it will likely not be cached for better performance (due to the query parameter), and it is not SEO friendly.

Instead, what we want is a url that looks like this:

/portfolio/filter

For this, we need a rewrite rule that looks like this:

portfolio/([^/]+)/?

The matched element ([^/]+) will be our filter name.

In our functions.php, we added the filter as follows:

add_filter( 'rewrite_rules_array', 'bk_rewrite_rules_array', 10, 1 );

We then implemented our filter with a function that looks like this:

function bk_rewrite_rules_array( $rules ) {
   $have_rule = false;

   foreach ( $rules as $rule => $rewrite ) {
      if ( preg_match( '/^portfolio\/\(\[\^\/\]\+\)\/\?/',$rule ) ) {
         $have_rule = true;
      }
   }

   if ( ! $have_rule ) {
      $rules = array_merge(
         array( 'portfolio/([^/]+)/?' => 'index.php?pagename=portfolio&filter=$matches[1]' ),
         $rules
      );
   }

   return $rules;
}

Notice that we need to be sure that we are not repeatedly adding our rewrite rule if it is already in the rules array. This is why we use the loop and preg_match() to set our $have_rule flag. Also note how we need to escape regex special characters in our call to preg_match(). That regular expression is the escaped version of the regular expression merged into the $rules array.

Be sure that you merge your rule into the rules array so that it is at the beginning of the rules array. Otherwise, urls are likely to match other WordPress rules before matching yours.

With this configuration, our rewrite rule will be added to the WordPress rewrite rules anytime that rewrite_rules_array filter is called, which will happen when the user saves the WordPress permalinks.

Any developer who is dealing with rewrite rules should definitely consider adding the Monkeyman Rewrite Analyzer plugin during development. This plugin has saved me hours of time and I want to thank Jan Fabry for this wonderful piece of work.