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 –
- 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.
- Regenerates new rewrite rules from scratch using the wp_rewrite_rules function. This function regenerates the rules and updates the ‘rewrite_rules’ option.
- 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(); }
Note – flush_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!
Ian Dunn says
A simpler solution for Multisite is to re-initialize the rewrite rules before flushing them:
https://gist.github.com/iandunn/c5a983d1a9d4b09d6d20
ShibaShake says
Thanks! Will have to try it out.
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!
snowboard mommy says
(actually it’s the $permastruct var that’s being passed, sorry about that)
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.
Thomas 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’));
staff 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.
rashid 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?
ShibaShake says
$gallery_structure contains a regular WP permalink structure, e.g. /galleries/%year%/%monthnum%/%gallery%.
http://shibashake.com/wordpress-theme/custom-post-type-permalinks-part-2