How to make WordPress permalinks conditional with Permalink Manager?

Recently, I spotted a few topics where WordPress users asked if it is possible to make the permalinks structure be based on eg. category or the date when the post was published.

I did not find any tailored or simple solution, so I decided to publish this short tutorial for more advanced WordPress users.

All the snippets posted below can be used in both Permalink Manager Lite and Permalink Manager Pro. You can easily customize them and make custom post types permalinks conditional using different conditions.

The shorter version of this tutorial can be found here.

Table of contents

    Essential information

    The default permalinks can be programmatically adjusted with following filter and its 5 variables:

    permalink_manager_filter_default_post_uri($default_uri, $native_slug, $post, $slug, $native_uri)
    • $default_uri is a final URI that we can filter/change
    • $native_slug is a native, unfiltered post’s slug (it may differ from slug defined with $slug variable if “Force custom slugs” is enabled in Permalink Manager settings)
    • $post is a post object, an instance of WP_Post class, see more info here
    • $slug is a slug used normally by Permalink Manager
    • $native_uri, the snippet should not be used to filter native URIs and that is why you should start your hook with following “if” expression: if($native_uri) { return $default_uri; }

    With Permalink Manager all custom URIs are stored in a single array (as I described it here) and therefore the new conditional permastructures will be used only for new posts/pages, but you can easily regenerate the old permalinks assigned to already created posts using one of included tools (“Permalink Manager -> Tools -> Regnerate/reset”).

    How this filter can and should be used?

    It may be particularly useful if you would like to use multiple permastructures for any post type (posts, pages, products and of course custom post types). The new conditional permalinks structure/format may be dependent for example on categories, taxonomies assigned to post or other conditions such as post meta fields: author, date, title (all of them are passed with $post object).

    If you would like to add extra parts to custom permalinks, please make sure that they are sanitized (I would recommend to use sanitize_title() function).
    The snippet should return the string without wrapping slashes and contain only URIs:
    // Correct: blog/lorem-ipsum
    // Incorrect: /blog/lorem-ipsum/
    // Incorrect: http://example.com/blog/lorem-ipsum/
    return trim($default_uri, "/");
    

    Example 1. Posts conditional permalinks

    As mentioned above, in the first line of code we need to check if the custom URI we are modifying is not a native URI:

    if($native_uri) { return $default_uri; }

    You can combine extra conditions here, for example limit the filter to selected post type:

    if($native_uri || $post->post_type != 'post') { return $default_uri; }

    or multiple post types:

    if($native_uri || in_array($post->post_type, array('post', 'page', 'product'))) { return $default_uri; }

    Shortly after we declare variables with some example data (in this particular case: year when the post was published & the post author login):

    $year = date("Y", strtotime($post->post_date));
    $author = get_the_author_meta('user_login', $post->post_author);
    

    Now, we can declare three sample permastructures (permalink formats). You do not need to copy all of them in their original form, as they are presented here only to indicate the potential applications of this filter from a broader perspective.

    Case 1. Different permalinks for posts assigned to specific categories

    Example URLs:

    # Posts in "Blog" category
    http://example.com/blog/lorem-ipsum/
    # Posts in "Press releases" category
    http://example.com/news-and-resources/press-releases/2016/dolor-sit-amet/ 
    
    If you would like to check for custom taxonomy term, you will need to replace “in_category()” with “has_term()” function.

    Please also note, that the second permastructure (used for posts permalinks assigned to “Press releases” category) includes an additional variable (publication date, declared in the beginning as $year).

    if(in_category('blog', $post)) {
    	$default_uri =  "blog/{$slug}";
    } else if(in_category('press-releases', $post)) {
    	$default_uri =  "news-and-resources/press-releases/{$year}/{$slug}";
    }
    

    Case 2. Different permalinks for posts published by specific authors

    Example URLs:

    # Posts published by John Doe
    http://example.com/interviews/2018/john-doe/etiam-ullamcorper/
    # Posts published by Jan Kowalski
    http://example.com/reportage/jan-kowalski/nulla-facilisis-sagittis-sed/
    

    In this case, we will use $author variable that we declared before to define two more variables ($author_name & $author_safename). The first one contains author’s first and last name combined and the second one is a sanitized form of the author’s name that can be used inside the custom URI.

    // Get the author full name
    $author_name = sprintf("%s %s", 
    	get_the_author_meta('first_name', $post->post_author), 
    	get_the_author_meta('last_name', $post->post_author)
    );
    
    // Sanitize the author name, so it can be safely used inside URI
    $author_safename = sanitize_title($author_name);
    
    if($author == 'john-doe') {
    	$default_uri =  "interviews/{$year}/{$author_safename}/{$slug}";
    } else if($author == 'jan-kowalski') {
    	$default_uri =  "reportage/{$author_safename}/{$slug}";
    }
    

    Case 3. Different permalinks for posts published in specific year

    Example URLs:

    # Posts published in or after 2017
    http://example.com/news-and-resources/malesuada-ultricies/
    # Posts published before 2017
    http://example.com/archive/2016/nulla-imperdiet/
    

    I would recommend to convert the $year variable to an integer when it is compared in “if” statement. Analogously to the previous case, the second permastructure also contains the post’s publication date added to the custom URI with $year variable.

    if(intval($year) >= 2017) {
    	$default_uri =  "news-and-resources/{$slug}";
    } else {
    	$default_uri =  "archive/{$year}/{$slug}";
    }
    

    Example 2. WooCommerce products conditional permalinks

    In this example, we will filter the custom permalinks used by custom post type posts (in this particular case, it will be “product” post type controlled by WooCommerce).

    Just like before, we need to start with a simple statement (we will filter only custom URIs assigned to “product” post type) and define some variables (product weight & SKU code) that can be used as conditions or be inserted to custom URIs after:

    if($native_uri || $post->post_type != 'product') { return $default_uri; }
    
    // Get WooCommerce product object & some meta data
    $product = wc_get_product($post->ID);
    $product_weight = $product->get_weight();
    $product_sku = $product->get_sku();
    
    // Get country of origin (set-up as a custom field) & prepare a sanitized string for URI
    $product_coo = get_post_meta($post->ID, 'country', true);
    

    Again, we need to declare our sample permastructures (permalink formats).

    Case 1. Different permalinks for products in specific categories

    Example URLs:

    # Products in "Toast" category
    http://shop.com/bread/bruschetta/
    # Products in "Honey" category
    http://shop.com/eco-products/blossom-honey-jar/
    

    This time, we have to use “has_term()” function to check meet the first condition (specific category). First argument in has_term() function may be term’s name, slug or ID.

    if(has_term('Toast', 'product_cat', $post)) {
    	$default_uri =  "bread/{$slug}";
    } else if(has_term('Honey', 'product_cat', $post)) {
    	$default_uri =  "eco-products/{$slug}";
    }
    

    Case 2. Different permalinks for products in specific categories & with specific custom fields

    Example URLs:

    # Products in "Milk" category and with custom field "country" value set to "Netherlands"
    http://shop.com/dairy/netherlands/gouda-cheese/
    # Products in "Drinks" category and with custom field "country" value set to "Poland"
    http://shop.com/alcohol/poland/vodka/zubrowka/ 
    

    This example is the more advanced version of the code presented in the previous case. It is extended with the second condition and the products’ custom fields values (see $product_coo variable) must have specific values to be filtered.

    As we are going to include the custom field (“country”) in both permastructures and therefore we need to sanitize it and declare a new variable ($product_safe_coo).

    // Sanitize "country of origin"
    $product_safe_coo = sanitize_title($product_coo);
    
    if(has_term('Milk', 'product_cat', $post) && ($product_coo == 'Netherlands')) {
    	$default_uri =  "dairy/{$product_safe_coo}/{$slug}";
    } else if(has_term('Drinks', 'product_cat', $post) && ($product_coo == 'Poland')) {
    	$default_uri =  "alcohol/{$product_safe_coo}/vodka/{$slug}";
    }
    

    Case 3. Different permalinks for products with specific weight (using WooCommerce API)

    Example URLs:

    # Products in "Milk" category and with custom field "country" value set to "Netherlands"
    http://shop.com/dairy/netherlands/gouda-cheese/
    # Products in "Drinks" category and with custom field "country" value set to "Poland"
    http://shop.com/alcohol/poland/vodka/zubrowka/ 
    

    It is a bit more sophisticated application, as we will use one of WooCommerce API functions (see how $product, $product_weight & $product_sku variables are defined above) and we will target only products that weigh more than 50 kilograms and also include the SKU number inside the custom URIs.

    Firstly, we will check if the product weight is larger than 50 kilograms and if so we will change its default permalink where sanitized SKU ID number is included.

    if($product_weight > 50) {
    	// Sanitize SKU
    	$product_safesku = sanitize_title($product_sku);
    	$default_uri = sprintf("overgauge/{$product_safesku}/{$slug}");
    }
    

    Case 4. Overwrite the default product permalinks for other products

    Example URLs:

    http://shop.com/shop/liquorice-chocolate/
    http://shop.com/shop/swedish-herrings/
    

    This part can be used after the other “if” statements (will be applied if any other condition was not met before).

    else {
    	$default_uri =  "shop/{$slug}";
    }
    

    If you would like to change all products permalinks programmatically (see “Example 3” below), you can use it alone (then it will be applied to all products default permalinks):

    Full snippets (simplified)

    Example 1 (conditional post permalinks)

    function pm_filter_default_post_uris($default_uri, $native_slug, $post, $slug, $native_uri) {
    	// Do not change native permalinks or differnt post types
    	if($native_uri || $post->post_type != 'post') { return $default_uri; }
    
    	// Get the year when the post was published
    	$year = date("Y", strtotime($post->post_date));
    
    	// Get post author meta - it can be user_login, user_email, ID or other field:
    	// Reference: https://developer.wordpress.org/reference/functions/get_the_author_meta/
    	$author = get_the_author_meta('user_login', $post->post_author);
    
    	// 1A. Blog posts (you can use either category name, slug or its ID)
    	// Example URL: http://example.com/blog/lorem-ipsum/
    	if(in_category('blog', $post)) {
    		$default_uri =  "blog/{$slug}";
    	}
    
    	// 1B. Press releases
    	// Example URL: http://example.com/news-and-resources/press-releases/2016/dolor-sit-amet/
    	else if(in_category('press-releases', $post)) {
    		$default_uri =  "news-and-resources/press-releases/{$year}/{$slug}";
    	}
    
    	// 2. Posts added by John Doe
    	// Example URL: http://example.com/interviews/2018/john-doe/etiam-ullamcorper/
    	else if($author == 'john-doe') {
    		// Get the author full name
    		$author_name = sprintf("%s %s", 
    			get_the_author_meta('first_name', $post->post_author), 
    			get_the_author_meta('last_name', $post->post_author)
    		);
    
    		// Sanitize the author name, so it can be safely used inside URI
    		$author_safename = sanitize_title($author_name);
    
    		$default_uri =  "interviews/{$year}/{$author_safename}/{$slug}";
    	}
    
    	// 3A. Posts published in and after 2017
    	// Example URL: http://example.com/news-and-resources/malesuada-ultricies/
    	else if(intval($year) >= 2017) {
    		$default_uri =  "news-and-resources/{$slug}";
    	}
    
    	// 3B. Posts published before 2017
    	// Example URL: http://example.com/archive/2016/nulla-imperdiet/
    	else {
    		$default_uri =  "archive/{$year}/{$slug}";
    	}
    
    	return trim($default_uri, "/");
    }
    add_filter('permalink_manager_filter_default_post_uri', 'pm_filter_default_post_uris', 10, 5);
    

    Example 2 (conditional product permalinks)

    function pm_filter_default_product_uris($default_uri, $native_slug, $post, $slug, $native_uri) {
    	// Do not change native permalinks or differnt post types
    	if($native_uri || $post->post_type != 'product') { return $default_uri; }
    
    	// Get WooCommerce product object & some meta data
    	$product = wc_get_product($post->ID);
    	$product_weight = $product->get_weight();
    	$product_sku = $product->get_sku();
    
    	// Get country of origin (set-up as a custom field) & prepare a sanitized string for URI
    	$product_coo = get_post_meta($post->ID, 'country', true);
    
    	// 1A. Products in "Toast" category
    	// Example URL: http://shop.com/bread/bruschetta/
    	else if(has_term('Toast', 'product_cat', $post)) {
    		$default_uri =  "bread/{$slug}";
    	}
    
    	// 1B. Products in "Honey" category
    	// Example URL: http://shop.com/honey/blossom-honey-jar/
    	else if(has_term('Honey', 'product_cat', $post)) {
    		$default_uri =  "eco-products/{$slug}";
    	}
    
    	// 2. Products in "Milk" category (you can use either category name, slug or its ID)
    	// Example URL: http://shop.com/dairy/netherlands/gouda-cheese/
    	if(has_term('Milk', 'product_cat', $post) && ($product_coo == 'Netherlands')) {
    		// Sanitize "country of origin"
    		$product_safe_coo = sanitize_title($product_coo);
    
    		$default_uri =  "dairy/{$product_safe_coo}/{$slug}";
    	}
    
    	// 3. Products heavier than 50kg with SKU inside permalink
    	// Example URL: http://shop.com/overgauge/abc-123-z/beer-keg/
    	else if($product_weight > 50) {
    		// Sanitize SKU
    		$product_safesku = sanitize_title($product_sku);
    		
    		$default_uri =  sprintf("overgauge/{$product_safesku}/{$slug}");
    	}
    
    	// 4. Other products
    	// Example URL: http://shop.com/shop/liquorice-chocolate/
    	else {
    		$default_uri =  "shop/{$slug}";
    	}
    
    	return trim($default_uri, "/");
    }
    add_filter('permalink_manager_filter_default_post_uri', 'pm_filter_default_product_uris', 10, 5);
    

    Example 3 (overwrite product permalinks settings)

    function pm_filter_default_product_uris($default_uri, $native_slug, $post, $slug, $native_uri) {
    	// Do not change native permalinks or differnt post types
    	if($native_uri || $post->post_type != 'product') { return $default_uri; }
    
    	// Get country of origin (set-up as a custom field) & prepare a sanitized string for URI
    	$product_coo = get_post_meta($post->ID, 'country', true);
    
    	// Sanitize "country of origin"
    	$product_safe_coo = sanitize_title($product_coo);
    
    	// Change programmatically all "product" permalinks
    	$default_uri = (!empty($product_safe_coo)) ? "shop/{$product_safe_coo}/{$slug}" : "shop/{$slug}";
    
    	return trim($default_uri, "/");
    }
    add_filter('permalink_manager_filter_default_post_uri', 'pm_filter_default_product_uris', 10, 5);
    

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    *

    *

    *

    Wait!!! Before you go...

    Still not convinced? Use following discount code during Gumroad checkout and get 10% off!

    BLOG

    If you have any questions or need further information, please feel free to contact me via email: