WooCommerce | Same Product Base & Shop Base

Here is a simple* code, modified after the solution by Le Van Toan outlined on his blog based in Vietnam. Have you ever tried to setup your own custom permalinks in WooCommerce so that the product base and the shop base are one and the same? This plugin makes it so.

Desired WooCommerce Shop/Products base structure:

shop page – website.com/shop/
product parent category archive page – website.com/shop/parent-category-slug/
product child category archive page – website.com/shop/parent-category-slug/child-category-slug/
single product page – website.com/shop/parent-category-slug/child-category-slug/single-product-page-slug

Here is what you need to set in Settings > Permalinks
visit website.com/wp-admin/options-permalink.php

Optional > Product category base: shop
Product permalinks > Custom base: /shop/%product_cat%/

Here is the code, for your functions.php

function category_base_is_shop_base(){
// Get all product categories
$terms = get_terms(array(
'taxonomy' => 'product_cat',
'post_type' => 'product',
'hide_empty' => false,
));

// Check if terms are retrieved successfully
if ($terms && !is_wp_error($terms)) {
$siteurl = esc_url(home_url('/'));
// Loop through each term to set up custom rewrite rules
foreach ($terms as $term) {
$term_slug = sanitize_title($term->slug);
$baseterm = str_replace($siteurl, '', get_term_link($term->term_id, 'product_cat'));
// Add rewrite rules for base term, pagination, and feeds
add_rewrite_rule($baseterm . '?$', 'index.php?product_cat=' . $term_slug, 'top');
add_rewrite_rule($baseterm . 'page/([0-9]{1,})/?$', 'index.php?product_cat=' . $term_slug . '&paged=$matches[1]', 'top');
// add_rewrite_rule($baseterm . '(?:feed/)?(feed|rdf|rss|rss2|atom)/?$', 'index.php?product_cat=' . $term_slug . '&feed=$matches[1]', 'top');
}
}
}

// Hook into 'init' action to set up rewrite rules
add_action('init', 'category_base_is_shop_base');

// Function to run when a new term is created
add_action('create_term', 'category_base_is_shop_base_edit_success', 10, 2);

function category_base_is_shop_base_edit_success($term_id, $taxonomy) {
category_base_is_shop_base();
// Inform the user to flush permalinks manually
add_action('admin_notices', function() {
echo '<div class="notice notice-warning is-dismissible"><p>To ensure your custom permalink structure works, flush your permalinks by going to Settings > Permalinks and clicking "Save Changes".</p></div>';
});
}

Visit your Settings > Permalinks and hit save twice to force flush/rebuilding of permalinks. Done.

Comments

I’m sure this can be of help to many people, if you are old school and don’t want the classic final trailing slash at the end of WordPress URL, you need the following rewrites:

// Add rewrite rules for the category base term without trailing slash
add_rewrite_rule($baseterm . '?$', 'index.php?product_cat=' . $term_slug, 'top');

// Add rewrite rules for the category base term with trailing slash
//add_rewrite_rule($baseterm . '/?$', 'index.php?product_cat=' . $term_slug, 'top');

// Add rewrite rules for category pagination without trailing slash
add_rewrite_rule($baseterm . 'page/([0-9]{1,})/?$', 'index.php?product_cat=' . $term_slug . '&paged=$matches[1]', 'top');

// Add rewrite rules for category pagination with trailing slash
//add_rewrite_rule($baseterm . '/page/([0-9]{1,})/?$', 'index.php?product_cat=' . $term_slug . '&paged=$matches[1]', 'top');

// Handle nested categories (subcategories) for pagination without trailing slash
add_rewrite_rule($baseterm . '(.+?)/page/([0-9]{1,})/?$', 'index.php?product_cat=$matches[1]&paged=$matches[2]', 'top');

// Handle nested categories (subcategories) for pagination with trailing slash
//add_rewrite_rule($baseterm . '/(.+?)/page/([0-9]{1,})/?$', 'index.php?product_cat=$matches[1]&paged=$matches[2]', 'top');

This also adds code for subcategories, for those interested. Comment or uncomment as needed.

callum says:

Excellent. Thank you Davide, thanks for stopping by!

Leave a Reply