How To Add Custom Rewrite Rules In WordPress

How To Add Custom Rewrite Rules In WordPress

WordPress rewrite API used to convert URLs from something programmatically convenient to something user and search engine friendly. This article will give you some background information about wordpress URL rewriting principles and API.

WordPress URL Rewriting

After installing wordpress, It creates .htaccess file in its root directory and contain the following.

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

This .htaccess directive tells the webserver the following:

  • If a client requests index.php, Send him to this file and stop matching to other rewrite rules.
  • If a request is not to a file.
  • ..and if a request is not to a directory.
  • Then rewrite URL to index.php and don’t apply any other rules.

So a typical wordpress URL like example.com/page/2/ doesn’t match any actual path on the webserver but wordpress will read this URL as example.com/index.php?paged=2. Also when you visit example.com/hello-world/, WordPress will read this URL as example.com/index.php?p=1. How wordpress translates URLs into query variables! Let’s explore one of the trickest objects in wordpress.

The $wp_rewrite object contains a list of all registered rewrite rules and all informations related to permalink structures. Let’s dump this object to know what i mean.

object(WP_Rewrite)[84]
  public 'permalink_structure' => string '/%postname%/' (length=12)
  .......
  public 'rewritecode' => 
    array (size=14)
      0 => string '%year%' (length=6)
      1 => string '%monthnum%' (length=10)
      2 => string '%day%' (length=5)
      ....
  public 'rewritereplace' => 
    array (size=14)
      0 => string '([0-9]{4})' (length=10)
      1 => string '([0-9]{1,2})' (length=12)
      2 => string '([0-9]{1,2})' (length=12)
      ....
  public 'queryreplace' => 
    array (size=14)
      0 => string 'year=' (length=5)
      1 => string 'monthnum=' (length=9)
      2 => string 'day=' (length=4)
      ....

As you can see, It contains a list of all registered rewrite rules. Each rewrite rule has rewritecode, rewritereplace and queryreplace. For example when wordpress detect example.com/year/2014/ pattern, it changes it according to the replacement into example.com/index.php?year=2014. After wordpress treanslates the URL into query variables, It collects all these variables to form MySQL statement, get post data, load required theme and display the requested page.

WordPress Rewrite API

Imagine you need to integrate custom page in which you can list your products. How can we do this?..First we need to create a new rewrite rule so wordpress can translate example.com/products/2/ to example.com/index.php?pagename=products&product_id=2. We can use add_rewrite_rule() function which accepts 3 parameters. The first parameter is the URL pattern. The second parameter is the URL replacement and the third parameter is the priority. Let’s explore how to create this rule.

function products_plugin_activate() {
  products_plugin_rules();
  flush_rewrite_rules();
 }

 function products_plugin_deactivate() {
  flush_rewrite_rules();
 }

 function products_plugin_rules() {
  add_rewrite_rule('products/?([^/]*)', 'index.php?pagename=products&product_id=$matches[1]', 'top');
 }

 function products_plugin_query_vars($vars) {
  $vars[] = 'product_id';
  return $vars;
 }
 
 //register activation function
 register_activation_hook(__FILE__, 'products_plugin_activate');
 //register deactivation function
 register_deactivation_hook(__FILE__, 'products_plugin_deactivate');
 //add rewrite rules in case another plugin flushes rules
 add_action('init', 'products_plugin_rules');
 //add plugin query vars (product_id) to wordpress
 add_filter('query_vars', 'products_plugin_query_vars');

As you can see

  • I used flush_rewrite_rule() in plugin activation and deactivation. This makes wordpress refresh and rebuild the rewrite rules list.
  • I added rewrite rules in plugin activation.
  • Also i added the rewrite rules on init in case another pugin flushes rules.
  • I added product_id query variable to wordpress using query_vars filter hook.

Now wordpress will redirect all requests to example.com/products/ to example.com/index.php?pagename=products and request to example.com/products/value/ to example.com/index.php?pagename=products&product_id=value. Let’s integrate our custom pages with these query variables.

  function products_plugin_display() {
  $products_page = get_query_var('pagename');
  $product_id = get_query_var('product_id');
  if ('products' == $products_page && '' == $product_id):
   //show all products
   exit;
  elseif ('products' == $products_page && '' != $product_id):
   //show product page
   exit;
  endif;
 }
 
 //register plugin custom pages display
 add_filter('template_redirect', 'products_plugin_display');

