Got a call from a client last week. One of our junior devs had been tasked with a “simple” job: create a custom landing page at /welcome/ for a new campaign. But the page was throwing a 404. Total dead end. The client was getting anxious. The dev was stumped. They swore the page existed, the template was there, but WordPress just wouldn’t see it. This, my friends, is a classic case of getting tangled in the WordPress Rewrite API.
The Rewrite API is WordPress’s internal translator. It turns a pretty URL like /my-awesome-post/ into a query WordPress understands, like ?p=123. Without it, we’d all be stuck with ugly URLs. But once you add custom rules, things can get messy fast.
The obvious fix that makes things worse
My first thought was the usual suspect. “Did you flush the rewrite rules?” I asked. Of course, he had. He even showed me the code. He had hooked a function into init to add the new rule for the /welcome/ page, and to be “safe,” he added a call to flush_rewrite_rules() right after it. And that’s the kicker. I could feel the site groaning from here. He was regenerating the entire URL routing table on every single page load.
This is a total nightmare for performance. flush_rewrite_rules() is an expensive operation. It writes to the database and can even modify the .htaccess file. Running it on every request is like rebuilding your house every time you want to open a window. It “works,” but it’ll bring even a powerful server to its knees. I’d made this same mistake years ago on a high-traffic site and almost took it down during a product launch. You only learn that lesson once.
Flushing rewrite rules the right way
The rule itself was fine. The problem was the flushing strategy. You don’t need to flush rules all the time. You only need to do it once, when the rules actually change: on plugin activation, deactivation, or when a setting that affects URLs is saved.
The reliable way to handle this is with a database flag. You add your rewrite rules on init every time, which is correct, because they need to be registered with WordPress on every load. But you only trigger the flush when you actually need it. This approach builds on a concept I saw over at carlalexander.ca, and it is rock solid.
// 1. Add your rule on every page load so WordPress knows about it
function my_custom_rewrite_rule() {
add_rewrite_rule('^welcome/?$', 'index.php?pagename=welcome-page', 'top');
}
add_action('init', 'my_custom_rewrite_rule');
// 2. Set a flag when your plugin is activated
function my_plugin_activate() {
add_option('my_plugin_flush_rewrite_rules', true);
}
register_activation_hook(__FILE__, 'my_plugin_activate');
// 3. Check for the flag, flush the rules ONCE, then delete the flag
function my_plugin_flush_rules_on_load() {
if (get_option('my_plugin_flush_rewrite_rules')) {
flush_rewrite_rules();
delete_option('my_plugin_flush_rewrite_rules');
}
}
add_action('init', 'my_plugin_flush_rules_on_load');
So what’s the takeaway?
The WordPress Rewrite API is powerful, but you have to handle it carefully. Writing the right regex is only half of it. The other half is knowing the performance cost of what you run. The main lesson: keep rule registration separate from rule flushing.
- Register your rules on every page load using the
inithook. - Flush your rules only when they change. Use an activation hook or a settings-save callback to set a one-time flag.
Look, this stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.
Get this right, and you’ll build robust, performant features. Get it wrong, and you’ll be wondering why your server is on fire. Simple as that.