How to reliably flush rewrite rules on multisite?

Let’s say you have a plugin which need to flush rewrite rules. You do it all properly with activation hook and adding flush late, so everything is smooth and compatible.

And then one fine day someone tries to run it on multisite.

Instead of a simple scenarios like:

  1. WordPress site is created
  2. Plugin is installed and activated

You now have nightmare scenarios like:

  1. Plugin is installed and network activated
  2. New WordPress site (or a hundred) in multisite is created

In theory it should just work, right? In practice it goes wrong in spectacular ways:

  • $wp_rewrite state can be from the wrong site
  • switch_to_blog() doesn’t track rewrite state either
  • the “later” part might happen in different blog entirely
  • all those other plugins, you are supposed to play nice with, might not be consistently enabled on different sites

For example you can see this issue how trying to do it right blows away permalinks on main site every time new site is created.

So how would plugin go about reliably flushing rewrite rules in multisite:

  1. When new site is created, for the site?
  2. When existing site is activated from inactive, for the site?
  3. When plugin is network activated, for every site?
  4. When plugin is network deactivated, for every site?
  5. Possibly in other scenarios, involving rewrite global context getting changed?

1

Note: this is an incomplete answer which will be expanded upon incrementally


The only reliable way to flush rewrite rules in multisite, without potentially destroying the permalink structure of the primary and or any other blog context (depending upon how and what you a switching to and from) is to flush rewrite rules in a given context like so:

global $wp_rewrite;
$wp_rewrite->init(); //important...
$wp_rewrite->flush_rules();

The above ensures that correct permalink structure for the given context is retrieved and set prior to constructing the rewrite rules and commiting the changes to the database.

This does not apply to single site where context does not matter, because there is only one context.

flush_rewrite_rules() in my opinion is flawed in the premise that it assumes the correct context, but does not take into account our use of switch_to_blog for which completely changes the context and leaves us in dangerous territory if we try to flush rules, potentially.

This is what the internals of flush_rewrite_rules() looks like:

function flush_rewrite_rules( $hard = true ) {
    global $wp_rewrite;
    $wp_rewrite->flush_rules( $hard );
}

I cannot think of a reason why it shouldn’t look like this:

function flush_rewrite_rules( $hard = true ) {
    global $wp_rewrite;
    $wp_rewrite->init(); //hello....
    $wp_rewrite->flush_rules( $hard );
}

…especially when you consider that the constructor of WP_Rewrite does what? It does this…

public function __construct() {
    $this->init();
}

Touching on your first point of concern to further this line of though,

So how would plugin go about reliably flushing rewrite rules in multisite:

  • When new site is created, for the site?

Let’s look at what WordPress core will notably call during this process:

  • first wpmu_create_blog()
  • which then calls install_blog() which in turn calls populate_options()
  • then populate_options() sets default permalink structure in options table
  • after install_blog() has ran, wp_install_defaults() then gets called
  • then wp_install_defaults() flushes the rewrite rules for the newly created site before finally switching back to the current blog via restore_current_blog().

Importantly to note is that wp_install_defaults() flushes rules exactly as I’ve suggested above above:

$wp_rewrite->init();
$wp_rewrite->flush_rules();

…because that’s the only way to be sure the correct permalink_structure and rules are built for the current context.

Also in the problem evidenced within the Github issue, the reason why the user experienced the following behavior:

When a new site is created it breaks the post level permalinks only on the top level site – in most permalink configurations but not all:

These 2 formats work correctly.

Default – Works as expected

Day & Name – Works as expected

…is because if the primary blog has a Day & Name permalink structure /%year%/%monthnum%/%day%/%postname%/, when a new site is created, it too has a Day & Name permalink structure /%year%/%monthnum%/%day%/%postname%/ by default which is why no notable problem presents itself when the Yoast SEO plugin flushes rewrite rules on the shutdown hook.

Leave a Comment