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:

  1. Create custom rewrite rules
  2. Add our new variables to the public_query_vars array
  3. Flush (and thus, regenerate) all WordPress rewrite rules
  4. Add our functions from steps 1, 2 and 3 into WordPress via actions and filters

Right, so lets get down to it then.

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.

* create_custom_rewrite_rules()
* Creates the custom rewrite rules.
* return array $rules.

function create_custom_rewrite_rules() {
global $wp_rewrite;

// Define custom rewrite tokens
$rewrite_tag = ‘%exampletag%’;

// Add the rewrite tokens
$wp_rewrite->add_rewrite_tag( $rewrite_tag, ‘(.+?)’, ‘car=’ );

// Define the custom permalink structure
$rewrite_keywords_structure = $wp_rewrite->root . “%pagename%/$rewrite_tag/”;
// $rewrite_keywords_structure = $wp_rewrite->root . “$slug/$rewrite_tag/”;

// Generate the rewrite rules
$new_rule = $wp_rewrite->generate_rewrite_rules( $rewrite_keywords_structure );

// Add the new rewrite rule into the global rules array
$wp_rewrite->rules = $new_rule + $wp_rewrite->rules;

return $wp_rewrite->rules;

} // End create_custom_rewrite_rules()

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.

function add_custom_page_variables( $public_query_vars ) {
$public_query_vars[] = 'car';

return $public_query_vars;

} // End add_custom_page_variables()

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.

function flush_rewrite_rules() {

global $wp_rewrite;


} // End flush_rewrite_rules()

Add our functions from steps 1, 2 and 3 into WordPress via actions and filters

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).

Download the  “Custom URL rewrites in WordPress – A Getting Started Guide” code example.

Update… customise author profile URLs!

I’ve read a few queries regarding customising the author archive URLs in WordPress. Click the link to read my post on the subject and to see the code snippet I’ve compiled. 🙂

WordPress tips in your inbox ✨

More WordPress thoughts and tips, right in your inbox.

We don’t spam! Read our privacy policy for more info.