As you can see, I used template_redirect action hook to send client to the custom page and don’t forget to use exit() to prevent wordpress from handling page display. Let’s review our functional plugin.

 /*
   Plugin Name: Products Plugin
   Plugin URI: http://clivern.com/
   Description: Register URL rules for our products
   Version: 1.0
   Author: Clivern
   Author URI: http://clivern.com
   License: MIT
  */
 function products_plugin_activate() {
  products_plugin_rules();
  flush_rewrite_rules();
 }

 function products_plugin_deactivate() {
  flush_rewrite_rules();
 }

 function products_plugin_rules() {
  add_rewrite_rule('products/?([^/]*)', 'index.php?pagename=products&product_id=$matches[1]', 'top');
 }

 function products_plugin_query_vars($vars) {
  $vars[] = 'product_id';
  return $vars;
 }

 function products_plugin_display() {
  $products_page = get_query_var('pagename');
  $product_id = get_query_var('product_id');
  if ('products' == $products_page && '' == $product_id):
   //show all products
   exit;
  elseif ('products' == $products_page && '' != $product_id):
   //show product page
   exit;
  endif;
 }

 //register activation function
 register_activation_hook(__FILE__, 'products_plugin_activate');
 //register deactivation function
 register_deactivation_hook(__FILE__, 'products_plugin_deactivate');
 //add rewrite rules in case another plugin flushes rules
 add_action('init', 'products_plugin_rules');
 //add plugin query vars (product_id) to wordpress
 add_filter('query_vars', 'products_plugin_query_vars');
 //register plugin custom pages display
 add_filter('template_redirect', 'products_plugin_display');

17 comments.

  1. Hi,

    Great post that covers two very difficult topics(WordPress rewrite functionality and custom URL rewrites). This solves a lot of my problems with WordPress. For those who heavily customize WP to be more than a blog, this is essential.

    One note I think will be helpful for others is my code wouldn’t work until I went into Permalinks and saved them. I only found this by reading thru http://codex.wordpress.org/Rewrite_API to make sure I wasn’t missing something. Not sure if it was overlooked in this post or there is another way around it, but this annoying step has bit me in the past.

    Anyhow, great post and now I can create custom rewrites the correct way without a plugin and .htaccess hacks.

  2. Thanks for the post.
    What if the page was a child page and had one or more parents?

    Using your example above….example.com/products/value/
    So if the URL was example.com/apple/products/value/
    With ‘apple’ being the parent page of ‘products’ how do you add a rewrite for this type of URL…or even if the page had more than one parent?
    Thanks again.

    • If you need to add static parent like apple. you should add rewrite rule like that add_rewrite_rule('apple/products/?([^/]*)', 'index.php?pagename=products&product_id=$matches[1]', 'top'); and the rest of the code will be the same. Then URLs that will be available example.com/apple/products/ and example.com/apple/products/$product_id$. What if you need to make this static parent dynamic, things will change so visit this gist https://gist.github.com/Clivern/2476ec77ad55248f445a. please always put in consideration that your plugin urls may conflict with other plugins so chose unique url parameters as possible. also don’t forget how url change with pretty links status example.com/?pagename=test&.... to example.com/test/../../..

      • I guess I am a little confused still in how to add a rewrite for a sub page with an unknown parent or parents.
        So for example if I have this url….
        example.com/apple/products/value

        What if ‘apple’ was unknown? Is there a way for some type of wildcard?
        add_rewrite_rule(‘WILDCARD/products/?([^/]*)’, ‘index.php?pagename=products&product_id=$matches[1]’, ‘top’);

        • you can use add_rewrite_rule(‘([^/]*)/products/?([^/]*)’, ‘index.php?pagename=products&company=$matches[1]&product_id=$matches[2]‘, ‘top’);. You can put wildcards at any place but always you should have one page to check if it exists so you can check for the rest unknowns. but placing wildcard at front of url and then products is ok but very different so I prefer placing known pages to the right and then wildcards.

  3. Great post and is exactly what I need… but I am confused by one thing…

    If:
    example.com/products/value/

    Rewrites to:
    example.com/index.php?pagename=products&product_id=value

    then why the need for the “products_plugin_display” function?

    If you have a custom page template for “products” isn’t that where you run code to decide what to do with the data?

    —————-

    In my case I have a custom page template which is displayed like this:
    example.com/results/?dob=1-14-1975
    — it works exactly as expected when viewing it like that…

    Now.. my goal is to have it display in the browser like:
    example.com/results/1-14-1975/

    It seems that everything in your plugin example would accomplish that without the need for the “products_plugin_display” function… but it doesn’t. What do I even put in the “products_plugin_display” function?

    I hope that my question makes sense.

    Thanks!!

    • products_plugin_display function used to check if your registered query args exist in url and if it found execute your code and then die because if you don’t place die at end; wordpress will keep looking
      for page to render. in your case you should replace all occurance of products with results and product_id with dob and it will work.

  4. Great post but I’m a little stuck. This variable is hanging me up I think…

    index.php?pagename=products

    Where is products located? I’m guessing this makes WP look for page-products.php in the theme root but I keep getting a 404 message.

    Any help is appreciated!

  5. Thanks for the great article, I’m a bit puzzled to get some custom Rewrites to work with me. so what I need to do is something like
    – domain.com/username/profile/update
    -domain.com/username/article/custom-post-title
    the second one is bit tough for me and how I make sure that no conflict between these as both are based on /username. Thanks in advance.

  6. Help me bro, i create page and i want rewrite this page!
    My file is next.php, i want rewrite to download-page/query
    page is prev / next page and query is query method.
    Method :
    page and query, to get this method using get_query_var(‘name method’)

Comments are closed.