Published

Custom URL rewrites in WordPress – A Getting Started Guide

I’ve been tweeting quite a bit recently about custom URL rewrites in WordPress. After a few hours of trial and error, I’ve managed to get my specific custom URL rewrites working. After reading through several tutorials online (the majority of which used the same examples to explain only a portion the information I was looking for), here’s my tutorial- a getting started guide to Custom URL rewrites in WordPress.

The process

So, what exactly are we doing here? To put things in point form, this is the process:

Create custom rewrite rules

Rewrite rules use a token which is replaced by the necessary query variable. For example, we create a token called %token% which, using a regular expression, gets replaced by “id=”. This will be shown further below.

Add our new variables to the public_query_vars array

By default, WordPress has an array of public query variables which can be used within templates and plugins. In order to access our query variables (ie: the “id=” in the above example), the variable needs to be added to the public query vars array.
/**
* add_custom_page_variables()
* Add the custom token as an allowed query variable.
* return array $public_query_vars.
**/

Flush all WordPress rewrite rules

By flushing the rules, we are forcing WordPress to regenerate it’s rules list, including our new rules in the rules set.
/**
* flush_rewrite_rules()
* Flush the rewrite rules, which forces the regeneration with new rules.
* return void.
**/

Now that our functions have been created, we need to hook them into the various necessary processes within WordPress. We do this using a combination of actions and filters.

Our first action runs on initialization. This is where we flush the rewrite rules in WordPress, causing them to be regenerated.

The regeneration brings us to action number 2. This is where we create our custom rewrite rule, hook it on to the global rewrites array and generate the rules.

Our final line is our filter where we add the new public query variable into the global public_query_vars array. We then later use this array to access the variable in our theme.
add_action( 'init', 'flush_rewrite_rules' );
add_action( 'generate_rewrite_rules', 'create_custom_rewrite_rules' );
add_filter( 'query_vars', 'add_custom_page_variables' );

The scope of rewrite rules

By default, WordPress rules are applied across all pages. Thus, the tag “%pagename%” is used in place of a specific page slug. To use your rules on only a single page, replace “%pagename%” with a specific page slug. You can either generate this slug dynamically or staticly.

That’s all, folks

And there you have it. A custom rewrite rule in WordPress. This post is intended as a starting point for using custom rewrite rules in WordPress. There are virtually infinite possibilities created when using rewrite rules (either one or many) and some really interesting plugins and functionalities can come out of using rewrite rules.

Thanks must also go to Joe Hoyle who’s use of rewrite rules provided the inspiration for this tutorial.

If there’s anything that isn’t clear, please comment below and I’ll do my best to clarify and help.

Before I go… wouldn’t you like the files?

I thought you might. Click below to download the code written above (it’s been written into a class for easy implementation in your projects).