31 responses to “Custom URL rewrites in WordPress – A Getting Started Guide”

  1. Great tutorial nd very well-explained. I will definitely find a number of uses for this.

    1. Thanks Justin. I look forward to it. 🙂

      Please tweet some URLs where you use this tut. I’d be keen to see your implementation of it. 🙂

      Cheers and thanks again,

  2. according to this post (, 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.


    p.s., i love your blog design!

    1. Hey lite,
      Thanks for the link and your very kind comments about my blog design. 🙂

      I’ll check into flush_rules() and optimise the code accordingly.

      Cheers and thanks again,

  3. 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.



    1. Hi Brad,
      Thanks for your comment.

      I’ll look into the call to flush_rewrite_rules.

      I’ll be writing a few more posts discussing custom rewrite rules in WordPress, how to work with them and a few advanced uses. These may be followed up with a plugin. 😉


  4. The guidance given in the WordPress Codex is to do it in ‘init’ rather than admin-init’, however I may be not taking into account performance with this.


    “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. ”


    1. @Dan Thanks for your comment, Dan. 🙂

      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. 🙂


  5. This was very very useful. I got all my rewrites working nicely, using a single general pagename rule, e.g

    $rewrite_keywords_structure = $wp_rewrite->root . “%pagename%/$rewrite_tag/”;

    However… my regular permalink’d blog posts don’t work anymore. i.e.
    now gives a 404.

    Any ideas? Having flushed the WordPress default rules, do I have to explicitly put appropriate default ones back in?


    1. Hi Mick,

      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. 🙂


    2. Hi Bud

      I’m having the same problem too. I’m trying to use this tutorial to eliminate ugly vehicle id’s from our web site, i.e.:

      and changing it to something like this: or even just …/view-honda-bike/34

      Problem I just can’t get this to work. My permalink structure is:


      What am I doing wrong?

      Thanks in advance!

      1. Hi Ste,

        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. 🙂


  6. William R. Dickson Avatar
    William R. Dickson

    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:


    …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:

    $rewrite_tag = ‘%news_item%’;
    $wp_rewrite->add_rewrite_tag($rewrite_tag, ‘(.+?)’, ‘news_item_id=’);
    $rewrite_keywords_structure = $wp_rewrite->root . ‘news/item/$rewrite_tag/’;

    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?

    Thanks for your time!

  7. HI Matty!

    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 … it will go into a folder named ’12’ and pull out a picture from that folder.

    or as another example:

    I already have this working exactly as I want.
    BUT! the ‘id’ variable in my dream scenario is a username.

    So I want the link to look like this:


    Side Note: Ideally I would like but I’m guessing if some bozo’s username is “contact” it would conflict with my contact page – which already exists. So is totally fine with me.

    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.

    Anyway . Thanks again.

    1. Hi AFAN,

      No problem. Let me see if I can assist. 🙂

      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”.

      More information on this can be found at:

      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 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. 🙂

      I hope this clarifies things. 🙂


    2. Hello AFAN,

      I see Matty’s point of using the WP author system but I still need to change to /profile and using the author system simplifies it as long as I can change the structure.

      Were you able to go from /author to /profile and what method did you used?.


  8. 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. is now working perfectly.

    It turns out the answer is:

    // hook add_rewrite_rules function into rewrite_rules_array
    add_filter(‘rewrite_rules_array’, ‘add_rewrite_rules’);

    function add_rewrite_rules($rules) {
    $new_rules = array(‘profile/([^/]+)/?$’ => ‘index.php/?pagename=profile&id=$matches[1]’);
    $rules = $new_rules + $rules;
    return $rules;

    I didn’t understand this bit — => ‘index.php/?pagename=profile&id=$matches[1]’);

    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 , 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 then I could have 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.

    Have a spectacular weekend.

  9. Hey, that is a great article!

    I wonder if you offer individual help on WordPress/php problems?

    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. will include a link to an image which is

    Could you please help me with that?


  10. Sartana Avatar

    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.

  11. Hello Matty,

    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 ( 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 because it will give me a 404 page not found.

    I see 2 options:

    1- using a rewrite for where the real address is something like OR

    2- using the wordpress author system which sounds like an easy way to get it working. The problem is how do I make /author be /profile ?

    Thanks for your assistance and the tutorial is great.

  12. Thanks! This is the only tutorial that actually worked for me..

  13. Hey All,

    Where do i need to save the code (class.php file) on my server?
    And where do i need to add “include” in order to have working?


  14. Hi,
    I’m not really a good programmer but I’ve been trying to base your guide to separate comments to a sub url, like so:

    $rewrite_keywords_structure = $wp_rewrite->root . “%pagename%/discussion/$rewrite_tag/”;
    $rewrite_keywords_structure = $wp_rewrite->root . “%postname%/discussion/$rewrite_tag/”;

    I also added a template redirect function to call on a comment.php if the query var shows up. My problem is that the posts works but when I open up a page, it doesn’t show at all even the page itself.


  15. Hi, It’s me again. Sorry for the previous question but I finally figured it out. I forgot to add “$wp_rewrite->add_rewrite_tag( $rewrite_tag, ‘([^/]+)’, ‘var=’ );” for the %postname% search.


  16. Thanks that worked well.

  17. Hi,

    Very well written article.
    In my scenario I want my post to be appeared as or simply

    I want solution through WP-Admin permalinks option if possible. I have tried hard but not successful. Please guide me.


    1. Hey Kamran,

      Simply place /posts/ in your permalinks structure, as follows:


      That should do the trick. 🙂

  18. Alex Righetto Avatar
    Alex Righetto


    Thanks for the article. I have a question. I want to create this kind of permalink:

    root / %city% / %category% / %postname% / for the single post

    root / %city% / %category% / for the category

    where %city% is my main query var. How can I obtain this structure?

    Any idea? %city% is a setter/getter for filter posts

    Thanks !!!!

  19. Nagaraj Avatar

    I Have a url and im showing posts from cat id 3 and i want to change the url to , how do i do that?

    1. Hi there,

      You could change the category slug and then use the “No Category Base” plugin to remove “category” from the URL.

  20. 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’);

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: