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);.

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.

  • 1
    it sounds like maybe you've got the order of things wrong, it's hard to say without seeing some code. the permalinks admin page just calls flush_rewrite_rules, which just deletes the rewrite_rules option and regenerates it, you can open the file wp-admin/options-permalinks.php and see where this happens. since this operation just deletes the whole option, it's not possible to partially flush rules.
    – Milo
    Nov 15, 2013 at 18:27
  • @Milo I think you're right. I have a class which is loaded on init which registers the post types. I figured, the page settings were being saved, and the page would reload... then firing the init hook again to register necessary post types. So I figured the post types would already be loaded, and all I needed to do was update the option, then flush the rewrite rules from my plugin settings page. I'll post an answer of how I figured out a solution. Nov 15, 2013 at 19:21
  • Just a warning flush_rewrite_rules() in my plugin ended up being part of the problem for me. I deleted the php hook and ended up just updating the permalinks manually and my CPT 404 errors disappeared.
    – myol
    Oct 20, 2014 at 8:38

8 Answers 8


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

function myplugin_activate() {
    // register taxonomies/post types here

register_activation_hook( __FILE__, 'myplugin_activate' );

function myplugin_deactivate() {
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.

  • 1
    Thanks for your suggestion, but I am aware of this. The issue isn't about plugin activation / plugin deactivation. It has to do with users changing settings to the already active plugin, which adjust the rewrite rules, thus requiring a flush. Nov 15, 2013 at 19:17
  • 1
    I thought that might be the case, but I was pretty tired when I was reading your question. ialocin's solution looks promising, hope you worked it out. Nov 17, 2013 at 0:22

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.


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 ) {
        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
    Or you could use a transient maybe? But definitely +1 for not flushing the rules on every admin_init. Nov 17, 2013 at 0:23
  • You're of course right about transient, I guess I opted for *_option() because of settings page. @helgatheviking Nov 27, 2013 at 22:11
  • 1
    This was very helpful. I had a similar situation to Michael. I ended up incorporating helga's suggestion on using a transient and I set it in the validation function for the settings. I ended up needing to set the transient to false after flushing or else it just kept flushing on every admin page load until the transient expired. So functionally using an option or a transient was the same. I guess transient might be nice just to keep the options table a little cleaner. But a minor point.
    – MatthewLee
    Feb 15, 2014 at 17:59
  • The difference is minor indeed, especially if the transients is permanent, non-expiring, but of course transients have other capabilities, benefits that are nice to have. @MatthewLee Feb 15, 2014 at 19:34

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.


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;


  • 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.


  • 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.


@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%/' );


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(){
                add_action('deactivate_' . plugin_basename($file), function(){


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

Add this to any time you want to clear rewrite rules, e.g. on your plugin activation or any event you want.


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.