Master the WordPress Rewrite API and learn why calling flush_rewrite_rules incorrectly ruins performance. Get expert tips on managing WordPress permalinks.
I remember the first time I nuked a client’s site by calling flush_rewrite_rules() inside a functions.php file on every page load. It felt like a minor adjustment, but the site’s TTFB jumped from a snappy 300ms to a sluggish 2.5 seconds. That experience taught me that the WordPress rewrite system is powerful, but it's also a high-cost operation you shouldn't touch lightly.
If you’ve ever registered a custom post type or a custom taxonomy and found yourself staring at a 404 error, you’ve likely bumped into the complexities of the WordPress Rewrite API.
At its core, WordPress routing explained is a game of regular expressions. When a user requests a URL, WordPress doesn't look for a file on the server. Instead, it matches that URL against a giant array of regex patterns stored in your database under the rewrite_rules option.
If you want to understand how these requests map to content, you should check out my previous post on the WordPress Rewrite API: How URL Requests Map to Content. It covers the foundational mechanics that happen before the query is even parsed.
When you add a custom endpoint or a new post type, WordPress doesn't automatically know how to route to it. You have to tell the WP_Rewrite class to rebuild that lookup table. That’s where the "flush" comes in.
flush_rewrite_rules is a Performance KillerMany developers reach for flush_rewrite_rules() the moment they see a 404. It’s the "turn it off and on again" of the rewrite world. However, calling this function triggers a massive database operation. It recalculates every single rewrite rule for your site—including those from plugins, themes, and core.
On a site with a large number of taxonomies or complex custom routing, this can take about 500ms to a full second of pure CPU time. If you hook this into init or wp_loaded, you’re essentially forcing your server to rebuild the entire routing table on every single request.
Instead of flushing on every load, you should only flush when your plugin or theme is activated or deactivated. Use the activation hook to register your rewrite rules and perform the flush exactly once.
PHPregister_activation_hook( __FILE__, 'my_plugin_activate' ); function my_plugin_activate() { #6A9955">// Register your custom post type here first my_custom_post_type_registration(); #6A9955">// Now flush once flush_rewrite_rules(); }
If you’re developing locally, you might be tempted to put a flush in your functions.php. That’s fine for testing, but please, for the love of your server, remove it before you push to production.
Even when you flush, things go wrong. Here’s what I usually look for when the WP_Rewrite rules aren't behaving:
rewrite_rules option in the wp_options table becomes corrupted or simply too large. If you’ve got thousands of rules, consider cleaning up unused ones.query_vars filter, the router will ignore them.If you're building complex, event-driven architectures, you might find that managing state becomes difficult. I’ve written about how to handle Outbox Pattern for WordPress: Reliable Event-Driven Architecture to keep your data flows clean, which is a great pattern to pair with custom routing logic.
Q: Why do my custom post type URLs return 404s? A: Usually, it’s because the rewrite rules haven't been regenerated. Visit the "Settings > Permalinks" page in your dashboard and click "Save Changes." This triggers a manual flush and usually fixes the issue.
Q: Can I flush rules via WP-CLI?
A: Yes, and you should. Using wp rewrite flush is significantly safer and faster than adding a hook in your code. It keeps the heavy lifting out of the request lifecycle.
Q: Is there any scenario where I should flush on every load?
A: Almost never. If you're building a highly dynamic system where routes change based on user input, you should consider using the add_rewrite_rule() function more strategically or caching the results, rather than rebuilding the entire database table.
I'm still cautious about modifying WP_Rewrite on high-traffic sites. There’s always a risk that a plugin conflict might overwrite your carefully crafted regex. Next time, I’m planning to experiment with a more modular approach to routing that bypasses the global WP_Rewrite object entirely for specific API endpoints. Until then, keep your flushes limited to activation hooks, and your site’s performance will thank you.
Master the WordPress Options API by understanding how the wp_options table handles autoloading. Learn to optimize your database and speed up your site today.
Read moreThe WordPress Transients API is the simplest way to boost site performance. Learn how to cache dynamic data, reduce database load, and speed up your site.