Whenever possible, I prefer to use pretty URLs in WordPress, and the ability to adjust permalink settings in the WordPress admin makes that simple to do. However, what if you want to pass a query string to a page without muddying up the URL with that query string? In this tutorial I'll show you how to add custom rewrite rules to WordPress to do just that.

It may not be immediately clear to some where a technique like this might come in handy. So I’m using a recent project as an example. This particular project was for a subsidiary of Milacron, one of Cincinnati’s most well-known manufacturing companies. Cimcool develops industrial fluids: additives, rust inhibitors and metalworking fluids are some of their major product lines and those product pages are what we’ll use for this tutorial.
Requirements
They already had a website but hired us to handle the SEO. Part of that process involved cleaning up the URL structure of the site by removing query strings from the URLs on product pages. The particular page I’m going to cover in this tutorial is this one. If you visit that link and click any of the categories listed on the left-hand side of the page you’ll see that you’re sent to a page that looks identical but has the category name in the URL. In fact, all of those pages are the same “MSDS-PIF” page served by WordPress, while the content shown on each of those pages is determined by a query string that is parsed from the last portion of the URL. This allows us to have keyword-rich URLs for the products categories without having to create separate WordPress pages for each category.
Problem
The biggest problem with getting this done in WordPress, as I alluded to in the last paragraph, is that even with pretty permalinks enabled, we would have had to create a new WordPress page with a unique template for each of those product categories. There were only 15 product categories at the time, so that wouldn’t be such a tedious task, until you consider the fact that it makes adding categories a bit of a hassle, in that we’d have to create a new WordPress page and page template anytime the company would need to add categories. While that would mean more billable hours for us, it wouldn’t be in the client’s best interest if it could be avoided.
The Solution
Thankfully, it could be avoided. Instead of creating a bunch of pages and their respective page templates every time new categories are added, we can use WordPress’ ability to specify custom rewrite rules to pass the query string as part of the URL and use a single page and page template to display all product categories.
The Process
The first step was to create a function to add variables to WordPress’ query string and then hook that function into the query_vars hook.
Next, we created a function to add rules to WordPress’ existing array of rewrite rules and hook that function into the rewrite_rules_array hook.
Finally, in the MSDS-PIF page template, we used the $wp_query object’s query_vars property to get the query string from the URL.
The Code
So let’s get down to the code. First we need to create a function to tell WordPress to keep track of our new query variables. This function goes in your theme’s functions.php file.
function add_query_vars($aVars) {
$aVars[] = "msds_pif_cat"; // represents the name of the product category as shown in the URL
return $aVars;
}
// hook add_query_vars function into query_vars
add_filter('query_vars', 'add_query_vars');
The above process is fairly simple. When the function is hooked into query_vars, WordPress passes the existing array of query variables to the function. We simply add “msds_pif_cat” as another query variable and send the array back to WordPress.
Now WordPress knows, in addition to it’s existing query variables, it should add one called “msds_pif_cat,” which we’ll use to store the product category name as specified in the URL.
However, this alone won’t do the trick. We also need to tell WordPress how to populate that query variable. In Cimcool’s case, we want the product category pages’ URLs to look like this: http://www.cimcool.com/msds-pif/category-name/. So, we need to tell WordPress that anytime it finds a URL matching that structure, to use the last portion, the category-name, to populate our new “msds_pif_cat” query variable. To do that we need to add a custom rewrite rule.
We accomplish this with another function in our theme’s functions.php file:
function add_rewrite_rules($aRules) {
$aNewRules = array('msds-pif/([^/]+)/?$' => 'index.php?pagename=msds-pif&msds_pif_cat=$matches[1]');
$aRules = $aNewRules + $aRules;
return $aRules;
}
// hook add_rewrite_rules function into rewrite_rules_array
add_filter('rewrite_rules_array', 'add_rewrite_rules');
The function is hooked into rewrite_rules_array, so WordPress passes the existing rewrite rules as an array to the function, which we then retrieve as $aRules.
We then add a new rule in the form of an array: array(‘msds-pif/([^/]+)/?$’ => ‘index.php?pagename=msds-pif&msds_pif_cat=$matches[1]‘)
This rule tells WordPress that anytime it finds a URL that includes “msds-pif/”, followed by anything other than a forward slash, followed by another (optional) forward slash, to capture the last portion of that URL (the part within the parenthesis), and serve the page found at “index.php?pagename=msds-pif&msds_pif_cat=$matches[1]“.
By default, WordPress will store the captured portion of the URL in an array called $matches. And so if we have a URL request like “http://www.cimcool.com/msds-pif/industrial-cleaners/”, WordPress will serve the page at “index.php?pagename=msds-pif&msds_pif_cat=industrial-cleaners”.
This allows us to capture that msds_pif_cat variable on the MSDS-PIF page template and vary the content depending upon the contents of that variable.
Getting the Query Variable
We can’t just use the $_GET superglobal to grab our msds_pif_cat query variable. Instead, we have to access the query_vars property of the $wp_query object.
So, on our MSDS-PIF page template, we’ll access that variable using the following code:
if(isset($wp_query->query_vars['msds_pif_cat'])) {
$sMsdsCat = urldecode($wp_query->query_vars['msds_pif_cat']);
}
Next steps
In our case, we then pull all products matching the category name as stored in the $sMsdsCat variable from the database and display them on the page. Obviously what you do with that variable is up to you and, from this point on, is no different than if you had used PHP’s $_GET superglobal to get a query variable on a non-WordPress page.
One thing I should mention here is that the rewrite_rules_array hook is called when you update or save your permalink structure, so after you make these changes, you’ll have to re-save permalinks to see the changes take effect.
More examples
Take a look at the Cimcool link above to see all this in action. For an example of using multiple custom query variables, have a look at Screen Innovations’ dealer locator, where we used a similar process to display dealers on the page for a given country, state and city using SEO-friendly URLs and without the need to create a hundred or so page templates.
Finally, check out the following resources for more information on URL rewriting in WordPress:
http://codex.wordpress.org/Function_Reference/WP_Query
http://www.prodeveloper.org/create-your-own-rewrite-rules-in-wordpress.html
http://www.seodenver.com/custom-rss-feed-in-wordpress/

