How WordPress Rewrite Rules Work

How WordPress Rewrite Rules Work

WordPress rewrite rules are an ordered list of regular expressions that match the path part of a request and map it to query variables. When you visit /blog/hello-world/, no hello-world.php file exists. Instead, a rewrite rule matches that path, sets name=hello-world, and WordPress runs a query to find the matching post. Here is how the whole system fits together.

How a URL becomes a query

The flow lives in two classes. WP_Rewrite builds and stores the rules. On each front-end request, WordPress walks the rules in order, finds the first regex that matches the requested path, and extracts the captured groups into query variables like name, category_name, or p. Those query vars are handed to WP_Query, which runs the actual database query and decides what template loads.

Pretty permalinks depend on one more piece: the server. Your .htaccess (Apache) or location block (Nginx) sends any request that is not a real file or directory to index.php. WordPress then reads the path from the request, runs it through the rewrite rules, and resolves the content. Without that server rule, pretty URLs return a 404 from the web server before WordPress ever sees them.

Adding a custom rewrite rule

To map your own URL pattern to content, you need two things: a rule and a registered query var.

add_action( 'init', function () {
	add_rewrite_rule(
		'^reports/([0-9]{4})/?$',
		'index.php?pagename=reports&year=$matches[1]',
		'top'
	);
} );

add_filter( 'query_vars', function ( $vars ) {
	$vars[] = 'year';
	return $vars;
} );

The first argument is the regex matched against the path. The second maps captured groups into index.php query vars using $matches[1], $matches[2], and so on. The 'top' priority puts your rule ahead of the built-in rules so it gets matched first. WordPress drops any query var it does not recognize, which is why you must register year through the query_vars filter, otherwise your value silently disappears.

To debug what rules exist and which one matches a given URL, paste your URL into the Rewrite Rules Explainer and see the matched pattern and resulting query vars without touching your database.

The number one gotcha: flushing rules

New rules do not take effect until the rewrite rules are flushed and rebuilt. This trips up almost everyone.

  1. Add your add_rewrite_rule and query_vars code.
  2. Go to Settings > Permalinks in wp-admin and click Save. This regenerates the rules.
  3. Test your URL. If it still 404s, paste it into the Rewrite Rules Explainer to confirm whether a rule actually matches it.

You can call flush_rewrite_rules() in code, but only on plugin or theme activation, never on init or any request that runs on every page load. Flushing is expensive because it rewrites the rules option on every hit, which slows the whole site. Register rules on init, flush once on activation, and let WordPress cache the result.

Add the rule, register the query var, save your permalinks once, and your custom URL just works.

← All posts