according to this post (http://wordpress.org/support/topic/226529), flush_rules() is quite expensive and shouldn’t be called on each init. newly generated rules are stored in the database, so you only have to call it once when you introduce new rewrites.

It would be nice if you followed this up with a real plugin example, and I “think” the add_action( ‘init’, ‘flush_rewrite_rules’ ); needs to be add_action( ‘admin-init’, ‘flush_rewrite_rules’ ); BUT I am not 100% certain on that.

“So if you want to modify rewrite rules, you will need to call $wp_rewrite->flush_rules() to force them to recalculate. You’ll need to do this in your plugin’s init action, so that it happens early enough in the process. ”

I don’t think using `init` or `admin-init` makes much of a difference. It means it’ll either run on init only when in the admin area or on init in general. Either way, init happens and the function doesn’t seem very intensive. Using `admin-init` could possibly increase front-end performance though, as the function would not be running on the front-end. Not sure if the performance difference would be noticeable though.

It looks, to me, like it’s a case of 6 of 1 or half a dozen of the other. Using either method, the function will run at the init stage. 🙂

Thanks for the link and quote. I find the WordPress codex to be a useful resource which I use regularly. 🙂

Thanks for your kind words about my tutorial. I’m glad you’ve found it to be useful. 🙂

/%postname%/ is causing a conflict with the default rewrite rule, causing it to not know where to turn, effectively. If you’d like to use /%postname%/ (not recommended, as it puts extra load on the system), I’d recommend placing it as a “custom structure” under “Settings -> Permalinks” in your WordPress Admin. 🙂

Without seeing the actual code in question, it can be difficult to ascertain the exact cause of the issue.

I’d recommend using a custom post type to handle the vehicle listings, which would take care of the permalinks and custom rewrite rules for you. More on this can be found here on the WordPress Codex. 🙂

Thanks for this tutorial – I think it’s got me started in the right direction. Hoping you might have a minute to give your thoughts on what I think is a related, but slightly different, problem.

I’m writing a plugin that generates content. Basically, any URI in the following pattern:

news/item/392/slug-goes-here

…should display the content for item #392. The issue being, #392 is not a post – it’s assembled from data in a couple of custom tables. In Drupal, this is second nature to me — I grab onto hook_menu and my module tells Drupal that it will provide content for any URL matching that pattern. But so far, my results in WordPress, trying various methods, seem to be either:

– I can get the data from the tables as expected, but also get a 404 error
– I just wind up displaying the site’s front page

Your method seems to result in the latter, using the following structure:

I believe the solution may be to have this rule redirect to a page (news-item) in which I’ve put a shortcode, which can then use the news_item_id to pull the necessary info. But as I say, it doesn’t seem to be winding up there, it’s just hitting the home page. Any thoughts?

OK.. I have spent a dozen hours on this and believe your article to be the closest thing to what I want to accomplish. I always try to find the solution myself, but as you started out by saying… others seem to LEAVE OUT exactly what you need.

So looking at your code I am hoping there is something you might do to help.
First you should know permalinks are working just fine for me in my WP site.
I totally get all aspects about the built in WP permalink stuff – and its’ all good.

But here’s the deal. And I will be as clear as I can.
I am looking to create a custom rewrite(s) – which is how I came to land on your page.

I have created a template_profile.php for my theme, and certain things on this page will change based on the value of ?id=XX . So for example, if I hit my page at http://mydomain.com/profile/?id=12 … it will go into a folder named ’12’ and pull out a picture from that folder.

I can’t seem to get my head around how to do this and make the url look nice.

The big challenge is to pass the (registered) username into the pretty url, get the user_id number from that, and then go into that numbered folder to pull some content out of that folder – to populate the page.

But HOW do I get that number from a /profile/username if the url doesn’t exist yet????? Mod rewrite should be the answer, yes???

I hope this is all clear. And a thousand thanks for any info you can provide. I think the solution might be staring me in the face right here on your page! But I don’t quite get it yet. And Googling this has been difficult because I am not even sure what to google specific to my scenario.

Regarding your “cars” example, I’d advise using the “register_post_type()” function and creating a custom “cars” post type. This will provide the URL structure you’re looking for, along with a templating structure in the form of the “single.php” file or, for more specific customisations, the “single-car.php” file. You’ll then have access to the appropriate ID when viewing that “car”.

Regarding your “profile” example, I’d recommend using the built in WordPress authors system. Each profile is a username on your website, with a URL that follows the convention of http://domain.com/author/username/. The output of this screen can be customised via the “author.php” template file.

Therefore, in the examples you’ve posted above, the desired results can be achieved without a custom WordPress URL rewrite rule. 🙂

BIG thanks Matty for your quick reply…. and while I carefully considered your suggestion, I accidentally stumbled upon my solution(!) using basic 101 trial and error. Love it when the stars align like that.

Not realizing that WP has this “pagename” built in so it’s essentially saying “for all requests to profile/whatever…. send them to index.php and specify the WP pagename” you want to use (or include) and pass the id – but we will make it LOOK nice for you.

Then at the top of the “profile” page …
in PHP, explode the requestURI and assume array[2] is the user name we want from the DB.
Get the user ID from that and WHAMMO! — it works!

So now the page accepts ?id=XX — AND — /username

I even tried it with http://domain.com/username , and that works great too.
You just have to use ” $new_rules = array(‘([^/]+)/?$’ => ‘index.php/?pagename=profile&id=$matches[1]’);

The only problem (as I previously suspected and mentioned in my last post) …. if there is a username by the name of “contact” or “about” (or any other name of an existing page) it will conflict. If I could somehow specify NOT to allow any usernames which will conflict with http://domain.com/whatever then I could have http://domain.com/username/ and that would be SWEET.

I would hate to write out an IF condition for ALL my pages, nav items, posts etc. to avoid this. Will have to see if I can somehow get my head around that. But I thank you for your page, and your response and your attention to this – which was driving me NUTS.

I want to create a rewrite rule or plugin that makes permalinks to articles look like .html files – to look like actual links to html-pages. And i want to make the links to embedded images in articles look like they come not from the upload /files directory, but from a directory for each article e.g. http://www.example.com/topic1/text.html will include a link to an image which is http://www.example.com/topic1/image.jpg.

Hello Matty. I would like to use your script to delete the words “page” from the achive pagination and “attachment” page from the image attachment. It’s possible to integrate this script with these features? And how? Thank you.

I found your blog and this tutorial and noticed it has been a while since you posted it.

My situation is very similar as to that of AFAN (last commenter).

I need to use profile instead of author (http://domain.com/profile/username). Therefore what I did was to duplicate a page and put it in the theme as a template (profile.php) so I can pass a variable to it. That was my intention of using a rewrite rule since I cannot use http://domain.com/profile.php because it will give me a 404 page not found.

Hi Matty, Thanks for the post, like others it has really helped me change URL parameters to nice looking urls on WordPress. However I do seem to have a slight issue with the way it works.

Just to fill you in, I have a page in WordPress called Events, and is at the root (/events). I want to show a block of content depending on $_GET parameter of flow=open-evenings. Using your code, and changing the relevant bits, I am able to display this block when visiting /events/open-evenings/ but I’ve also noticed, if I replace open-evenings with anything else, the standard events page loads up, instead of a 404. So this means I have countless duplicates of the events page (as far as Google is concerned).

What can be done about this? By the way, the $_GET I’m using in the page file is $flow = get_query_var(‘flow’);