How to Flush Permalink Rules with flush_rules()

If we want to create our own permalinks for custom post types, we can do so by using the WP_Rewrite WordPress object.

We can also create our own permalink rules from scratch, by using the same object.

One very important function while creating permalink rules is the $wp_rewrite->flush_rules() function. This function does the following –

  1. Deletes the ‘rewrite_rules’ option. This is where all the permalink rules for our blog are stored. When someone clicks on one of our blog pages, the parse_request function retrieves these rewrite rules and translates them into a query request.
  2. Regenerates new rewrite rules from scratch using the wp_rewrite_rules function. This function regenerates the rules and updates the ‘rewrite_rules’ option.
  3. It may also write to the .htaccess file if necessary.

When we add new permalink structures with register_post_type or add_permastruct, the structures only get added to the global $wp_rewrite object. It does not manifest as a rewrite rule until after we execute the $wp_rewrite->flush_rules() command.

That is why if we have a plugin that adds new permalink rules (including through register_post_type) we want to run $wp_rewrite->flush_rules()

  • During the activation process to ensure that our new permalink structures show up in the blog rewrite rules.
  • During the deactivation process to remove our permalink structures from the blog rewrite rules.
function _activate() {
    	global $wp_rewrite;
	$wp_rewrite->add_rewrite_tag("%gallery%", '([^/]+)', "gallery=");
	$wp_rewrite->add_permastruct('gallery', $gallery_structure, false);    
	$wp_rewrite->flush_rules();	
}

function _deactivate() {
    	global $wp_rewrite;		
	$wp_rewrite->add_permastruct( 'gallery', '');
	$wp_rewrite->flush_rules();	
}

Noteflush_rules is also run by the WordPress system when we Save Changes in Settings >> Permalinks. Therefore, if our blog permalinks are not working properly, we can manually flush our permalink rules by going into Settings >> Permalinks and doing a save.

Flushing Permalink Rules in WordPress Multi-Site

In WordPress multi-site, we may need to flush rewrite rules for multiple blogs during network activation and deactivation.

However, there is a fly in the ointment. When we change blogs using set_blog_id, we are only changing the database context. The global $wp_rewrite object remains the same, i.e., it is the $wp_rewrite object of the original blog from where we executed the network activate or deactivate.

If we cycle through the blogs of a multi-site configuration and simply execute flush_rules, the wrong permalink rules from the non-updated $wp_rewrite object will be written into the ‘rewrite_rules’ option of each of the blogs. In effect, we would mess up the permalink rules of all our blogs except for the original activation/deactivation blog.

To properly flush permalink rules, we must limit our changes to the database and not access blog specific data stored within the $wp_rewrite object. Below, we do this by adding new permalink rules directly into the ‘rewrite_rules’ database option instead of using the flush_rules function.

function shiba_add_rewrite_rules($permastruct, $ep_mask=EP_NONE) {
	if (!$permastruct)return;
	global $wp_rewrite;
	$wp_rewrite->matches = 'matches'; // this is necessary to write the rules properly
	$new_rules = $wp_rewrite->generate_rewrite_rules($permastruct, $ep_mask);
	$rules = get_option('rewrite_rules');
	$rules = array_merge($new_rules, $rules);
	update_option('rewrite_rules', $rules);				
}

// remove all rewrite rules for a given permastruct
function shiba_remove_rewrite_rules($permastruct, $ep_mask=EP_NONE) {
	// replace all tags within permastruct
	if (!$permastruct)return;
	global $wp_rewrite;
	$wp_rewrite->matches = 'matches';
	$remove_rules = $wp_rewrite->generate_rewrite_rules($permastruct);
	$num_rules = count($remove_rules);
	// Get first rule
	$rule1 = reset($remove_rules); $key_rule1 = key($remove_rules);
		
	$rules = get_option('rewrite_rules');
	$i = $num_rules;
	foreach ($rules as $pretty_link => $query_link) {
		// find the first rule
		if (($pretty_link == $key_rule1) && ($query_link == $rule1)) { $i = 0; }
		if ($i < $num_rules) {
			// Delete next $num_rules
			unset($rules[$pretty_link]); $i++;
		}	
	}
	update_option('rewrite_rules', $rules);
}

Line 5 – Generate permalink rules for our plugin permalink structures. Even though we are using $wp_rewrite here, we are only using one of its functions. Most importantly, that function does not rely on any blog specific data or state stored in the $wp_rewrite object.
Line 6 – Get the ‘rewrite rules’ for the current blog. This is a database operation which does get updated with set_blog_id.
Line 7 – Merge our new permalink rules with the previous blog rules.
Line 8 – Update the ‘rewrite rules’ for the current blog.

In the shiba_remove_rewrite_rules function we use a similar process to remove the permalink rules added by our plugin.

We Are Done!

Now we can flush permalink rules in our regular and multi-site blogs!

Related Articles

Comments

  1. snowboard mommy says

    Hi Shiba,

    Thanks for this information. How/where do we call the rewrite functions for multisite? Is it part of the activation routine, and where is the $permalink variable is being passed from?

    Thanks!

    • ShibaShake says

      I pass in the permastruct variable myself. It is simply the permalink structure I want to set my object to, for example-
      “/gallery/%monthnum%/%category%/%gallery%”

      I call the flush functions during activation and deactivation.

      • snowboard mommy says

        Great! That makes sense. Thank you for the speedy reply. You are pretty, awesome, you know, right? : )

        • snowboard mommy says

          I’m back … I was having trouble with WordPress adding yet another “/blog/” before my custom post type slug. I had to set the “with_front” property of the custom post type rewrite setting to “False” (and not put it in quotes!!! yeah, that was kinda stupid). Anyway, I just though I’d mention it in case anyone else crazy enough to use both Multisite and Custom Post Types comes along.

  2. says

    You can also add rewrite rules within the rewrite_rules_array filter directly. I wrote up this gem earlier today with the purpose of manually creating callback linked action pages. The usage would be something like:

    new action_page(‘cart’, array(‘items’));
    add_action(‘admin_post_nopriv_cart’, array($this, ‘display_cart’));
    add_action(‘admin_post_cart’, array($this, ‘display_cart’));

    <?php
    action_page::init();
    class action_page {
    
    	protected $action;
    	protected $vars;
    		
    	public static function init() {
    		add_action('template_redirect', array(__CLASS__, 'action_redirect'));	
    	}
    	
    	public function __construct( $action, $vars = array()) {
    	
    		$this->action = $action;
    		$this->vars = $vars;
    				
    		add_filter('query_vars', array($this, 'add_query_vars'));
    		add_filter('rewrite_rules_array', array($this, 'add_rewrite_rules'));
    		add_filter("{$action}_query_vars", array($this, 'import_vars'));
    	}
    	
    	public function add_query_vars( $vars ) {
    		$vars[] = 'action';
    		return array_merge($vars, $this->vars);
    	}
    	
    	public function add_rewrite_rules( $rules ) {
    		$regex = implode("/", $this->regex());
    		$regex = !empty($regex) ? "{$regex}/?$" : "?$";
    		$rules = array("{$this->action}/{$regex}" => $this->match()) + $rules;
    		return $rules;
    	}
    	
    	protected function regex() {
    		$m = array_fill_keys($this->vars, "([^/]+)");
    		return apply_filters("{$this->action}_regex", $m);
    	}
    	
    	protected function match() {
    		$size = sizeof($this->vars);
    		$match = "/index.php?action={$this->action}";
    		for($i = 0; $i < $size; $i++) { 
    			$m = $i + 1;
    			$match .= "&{$this->vars[$i]}=\$matches[{$m}]"; 
    		}
    		return apply_filters("{$this->action}_match", $match);
    	}
    	
    	public function import_vars( $vars = array() ) {
    		global $wp_query;
    		$vars += array_intersect_key($wp_query->query_vars, array_flip($this->vars));
    		return $vars;
    	}
    	
    	
    	public static function action_redirect() {
    		if($action = get_query_var('action')) {
    			$vars = apply_filters("{$action}_query_vars", array());
    			$action = wp_validate_auth_cookie() ? "admin_post_{$action}" : "admin_post_nopriv_{$action}";
    			do_action($action, $vars);
    		}
    	}
    }
    
  3. says

    Still a bit confuse here.. I am trying to add a new category using the WP API (multiple sites), the permalinks are not refreshing.
    So when a visitor goes to the page on the site it throws a 404 error. I have to manually go to to wp-admin -> settings -> permalinks and save to solve the problem.

    Here is what I am doing that is not working:

    add_action(‘init’, ‘flush_permalinks’);

    function flush_permalinks(){
    global $wp_rewrite;
    $wp_rewrite->set_permalink_structure(‘/%postname%/’);
    //$wp_rewrite->flush_rules();
    // flush_rewrite_rules();
    }

    Could you find my error? Thanks.

  4. says

    what the $gallery_structure, in $wp_rewrite->add_permastruct(‘gallery’, $gallery_structure, false); contains, (I understand its a struct based on google search). BUt its initialized or assigned a value?

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>