The WordPress rewrite system is an area many people struggle with when starting out. People not familiar with rewrite systems don’t necessarily understand what role they play and often developers who are used to using rewrite systems find WordPress’s implementation somewhat clunky.
When working on relatively complex WordPress projects, which have multiple custom taxonomies and combinations of post types, custom rewrite rules are likely to be needed.
So, what are rewrite rules? In WordPress, a rewrite rule is essentially a map of a URL pattern (using regex) to the WP_Query arguments. When the URL regex pattern is matched, the global $wp_query
is populated with the arguments of the rule. For example, a year archive “page” would have a rewrite rule like:
^([d*4])/?$ => year=$matches[1]
Now, to add your own custom rewrite rules, you have hook into potentially several places. Firstly you must hook into flush_rewrite
to add your regex => wp_query
arguments:
add_rewrite_rule( '#some regex#', 'post_type=offer&posts_per_page=5' );
Some things to note:
- If you want to do any more advanced wp_query args that you can not pass in the string args style, or dynamically set arguments, you will need to hook into parse_request.
- If you have used any non-standard public query vars in your wp_query arguments you will need to hook into public_query_vars and add them to the array.
- Then you will need to hook into template_redirect to load a custom template for that URL pattern (if you so wish).
As a large amount of the work that I do is with quite complex relationships of post types and taxonomies, I have wrapped all the rewrite and associated functionality into a simple API, called hm_add_rewrite_rule.
The function takes an array of arguments about the specific rewrite rule, though the interface is all via one function call, it is backed by a pretty modular object orientated approach. A brief rundown of the argument
<?php | |
regex // the regex pattern to match against the URL | |
query // the wp_query arguments | |
template // the template file to load for this rewrite, full path or relative to active theme | |
request_callback // callback called when the url is matched, useful for adding complex wp_query arguments | |
disable_canonical // if set to true, will disable the often pesky canonical redirect that can erosion sly run on complex queries | |
post_query_properties // list of properties to add / overwrite on the WP_Query object, useful for setting is_my_custom_page=1 style properties. | |
title_callback // called via the wp_title hook to easily set the page title | |
restrict_access // can be set to "logged_in_only" etc for private pages. |
That’s about all the most useful arguments, but there are more. An example of a user profile rewrite rule:
<?php | |
hm_rewrite_rule( array( | |
'regex' => '^users/([^/]+)/?', | |
'query' => 'author_name=$matches[1]&', | |
'template' => 'user-archive.php', | |
'body_class_callback' => function( $classes ) { | |
$classes[] = 'user-archive'; | |
$classes[] = 'user-' . get_query_var( 'author_name' ); | |
return $classes; | |
}, | |
'title_callback' => function( $title, $seperator ) { | |
return get_query_var( 'author_name' ) . ' ' . $seperator . ' ' . $title; | |
} | |
) ); |
Another rewrite with more callback options:
<?php | |
hm_rewrite_rule( array( | |
'regex' => '^reviews/([^/]+)/?', // a review category page | |
'query' => 'review_category=$matches[1]&', | |
'template' => 'review-category.php', | |
'request_callback' => function( WP $wp ) { | |
// if the review category is "laptops" then only show items in draft | |
if ( $wp->query_vars['review_category'] == 'laptops' ) | |
$wp->query_vars['post_status'] = 'draft'; | |
}, | |
'query_callback' => function( WP_Query $query ) { | |
//overwrite is_home because WordPress gets it wrong here | |
$query->is_home = false; | |
}, | |
'body_class_callback' => function( $classes ) { | |
$classes[] = get_query_var( 'review_category' ); | |
return $classes; | |
}, | |
'title_callback' => function( $title, $seperator ) { | |
return review_category . ' ' . $seperator . ' ' . $title; | |
} | |
) ); |
Or, if you are writing a nice RESTful API, and you want to do it using WordPress rewrite rules (for the cool kids), you can define the type of method request (GET|POST|PUT|DELETE) etc, capture the post_id from the url and handle the request in the request_callback.
<?php | |
hm_add_rewrite_rule( array( | |
'request_method' => 'DELETE', | |
'regex' => '/api/v3/posts/([^/]+)', | |
'query' => 'post_id=$matches[1]', | |
'request_callback' => function( WP $wp ) { | |
wp_delete_post( $wp->query_vars['post_id'] ); | |
exit; | |
} | |
) ); |
HM Rewrite is currently part of the HM Core plugin, though the hm-rewrite file is self-contained, so if you want to try hm_add_rewrite_rule()
out, can can just use this file: https://github.com/humanmade/hm-core/blob/master/hm-core.rewrite.php.
Remember: Whenever you add a new rewrite rule via hm_add_rewrite_rule()
or update the regex/query of an existing rule, you have to flush your permalinks (just visit the permalinks options page).