Mastering WordPress Rewrite Rules: A Developer's Guide to Routing Flexibility
Syed Talha Ibrahim
Wordpress Developer @ CODSATS | Graphic Design, Social Media Marketing
At BrightMinded, we’re deeply immersed in WordPress development. We understand it inside and out! But sometimes, to truly harness its potential, we need to push beyond its default capabilities. One of the most common pain points for developers is WordPress’s lack of a flexible, modern routing system, like those in Laravel, Express.js, or Django. However, with a solid understanding of WordPress's rewrite system, developers can unlock more flexibility than it initially appears to offer.
A Brief History of WordPress
WordPress has been around since 2003 (or earlier, if you count its predecessor, b2/cafelog). In its early days, WordPress was a basic blogging platform, far from the robust CMS it has become today.
Back in 2003, URLs for blog posts looked something like this: brightminded.com?id=3715. These URLs relied on query parameters like ?id= rather than the clean, SEO-friendly paths we expect today.
That all changed with WordPress 1.0, released in January 2004, which introduced Pretty Permalinks. This new feature transformed URLs from something like brightminded.com?id=3715 to more readable, SEO-friendly versions such as brightminded.com/blog/mastering-wordpress-rewrite-rules. However, in order to maintain backward compatibility and support servers without Apache’s mod_rewrite, the original query-string routing system was never completely phased out.
Since WordPress still relies on query parameters for routing, many of them are reserved and cannot be used as GET parameters or form field names. A complete list of these reserved parameters can be found in the WordPress Codex.
Renaming Internal Rewrite Rules
With a basic understanding of WordPress’s query string-based routing, let’s explore how we can make use of this system. WordPress comes with a set of built-in rewrite rules by default, such as:
But why might you want to modify these default rules? A common reason is the need to translate default permalinks if your site isn’t in English. For example, let’s say you want to change the author permalink to French. You can do so by replacing the hardcoded strings stored as class properties in WP_Rewrite.
You can hook into the init action to modify the WP_Rewrite properties:
add_action('init', 'translate_author_rewrite_rules_to_french');
function translate_author_rewrite_rules_to_french() {
global $wp_rewrite;
$wp_rewrite->author_base = 'auteur';
}
Other properties that can be renamed in a similar manner include:
Though we believe these translations should be handled natively in WordPress core, this feature is unlikely to be added in the near future.
Removing Internal Rewrite Rules
WordPress's default rewrite rules come with an unintended consequence: they reserve certain terms, preventing you from using them as post or page permalinks. For example, if you try to set a permalink to "author", WordPress will trigger the author rewrite rule and load the author.php template instead.
To address this, you can either rename the default rewrite rule (as shown above) or remove it completely. Here’s how you can remove various default rewrite rules:
add_filter('post_rewrite_rules', '__return_empty_array');
add_filter('date_rewrite_rules', '__return_empty_array');
add_filter('comments_rewrite_rules', '__return_empty_array');
add_filter('search_rewrite_rules', '__return_empty_array');
add_filter('author_rewrite_rules', '__return_empty_array');
add_filter('page_rewrite_rules', '__return_empty_array');
For custom post types and taxonomies, the filter format is:
add_filter('{post_type}_rewrite_rules', '__return_empty_array');
add_filter('{taxonomy}_rewrite_rules', '__return_empty_array');
Adding New Rewrite Rules
Beyond modifying existing rules, you might need to add your own. Custom rewrite rules are ideal when working with dynamic URLs that can’t easily be handled by posts, pages, or custom post types. For instance, let’s say we run a weather website and want to generate a unique URL for each city. With Pretty Permalinks disabled, we might start with a query parameter like city=london:
Since WordPress maps the city parameter to a Pretty Permalink, we can't use the usual $_GET superglobal directly. We’ll need to first register the new query parameter using add_rewrite_tag():
function custom_rewrite_tag( $vars ) {
add_rewrite_tag( '%city%', '([^&]+)' );
}
add_action( 'init', 'custom_rewrite_tag' );
The regular expression ([^&]+) matches any character except &, which is a safe default for most cases.
Next, you can access this query variable in your templates or plugins via get_query_var():
get_query_var( 'city', false );
To map this dynamic URL to a custom template, we use the template_include filter:
add_filter( 'template_include', 'add_city_to_hierarchy' );
function add_city_to_hierarchy( $original_template ) {
if ( get_query_var( 'city', false ) ) {
return get_query_template( 'city' );
} else {
return $original_template;
}
}
In the city.php template, you can include any custom logic you need, just like any other WordPress template. For instance, Yoast uses this technique to generate sitemap.xml files for specific content types.
Mapping to Pretty Permalinks
Now, we need to map our custom URL to a Pretty Permalink. This can be done with add_rewrite_rule():
function city_rewrite_rule() {
add_rewrite_rule( '^city/([^/]*)/?', 'index.php?city=$matches[1]', 'top' );
}
add_action( 'init', 'city_rewrite_rule', 10, 0 );
This rule matches URLs like example.com/city/london and translates them into example.com?city=london.
Handling Duplicate Content
A potential SEO issue arises here—since WordPress allows content to be accessed via both query string URLs and Pretty Permalinks, Google might treat them as duplicate pages. This can lead to SEO penalties or diminished rankings.
The best way to prevent this is by specifying a canonical URL, which tells search engines which version of a page to index. WordPress automatically adds canonical tags to posts and pages, but not to custom rewrite rules. To handle this, you can manually add a <link rel="canonical"> tag in the <head> of your template:
add_action( 'wp_head', 'add_canonical_tag' );
function add_canonical_tag() {
if ( get_query_var( 'city', false ) ) {
echo '<link rel="canonical" href="' . esc_url( get_permalink() ) . '" />';
}
}
The Rewrite Cache
WordPress stores rewrite rules in the database, known as the rewrite cache. After any changes to the rules, you’ll need to flush the cache. This is done by visiting “Settings” -> “Permalinks” in the admin panel—no need to save.
This mechanism allows rewrite rules to be generated on-demand when changes are made. However, any computationally expensive rules should be generated directly using the rewrite_rules_array function.
The Future of WordPress Routing
Though WordPress's rewrite system is based on older technology and may seem less intuitive than modern frameworks, there’s no reason it should limit your development. By mastering the rewrite rules and leveraging them in creative ways, you can add powerful, dynamic routing to your WordPress site.
To make the system even more developer-friendly, consider using helper classes, like Humanmade’s hm-rewrite project, or the Timber theming framework, which offers a more modern approach to routing. These projects provide lightweight improvements and can inspire clean and scalable solutions for adding rewrite rules in your own projects.
What are your thoughts on WordPress's routing system? Do you have any better solutions for custom rewrites? Let us know by emailing us.