I am writing a plugin that instantiates a custom post type (among other things). It is a multisite plugin and lives in the directory mu-plugins.

What is the best practice for handling flush_rewrite_rules() in this situation? For a 'normal' plugin you'd do this in an activation hook -- which is not going to be possible for a must-use plugin since those hooks are not available.

Since this is supposed to be a "one time" event after registering the custom post type, would it make sense to do something like this in my class that registers the CPT:

private function check_flush_my_CPT() {
    global $wp_rewrite;
    if ( !get_option('my_plugin_firstrun') ) {
        update_option('my_plugin_firstrun', 'yes');

public function register_my_CPT() {
   // do all the CPT setup steps for the $args array...  

   register_post_type('my_CPT', $args);

add_action( 'init', array(&$this, 'register_my_CPT' ) );

So, the CPT registration happens on every 'init' action -- but if I have this right, the rewrite rules flush only happens once. Ever.

Am I on the right track?

(edit): I just tried it; my CPT is giving a 404 not found error, so the rewrites rules are not working :-(

(edit #2): I did try the solution for accessing the global variable as shown in this question: How to reliably flush rewrite rules on multisite? - I will update my code example above to show this. Unfortunately I am still getting 404 error when trying to load a CPT. I see that the rewrite rules are being stored in the database, it just seems like they are not being used. I'm lost.

  • 1
    Possible duplicate of How to reliably flush rewrite rules on multisite? Nov 28, 2015 at 5:10
  • There really is no proper way to do this. This is basically the best at the time of writing this comment. Nov 28, 2015 at 5:12
  • @PieterGoosen - thanks, I did see that earlier, and I updated my question to consider the other solution. Still not working. I wonder if I broke something by executing flush_rewrite_rules() before seeing the other answer? Maybe I have to delete my test site and re-establish it, to accurately test the correct procedure?
    – C C
    Nov 28, 2015 at 5:23
  • That might be possible. Executing flush rewrite rules wrongly in a multisite can break all the rewrite rules in your network. If I where you, I would just start fresh. Just hope is this a local install end not a live site ;-) Nov 28, 2015 at 5:25
  • right...I will try that. And no, it's a test site and I think I can wipe out the DB and re-set the network config in my sleep by now! :-)
    – C C
    Nov 28, 2015 at 14:39

4 Answers 4


The flush_rewrite_rules function is reliable in some contexts like a theme or a plugin based on hooks but I'm not sure if it works for a mu-plugin

My statement is based on the fact that WordPress is initialized in this way:

  • call the wp-settings.php file
  • call the do_action( 'muplugins_loaded' ); hook, here your plugin is initialized
  • call $GLOBALS['wp_rewrite'] = new WP_Rewrite(); here the method flush_rules is initialized and available from now on
  • do_action( 'setup_theme' ); is called and I bet all my money that on this hook the flush_rewrite_rules will work


Personally, I find reliable the deletion of the rewrite_rules option.



update_option('rewrite_rules', '' );

Whenever WordPress will lack the rewrite_rules it will build them back, this is also what the flush_rules method does.

There are points in WordPress execution flow where functions like this aren't available. even in the core of WordPress I found this statement

// Rewrite rules can't be flushed during switch to blog.
delete_option( 'rewrite_rules' );

The only problem would be the performance, don't do this on every request because it is a hard process to build them back. As I can see you want to flush them only at the first call and this is a good thing.

P.S: I'm not such a self-promo fan but I've also written an article about this long time ago and I think it still stands up for this


MU-Plugins are considered to be always activated, so the register_activation_hook and register_deactivation_hook hooks are never called.

Instead add a plugin version, which must be updated whenever any changes affecting the rewrite rules are made:

namespace My\Namespace;

define( 'MY_CPTS_REWRITES', '2020-05-19.1' );

function maybe_flush_rewrite_rules() {
    if ( update_option('MY_CPTS_REWRITES', MY_CPTS_REWRITES) ) {

add_action( 'init', __NAMESPACE__ . '\\maybe_flush_rewrite_rules', 11 );

The hook priority is set to 11 to ensure it's ran after the post type is registered, (in case the default priority 10 is used).

Note that by testing for update_option() we avoid a race condition that exists on high traffic sites where between a get_option() call and an update_option() call the value is changed on another request, causing eternal flushes. update_option() includes a check for the old value of the option and only returns true if the option is changed.

To test this works, update the custom post type’s rewrite slug to anything else, then view one of the items in the front end and it should contain the updated slug. If it updates without updating the version number, something else is updating the permalinks which should be investigated.


If your mu-plugin has options I would put the flush right after updating them:

update_option( 'my_options', $values );
// Flush rules after install

I am using follow code. It's check if current file has changed, then flush rewrite rules.

add_action('init', function() {
   $md5 = md5_file(__FILE__);
   if (get_option(__FILE__) !== $md5) {
     update_option(__FILE__, $md5);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.