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

  3. 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?

Speak Your Mind