Where, When, & How to Properly Flush Rewrite Rules Within the Scope of a Plugin?

I am having a bit of a strange issue with rewrite rules not flushing properly.

I have tried using flush_rewrite_rules(); and flush_rewrite_rules(true);.

Read More

I have also tried globalizing $wp_rewrite using $wp_rewrite->flush_rules(); and $wp_rewrite->flush_rules(true);

Neither of which appear to be flushing the rewrite rules correctly. Those calls are indeed flushing the rewrite rules when called. How do I know this? Using the solution for debugging rewrite rule flushing.

Currently, I have rewrite rules flushed on plugin activation and plugin deactivation. No issues there.

I have an plugin administration settings page for users to configure the plugin. Some of the settings adjust the permalink structure, so the rewrite rules are required to be flushed on plugin administration settings page “Save Settings”. (Uses the standard update_option();) for saving settings.

I would like to note that depending on the settings specified, custom post types are created to match user specified settings. So the rewrite rules must be flushed immediately after the settings are saved. This is where things aren’t functioning appropriately.

The above link solution for debugging rewrite rules provided by @toscho is displaying that it’s flushing tons of rewrite rules. However, when visiting the custom post type singular item, or even custom post type archive for that matter, each return as 404 errors.

The custom post type is registered correctly and appropriately. I know for certain that is not the issue.

Immediately following with the plugin administration page settings save. The custom post types are created, permalink structure is adjusted, and all rewrite rules are attempted to be flushed.

The custom post types are then loaded always, and loaded on init like normal.

For some reason, the rewrite rules aren’t flushing properly, because like I said before, visiting singular or archive sections of the custom post type return 404 errors.

Now the weird part, if all I do is simply visit the administration permalinks settings page, and then go back to the front end to view either singular or archive sections of the custom post type, they magically work as expected.

What does that administration permalinks settings page do that I’m not doing that allows the rewrite rules to flush appropriately and mine do not?

I mean, as a temporary solution, I am redirecting the user to the administration permalinks settings page after saving the plugin administration settings page, but this is not an ideal solution. I’d prefer the rewrite rules just flush properly within my plugin’s code.

Is there a certain point in WordPress where flushing the rewrite rules just doesn’t flush ALL the rules anymore?

admin_menu – Plugin settings page is added to WordPress administration.

add_options_page() – Plugin settings page is added under Settings menu.

Settings page is rendered in the callback for add_options_page(). This is also where $_POST is processed for updating plugin settings and flushing rewrite rules.

Since this is already a long question, I would be willing to provide code blocks (if it helps) in an offsite link that to assist producing a valid answer.

Related posts

