Jeremy Felt

Flushing rewrite rules in WordPress multisite for fun and profit

Developing plugins and introducing new rewrite rules for features on single site installations of WordPress is pretty straight forward. Via register_activation_hook(), you’re able to setup a task that fires flush_rewrite_rules() and you can safely assume job well done.

But for multisite? A series of bad choices awaits.

Everything seems normal. The activation hook fires and even gives you a parameter to know if this is a network activation.

Your first option is to handle it exactly the same as a single site installation. flush_rewrite_rules() will fire, the rewrite rules for the current site will be built, and the network admin will be on their own to figure how these should be applied to the remaining sites.

It seems strange to say, but I’d really like you to choose this one.

Instead, you could detect if this is multisite and if the plugin is being activated network wide. At that point a few other options appear.

Grab all of the sites in wp_blogs and run flush_rewrite_rules() on each via switch_to_blog().

This sounds excellent. It’s horrible.

// Please don't. :)
$query = "SELECT * FROM $wpdb->blogs";
$sites = $wpdb->get_results( $query );
foreach( $sites as $site ) {
    switch_to_blog( $site->blog_id );
    flush_rewrite_rules();
    restore_current_blog();
}

Switching sites with switch_to_blog() changes the current context for a few things. The $wpdb global knows where it is, your cache keys are generated properly. But! There is absolutely no awareness of what plugins or themes would be loaded on the other site, only those in process now.

So. We get this (paraphrasing):

// Delete the rewrite_rules option from
// the switched site. Good!
delete_option( 'rewrite_rules' );

// Build the permalink structure in the
// context of the main site. Bad!
$this->rewrite_rules();

// Update the rewrite_rules option for the
// switched site. Horrible!
update_option( 'rewrite_rules', $this->rules );

All of a sudden every single site on the network has the same rewrite rules. 🔥🔥🔥

Grab all of the sites in wp_blogs and run delete_option( ‘rewrite_rules’ ) on each via switch_to_blog()

This is much, much closer. But still horrible!

// Warmer.... but please don't.
$query = "SELECT * FROM $wpdb->blogs";
$sites = $wpdb->get_results( $query );

foreach( $sites as $site ) {
    switch_to_blog( $site->blog_id );
    delete_option( 'rewrite_rules' );
    restore_current_blog();
}

It’s closer because it works. Deleting the rewrite_rules option occurs in the context of the switched site and you don’t need to worry about plugins registering their rewrite rules. On the next page load for that site, the rewrite rules will be built fresh and nothing will be broken.

It’s horrible because large networks. Ugh.

Really, not much of a deal at 10, 50, 100, or even 1000 sites. But 10000, 100000? Granted, it only takes a few seconds to delete the rewrite rules on all of these sites. But what’s going on with other various plugins on that next page load? If I have a network with large traffic on many sites, there’s going to be a small groan where the server has to generate and then store those rewrite rules in the database.

It’s up to the network administrator to draw that line, not the plugin.

You can mitigate this by checking wp_is_large_network() before taking these drastic actions. A network admin not wanting anything crazy to happen will be thankful.

// Much better. :)
if ( wp_is_large_network() ) {
    return;
}

// But watch out...
$query = "SELECT * FROM $wpdb->blogs";
$sites = $wpdb->get_results( $query );
foreach( $sites as $site ) {
    switch_to_blog( $site->blog_id );
    delete_option( 'rewrite_rules' );
    restore_current_blog();
}

But it’s also horrible because of multiple networks.

When a plugin is activated on a network, it’s stored in an array of network activated plugins in the meta for that network. A blanket query of wp_blogs often doesn’t account for the site_id. If you do go the drastic route and delete all rewrite rules options, make sure to do it on only sites of the current network.

// Much better...
if ( wp_is_large_network() ) {
    return;
}

// ...and we're probably still friends.
$query = "SELECT * FROM $wpdb->blogs WHERE site_id = 1";
$sites = $wpdb->get_results( $query );
foreach( $sites as $site ) {
    switch_to_blog( $site->blog_id );
    delete_option( 'rewrite_rules' );
    restore_current_blog();
}

And really, ignore all of my examples and use wp_get_sites(). Just be sure to familiarize yourself with the arguments so that you know how to get something different than 100 sites on the main network by default.

// Much better...
if ( wp_is_large_network() ) {
    return;
}

// ...and we're probably still friends.
$sites = wp_get_sites( array( 'network' => 1, 'limit' => 1000 ) );
foreach( $sites as $site ) {
    switch_to_blog( $site->blog_id );
    delete_option( 'rewrite_rules' );
    restore_current_blog();
}

Alas.

At this point you can probably feel yourself trying to make the right decision and giving up in confusion. That’s okay. Things have been this way for a while and they’re likely not going to change any time soon.

I honestly think your best option is to show a notice after network activation to the network admin letting them know rewrite rules have been changed and that they should take steps to flush them across the network.

Please: Look through your plugin code and make sure that activation is not firing flush_rewrite_rules() on every site. Every other answer is better than this.

If you’re comfortable, flush the rewrite rules on smaller networks by deleting the rewrite rules option on each site. This could be a little risky, but I’ll take it.

And if nothing else, just don’t handle activation on multisite. It’s a bummer, but will work itself out with a small amount of confusion.

Responses and reactions

Replies

Antonio replied on 

Great post, but i have a questions. Since we are reseting the network, how could to modify all the permalinks to a custom format? like post /%post_id%/

Sal Ferrarello replied on 

Great post, thank you. One quick note, the code in the article uses wp_get_sites() which has been deprecated. Replacing it with get_sites() does the trick.

Thanks again.

khromov replied on 

Thanks for the insightful post Jeremy - I was really scratching my head wondering why flush_rewrite_rules() didn't work properly with switch_to_blog()...

Leave a Reply

Your email address will not be published. Required fields are marked *

The only requirement for your mention to be recognized is a link to this post in your post's content. You can update or delete your post and then re-submit the URL in the form to update or remove your response from this page.

Learn more about Webmentions.