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 usingquery_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');
Nice post! Very handy info for an upcoming project and I will definitely be referring back to this.
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.
Can you please explain the “queryreplace” property in the dump information above?
It is also the third parameter of add_rewrite_tag (http://codex.wordpress.org/Rewrite_API/add_rewrite_tag)
can’t seem t get my head round it.
queryreplace is how the tag appear in url and it is optional in case of
add_rewrite_tag()
. for example you defined your tag as%product_id%
it will validated according to$regex
you passed and appear in url in the formhttp://example.com/.../?product_id=value
but if you defined query replace to bepro_id
it will appear in url in the formhttp://example.com/.../?pro_id=value
.As hard as i try, I still do not understand.
I will be very happy if you explain it one step at a time in form of like a break down.
Please and please.
Actually WordPress Rewrite API try to make things simple. it is not different from basic URL rewriting so still you need to better grasp basic URL rewriting. I hope you read this article https://www.addedbytes.com/articles/for-beginners/url-rewriting-for-beginners/ and then come to WordPress Rewrite API and you will find things simple also I will write another tutorial about WordPress Rewrite API soon.
Can’t wait for it.
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 availableexample.com/apple/products/
andexample.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 statusexample.com/?pagename=test&....
toexample.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.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 lookingfor page to render. in your case you should replace all occurance of
products
withresults
andproduct_id
withdob
and it will work.Worked for me… Thanks
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!
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.
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’)