I am messing around with WordPress, trying to get a good grasp of the thinking behind conventions used in WordPress. I came across a question, and my searches fell short of explaining what I was trying to figure out. I tried to look at the code for ‘wp-include/rewrite.php’, but I can’t seem to track down what’s going on for some reason. So I tried to black-box test it, and I am now confused.
When adding a custom route to WordPress, you can use add_rewrite_rule
. But unlike add_action
/ remove_action
and add_filter
/ remove_filter
, there is no remove_rewrite_rule
. Everything I have found online has said just use flush_rewrite_rules
.
This ‘weirdness’ led to some experimentation with flush_rewrite_rules
that in turn led to a concern that there is probably a better, ‘right’, way to do URL rewrites in a plugin. I will describe what I have done to confuse myself.
I created two plugins. The first ‘plugin-x’ adds a rewrite rule ‘^plugin-x/?$’ and then calls flush_rewrite_rules
in the plugin activation hook. The second ‘plugin-y’ only calls flush_rewrite_rules
in the activation hook. Both call flush_rewrite_rules
in their deactivation hooks.
I wanted ‘plugin-y’ to interfere with ‘plugin-x’ by flushing the rules that plugin-x created, based on what I had seen up to this point. Activating ‘plugin-x’ put the rewrite rule in place, and activating ‘plugin-y’ removed the rule created by ‘plugin-x’.
The part that confuses me is that flush_rewrite_rules
seems to be doing two different things. If it’s not called after add_rewrite_rule
within the activation hook, then the route is never actually added to the database. Implying that flush_rewrite_rules
is saving the existing rules before deletion and reload. But then how is flush_rewrite_rules
called from ‘plugin-y’ deleting the rule created by ‘plugin-x’?
The only other explanation I can come up with is that add_rewrite_rules
is deferring the insert into the database into the flush_rewrite_rules
call, and marks it for removal on the next call to flush_rewrite_rules
. I don’t think that is the case, but I am not sure how to explain it.
Since this is not how you are supposed to add a rewrite rule, I wanted to see if add_rewrite_rule
called in the ‘init’ action would show up in the database. So I created a third plugin called ‘plugin-z’, that calls add_rewrite_rule
in the ‘init’ action. The rewrite rule showed up in the database without the need to call flush_rewrite_rules
, and was resilient to subsequent calls to flush_rewrite_rules
.
So I have two questions
- What does
flush_rewrite_rules
actually do? - Does calling
add_rewrite_rule
in the ‘init’ action result in an extra database call every page load?
plugin-x
<?php
/**
* Plugin Name: plugin-x
* Description: create a rewrite rule on activation, and remove it on deactivation.
*/
register_activation_hook(__FILE__, 'plugin_x_activation_hook');
register_deactivation_hook(__FILE__, 'plugin_x_deactivation_hook');
function plugin_x_activation_hook()
{
add_rewrite_rule('^plugin-x/?$', 'index.php?plugin_x_custom_rewrite_rule=1', 'top');
flush_rewrite_rules();
}
function plugin_x_deactivation_hook()
{
flush_rewrite_rules();
}
/* necessary nonsense required to point the custom route at code within this plugin. */
// add custom query vars for rewrite rule to be associated with something custom
function plugin_x_query_vars($vars)
{
$vars[] = 'plugin_x_custom_rewrite_rule';
return $vars;
}
add_filter('query_vars', 'plugin_x_query_vars', 0);
// add a request parser to search for custom query var
function plugin_x_parse_request()
{
/** @global WP $wp */
global $wp;
if(array_key_exists('plugin_x_custom_rewrite_rule', $wp->query_vars))
{
wp_die('plugin-x route active', 'plugin-x', array('response' => 200));
}
}
add_action('parse_request', 'plugin_x_parse_request', 0);
?>
plugin-y
<?php
/**
* Plugin Name: plugin-y
* Description: call flush_rewrite_rules on activation and deactivation
*/
register_activation_hook(__FILE__, 'plugin_y_activation_hook');
register_deactivation_hook(__FILE__, 'plugin_y_deactivation_hook');
function plugin_y_activation_hook()
{
flush_rewrite_rules();
}
function plugin_y_deactivation_hook()
{
flush_rewrite_rules();
}
?>
plugin-z
<?php
/**
* Plugin Name: plugin-z
* Description: create a rewrite rule in the 'init' action, and remove it on deactivation.
*/
register_activation_hook(__FILE__, 'plugin_z_activation_hook');
register_deactivation_hook(__FILE__, 'plugin_z_deactivation_hook');
function plugin_z_activation_hook()
{
// no-op
}
function plugin_z_deactivation_hook()
{
flush_rewrite_rules();
}
function plugin_z_init()
{
add_rewrite_rule('^plugin-z/?$', 'index.php?plugin_z_custom_rewrite_rule=1', 'top');
}
add_action('init', 'plugin_z_init');
/* necessary nonsense required to point the custom route at code within this plugin. */
// add custom query vars for rewrite rule to be associated with something custom
function plugin_z_query_vars($vars)
{
$vars[] = 'plugin_z_custom_rewrite_rule';
return $vars;
}
add_filter('query_vars', 'plugin_z_query_vars', 0);
// add a request parser to search for custom query var
function plugin_z_parse_request()
{
/** @global WP $wp */
global $wp;
if(array_key_exists('plugin_z_custom_rewrite_rule', $wp->query_vars))
{
wp_die('plugin-z route active', 'plugin-z', array('response' => 200));
}
}
add_action('parse_request', 'plugin_z_parse_request', 0);
?>