7 comments

  1. The best place to flush rewrite rules is on plugin activation/deactivation.

    function myplugin_activate() {
        // register taxonomies/post types here
        flush_rewrite_rules();
    }
    
    register_activation_hook( __FILE__, 'myplugin_activate' );
    
    function myplugin_deactivate() {
        flush_rewrite_rules();
    }
    register_deactivation_hook( __FILE__, 'myplugin_deactivate' );
    

    See the codex article

    Apologies in advance, I didn’t make it all the way through your question, so this is a bit of a cookie cutter response.

  2. Hard to tell what’s wrong, without seeing your code. But after saving some settings it’s practical to hook into admin_init like shown below to flush your rewrite rules.

    Code:

    add_action('admin_init', 'wpse_123401_plugin_settings_flush_rewrite');
    function wpse_123401_plugin_settings_flush_rewrite() {
        if ( get_option('plugin_settings_have_changed') == true ) {
            flush_rewrite_rules();
            update_option('plugin_settings_have_changed', false);
        }
    }
    

    You have to set the option some where at your settings page or to be exact some where in the process of saving the settings. Doing it without the option is bad, because you don’t want to flush the rules every time.

    Note: untested

  3. I had a post types class file which was responsible for reading the plugin’s option settings, and creating the necessary custom post types based on the user specified settings.

    This post types class file was loaded on the hook init.

    I figured all I would need to do then was update the plugin settings, then flush the rewrite rules. Since the post types class was already loaded based off the plugin settings. But with administration pages, they’re loaded AFTER the init hook.

    The post types were never actually registered, because the settings weren’t actually set yet. The post types registration class ended up terminating prematurely with no post types registered.

    The solution was:

    1. Update the plugin settings.
    2. Load the post types class file ONLY one time here so that the new
      rewrite rules are created.
    3. Flush the rewrite rules.

    (Previously… step2 was missing — As mentioned above…)

    From now on, post types will be loaded on the init hook, and will have settings specified already, allowing post types to be created and paired with appropriate rewrite rules.

    For what ever reason, I had to add a JavaScript call to redirect to the current page, after performing the above three steps.

    I also had to add a call to flush_rewrite_rules(); on the plugin’s administration settings page.

    So for the sake of ensuring everything is flushed…

    Step 1) Navigate to plugin’s administration settings page. — Initial flush.

    Step 2) Update plugin settings. — Second flush.

    Step 3) Page redirects to plugin’s settings page. Causing the … Third and final flush (same as initial flush — Done automatically when plugin’s settings page is visited)

    I’m not saying this is a practical solution, but it worked for me. Very strange issue and most likely has to do with my coding infrastructure.

  4. I was having exactly the same problem. In my plugin, I have post types that are dynamically created. They can therefore not be registered via register_post_type() in a static method during the activation_hook and are therefore not yet active when flush_rewrite_rules() is run during this hook (which is normally the recommended way of flushing rewrite rules).

    The cleanest solution I could come up with in the end was to flush the rewrite rules after registration of the post types, but of course only if such flushing was actually necessary (because the operation is slow). In my case I actually have several custom post types that inherit from a single base class and so it was desirable to implement the code that does the flushing there.

    Whether flushing is necessary can be decided by looking at the output of get_option( 'rewrite_rules' ):

    class MyPostTypeClass {
    
    public final function register_as_custom_post_type() {
        ...   //do all the setup of your post type here     
        $args = array(
                      ... //populate the other arguments as you see fit
                      'rewrite' => array('slug' => 'slug-of-your-post-type')
                     );
        register_post_type('post-type-name-of-your-post-type', $args );
    
        $rewrite_rules_must_be_fluhed = true;
        foreach( get_option( 'rewrite_rules' ) as $key => $rule)
            if(strpos($key, $args['rewrite']['slug'] ) === 0)
            {
                $rewrite_rules_must_be_fluhed = false;
                break;
            }
        if($rewrite_rules_must_be_fluhed)
            flush_rewrite_rules(true);
    }
    }
    

    Drawbacks:

    • Relies to some extent on which exact rewrite rules WP generates during register_post_type().
    • Checking whether flushing is necessary during each page load also creates some overhead.

    Advantages:

    • Fully encapsulated in the class representing the post type.
    • Only flushes the rewrite rules if really necessary.

    Only use this if you cannot register your post type in a static function that can call during both init and the activation_hook!

    The dependence on how the rewrite rules generated during register_post_type() look like can be mitigated by replacing the test if(strpos($key, $args['rewrite']['slug'] ) === 0) with something more elaborate, i.e., a regular expression.

  5. @tazo-todua this worked for me too when using multisite.

    add_action( 'wpmu_new_blog', 'set_my_permalink_structure', 11, 2 );
    
    function set_my_permalink_structure( $blog_id ) {
    
        switch_to_blog( $blog_id );
    
        global $wp_rewrite;
        $wp_rewrite->set_permalink_structure( '/%postname%/' );
        $wp_rewrite->flush_rules();
        $wp_rewrite->init();
    
        restore_current_blog();
    }
    
  6. If you want to flush_rewrite_rules when ANY plugin is activated or deactivated

    add_action('init', function(){
    
        // Get the path of every plugin installed
        $pluginsInstalledPaths = glob(WP_PLUGIN_DIR . '/*', GLOB_ONLYDIR);
    
        foreach ($pluginsInstalledPaths as $pluginPath) {
            // Get all PHP files available in the plugin root directory
            $files = glob($pluginPath . '/*.php');
            foreach ($files as $file) {
                $contents = file_get_contents($file);
                // If the file contains "Plugin Name" it is the main plugin file
                if (preg_match('//**[^/]*?Plugin Name:/', $contents)) {
                    add_action('activate_' . plugin_basename($file), function(){
                        flush_rewrite_rules();
                    });
                    add_action('deactivate_' . plugin_basename($file), function(){
                        flush_rewrite_rules();
                    });
                }
            }
        }
    });
    
  7. MY found SOLUTION WAS:

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

Comments are closed.