What is your wp version? I tried in wp 2.9.1 and it doesnt work. I guess the plugin I used in the page template need the query string catched and processed before it renders the page template.
I meant, I can display the variable content passed in query string, but the process of lookup category content seems begun before wp render / call the page template. Do you have any solution to this?
Sorry Roy, but I have no idea what you’re asking me here. Can you explain further?
I’m trying to re-phrase my qearlier question:
I want to append query vars onto urls using the permalinks. My plugins only work currently by using the ?variable=x method and I would really like to have them as
http://blogaddress.com/pagename/x/
(where ‘x’ is in a predefined pattern to be recognized)
Thanks for the assistance toward better plugins for the community.
Using WP 2.9.1 and 2.9.2
That’s a tough one Roy. Without working on it it’s hard to know what the issue could be.
Hi !
Excellent article, i’m developping a quite smimlar system to generate multiple URL without creating pages and it’s really hard to find some good documentation on this.
The only weakness in the example given with Screen Dealers is the non support of 404 erros pages in case of non existing variable passed in the URL :
http://www.screeninnovations.com/dealers/You/See/?
By the way, it’s shame that the title tag doesn’t change with query variables. Why not hook this element too ?
Nico
Hi Nico, thanks for the compliment!
As for the Screen Innovations site, we were only contracted to convert the existing code to WordPress. Your suggestions are ideal in my opinion, but they weren’t within the project scope in that case.
Great article, very useful. It’s got me started but how do I add more directory levels? In your example you can drill down from country to state then to city. Cheers.
@jon : Just add a new line to grab the second variable passed in the URL :
$aNewRules = array(‘
msds-pif/([^/]+)/?$’ => ‘index.php?pagename=msds-pif&msds_pif_cat=$matches[1]‘),
msds-pif/([^/]+)/([^/]+)/?$’ => ‘index.php?pagename=msds-pif&msds_pif_cat=$matches[1]&msds_pif_title=$matches[2]‘)
);
Don’t forget to add the extra variables added in the ‘query_vars’ filter.
@John Crenshaw : Do you have an idea on how to handle 404 errors ? I didn’t manage to know which hook to use and when because when I display my results, headers have already been sent.
Not quite there – you may need to add an extra function to make sure wp generates the rewrite rules correctly – see the example at the bottom of http://codex.wordpress.org/Function_Reference/WP_Rewrite. I got 404 errors using the example above, but it just needed a couple extra lines. Thanks for the write up!
Hi Chris, did you (re)save permalinks? That may be what’s causing your problem.
Remember to flush_rules() when adding rules:
function flushRules(){
global $wp_rewrite;
$wp_rewrite->flush_rules();
}
add_filter(‘init’,'flushRules’);
Emad, WordPress flushes rewrite rules when you (re-)save permalinks through the wp admin, which is why this tutorial says to make sure and do that.
Incidentally, if you call the flush_rules function using init filter, it’ll run on every page load, which is overkill and will slow down your app.
Yes, if you are going to use flush_rules, only do it in your plug-ins’ activate callback so it is only done once.
How to do it if I want to add this in my functions.php theme file?
@ryan. I think your only option would be to create a custom option in wp_options upon running a flush function, and then check against that every time the function runs. In short, you should do this from within a function
Hi John, I have a problem which is related that I wondered if you might be able to help with.
We have a new WordPress site, and want to pass along an affiliate referral parameter. So say someone clicks this link
http://www.site.com/product21?partner=alice
we want to preserve that affiliate id somewhere, either passing it along in the querystring from page to page, or stuffing it in some vars collection.
then on this one page we have a contact us form. when they enter their name, email and message, we want the form to email that partner id along with their message i.e.
Your Name:: mary
Email:: mary@aol.com
Ship-to Country:: Spain
Product Name:: test
partner=alice
Please let me know if you have any questions. I look forward to your quote.
You will need to use cookies and/or sessions for this. To enable sessions, you have to put in your wp-config.php file
session_start();
Then you can store the your affiliate id in the session superglobal on any page you get the request on by hooking into the ‘wp’ hook (which comes after the query string is parsed and before any output is sent to the user so a good place to do any cookie/session cutting).. then you grab that affiliate id from the session cookies when your form submission is processed and pass it along with the email response.
can this code be used for taxonomy term pages. i would like to use your code to display taxonomy term-specific pages. for example, i have a movie site containing 37k movies and countless number of actors. the way it is setup each actor has its own taxonomy term page. when clicking on an actor, it will list all the movies for that actor. i was able to do that. but i don’t like the look of the wordpress supplied taxonomy page format. i would like to have the term pages list thumbnails instead for each movie for that actor. now, instead of creating thousands and thousands of actor pages, i can have one generic actor page template to display all actors. i hope i am making sense. thanks.
i am actually going thru this tutorial and i am getting error 404. i just want to clarify your instructions:
1. you said to create a page template. i did that and put the codes:
if(isset($wp_query->query_vars['celebrityname'])) {
$scelebs = urldecode($wp_query->query_vars['celebrityname']);
}
i also added some codes to process the variable $scelebs.
2. i also created my own functions file. for some reason, my theme doesn;t allow me to insert new functions to functions.php. so i just require_once the new function file so the page template can reference the functions.
3. i created a new page titled ‘Celebrities’ using the new template file.
4. in the browser, to test, i typed in: http://www.thevideocapital.com/celebrities/martin-sheen/
5. martin sheen is a term under taxonomy “actors”.
what do you think is my problem. is this script possible with taxonomy terms.
Looks like it’s working fine to me.
Somethings definitely not right there, 404 error. To help with testing, you can get the plugin apache-rewrite-rules and also just go to your blog with /rewrites as the path if you have 2.9.2. Paste the rest of your code here might help.
The URL was incorrect. Now it works.
yeah you’re right. but this doesnt work: http://www.thevideocapital.com/celebrities/angela-lansbury
Are you doing things the same on both page? It seems odd that one would work and not the other.
Your actors page doesnt have Angela Lansbury on it http://www.thevideocapital.com/actors/ but celebrities/ben-gazarra also 404′s and he is on the actors page. Hard to see what is going on without seeing more of your tag/tax/cat structure, permalink settings, generated rewrite rules, .htaccess and the code in your query_vars and rewrite_rules hooks.
the setup was really a bit complicated. i have a people category which i developed earlier. if you look at that category, the pages for each actor have more details (news, tweets, photos, movies, etc.). later on, i implemented the actor taxonomy where you can have an actor page (showing only movies where that actor appeared in) for every actor mentioned in a movie. all i want to do is reformat the actor taxonomy pages and i created a page named celebrities using the page template celebs according to john’s tutorial.
Hey,
I tried to implement these features on my site.
I have changed these 2 lines to my requirements:
$NewRules = array(‘schedule/(.+)/([^/]+)$’ => ‘index.php?pagename=$matches[1]&spielname=$matches[2]‘);
and $vars[] = “spielname”;
And i use this in my page template:
if(isset($wp_query->query_vars['spielname'])) {
$spiel = urldecode($wp_query->query_vars['spielname']);
…}
I have the page schedule and the subpage soccer. With the current $_GET superglobal variable i was able to usw links like this:
domain.de/schedule/soccer/?spiel=blah
But i wanted to be able to use domain.de/schedule/soccer/blah
But it now redirects me to domain.de/schedule/soccer/
Do you have any idea why it does not work as it is supposed to?
Thanks in advance,
Phil
Hi there,
I see folks here are trying to do this with custom taxonomies. This is exactly what I am trying to to in one of my sites. I started a discussion about this subject in the WordPress forums at: http://wordpress.org/support/topic/401527/ but so far I am out of luck in finding some help
Here is what I am trying to do. In my website I am currently using:
1. Posts (the default WP post type) for property adverts.
2. A custom post type called partner for our partners nationwide.
3. Categories (the default WP) for property types (eg: House, Flat, Condo, etc).
4. A custom taxonomy type called city for listing our cities (eg: Rome, Milan, Madrid, etc). This taxonomy is used by BOTH posts (i.e.: properties) and partners.
5. index.php only shows posts of the type post (the default post type used for property adverts).
6. page-partners.php shows the posts of the type partner (the custom type I created for our partners nationwide).
So far so good. This is all working fine. However, I noticed a little nuisance with queries being passed using the URL:
When I browse the categories of my site (eg: /flats/ or /houses/, I can use a query in the URL for my custom taxonomy called city and it works like a charm. But with my custom post type partner, it doesn’t work.
For example, if I add to my URL: /house/?city=rome
– WORKS: It will return to me all the posts in the category house that only have the city Rome assigned to them (rome is a term of my custom taxonomy called city).
However, if I try the above example of using a query in the URL, it doesn’t work.
For example, if I add to my URL: /partners/?city=rome
– WON’T WORK: Instead of returning all the posts of type partner that only have the city Rome assigned to them, I still returns me all the partners regardless of their city.
Any ideas of what I might be doing wrong?
Thanks in advance,
P.
Based on this tutorial, this is what I added on my functions.php theme file:
// my desperate attempt of passing URL queries of the taxonomy CITY to post type PARTNER inside my page /PARTNERS
function add_query_vars($aVars) {
$aVars[] = “city”; // in my case, city is a custom taxonomy
return $aVars;
}
add_filter(‘query_vars’, ‘add_query_vars’); // hook add_query_vars function into query_vars
// I have a custom page called PARTNERS that is used to display custom posts of type PARTNER
function add_rewrite_rules($aRules) {
$aNewRules = array(‘partners/([^/]+)/?$’ => ‘index.php?pagename=partners&city=$matches[1]‘);
$aRules = $aNewRules + $aRules;
return $aRules;
}
// hook add_rewrite_rules function into rewrite_rules_array
add_filter(‘rewrite_rules_array’, ‘add_rewrite_rules’);
However I got no luck with this… What am I doing wrong?
Any help is greatly appreciated.
Thanks,
P.
I noticed you said you found the answer on the WP forums, can you post your answer here?
R’phael,
Yes I found an answer, but so far it only works if I use /index/php?pagename=my-custom-pagetemplate&my-customt-axonomy=my-taxonomy-term
The reason why I didn’t post the answer in the forums is because I was very tired of working and ding on and on for this solution, and I am getting behind the schedule in my project. So besides the fact I was quite busy, I got some obnoxious replies in wordpress.org from people who I was asking for help. So the pressure of the timeline, the lack of support PLUS the bad attitude I got from some folks has really put me off.
Don’t take this the wrong way, I’ll be happy to help others, do some brainstorming and share knowledge. I just don’t feel like doing it in forums or sites.
In fact I plan to release a full in-depth tutorial of what I’ve been doing. It involves a lot of stuff such as custom posts, taxonomies, URL queries, complext query_post() stuff, thmbnails, google maps, custom fields, custom page templates and so on. I think it is enough material for a book to be honest!
IN THE MEANTIME, feel free to contact me through MSN/LiveMessenger and I’ll be happy to talk to you guys, share my findings exchange ideas. My MSN is: guyrush_yyz [at] yahoo [dot] com
I’ll be happy to help and to to you guys. But as I said before, away from forums for the moment
In the wordpress forums you said that you found a solution. Would you mind sharing it with us?
Hi Phil,
Please see my comment above (comment-1356) to R’phael. The short answer is yes I would be happy to share it (no catch). But I’d rather talk privately with individuals, away from forums.
Folks,
I actually changed my mind and posted my solution back in the forum. You can find it at http://wordpress.org/support/topic/401527/
Regards,
P.
Hi P,
Thanks for this! I was wondering how this was going but didn’t have a moment to get in and try to answer it completely. I’m glad you found the answer!
John,
I am happy I could help. If you think there is other ways of doing it, perhaps an improvement, please let us know. I am sure I speak for all of us here
Just wanted to say thanks for posting this – exactly what I was looking for and saved me a lot of time.
This is a great tut and it works great except for one problem, now every page goes to my index page? does anyone else have this problem?
Did you re-save permalinks?
This doesn’t seem to work in WordPress 3.0. I need custom permalink to get tag pages, adding a parameter in the url. It works in wp 2.9.2, but not with the last version
@David: I haven’t tested this in WP 3.0 yet so I couldn’t help you on this one.
I think thats my problem, so how can we get the same behavior in WP 3?
This seems to be a popular topic, so I’ll take a look at doing this in WP3.0 in the next few days and post an update here. If anyone else comes across something before then, please do post it.
Just went through this tutorial in WP3.0 to see if there were any issues and everything worked just fine. I had no trouble getting query vars to come through on the page.
If anyone’s having trouble, maybe you could elaborate a bit here and one of us can help.
Thanks for sharing these helpful tips. I have it work fine with 3.0. I’m stuck on one thing though. How do I get it to work with pagination (mysite.com/my-page/my-cat/page/2/). Do I have to add more rules and query variables?
Thanks.