Shopware 6 - How do SEO URLs work?

August 01, 2021 - 5 min read

In this article, I’ll explain how SEO URLs work in Shopware, where they are set, how they are generated and how you can override them.

In a default Shopware 6 install, SEO URLs are automatically generated based on a “SEO URL template”. These templates can be found under Settings > (Shop) SEO. There are templates for product detail pages, landing pages and category pages.

The default product detail page SEO URL template is {{ product.translated.name |lower }}-{{ product.productNumber |lower }}. So a product called “Joust Duffle Bag” with SKU “24-MB01” would get this SEO URL: joust-duffle-bag-24-mb01.


These URLs are generated by the SEO URL generator class (\Shopware\Core\Content\Seo\SeoUrlGenerator) and are stored in the seo_url table (the templates are stored in seo_url_template). This is what the seo_url table looks like in the database;

mysql> describe seo_url;
| Field            | Type         | Null | Key | Default | Extra |
| id               | binary(16)   | NO   | PRI | NULL    |       |
| language_id      | binary(16)   | NO   | MUL | NULL    |       |
| sales_channel_id | binary(16)   | YES  | MUL | NULL    |       |
| foreign_key      | binary(16)   | NO   |     | NULL    |       |
| route_name       | varchar(50)  | NO   |     | NULL    |       |
| path_info        | varchar(750) | NO   |     | NULL    |       |
| seo_path_info    | varchar(750) | NO   |     | NULL    |       |
| is_canonical     | tinyint(1)   | YES  |     | NULL    |       |
| is_modified      | tinyint(1)   | NO   |     | 0       |       |
| is_deleted       | tinyint(1)   | NO   |     | 0       |       |
| custom_fields    | json         | YES  |     | NULL    |       |
| created_at       | datetime(3)  | NO   |     | NULL    |       |
| updated_at       | datetime(3)  | YES  |     | NULL    |       |
13 rows in set (0.01 sec)

The SEO URLs are generated when the indexer runs, which can be manually triggered by running bin/console dal:refresh:index. Make sure you run this command after you’ve changed the template - it does not automatically regenerate the URLs.

Overriding SEO URLs

Sometimes you don’t want to use the SEO URL template to create a SEO URL but you want to input it manually. Open a product in the admin and navigate to the SEO tab. Then scroll down to the “SEO URLs” fieldset and pick your sales channel. You can now set the SEO URL manually under “SEO path”. You can optionally set a “Main Category” here. This value is used in the Category Breadcrumb Builder (\Shopware\Core\Content\Category\Service\CategoryBreadcrumbBuilder). It will show the chosen main category in the breadcrumbs instead of the first category Shopware finds the product in.

When you’ve manually configured a SEO URL, it will be saved in the seo_url table with the is_modified column value set to 1. This column flags whether a SEO URL has been modified manually. It will prevent the SEO URL generator overwriting the URL with a generated one (see \Shopware\Core\Content\Seo\SeoUrlPersister::skipUpdate).

Changing the SEO URL template

After you have changed the template and run the indexer, you will notice more URLs are present in the seo_url table. This is because Shopware keeps the old URL around to make sure the URLs indexed by search engines keep working. They will now redirect to the new URL. When this happens, the is_canonical column in the seo_url table for the old URL will now be 0 while the is_canonical column for the new URL will be 1.

But what if you don’t want to change the old URLs when updating the template but only want the new URLs to stick to the new template? In that case, you can set the is_modified column in the seo_url table to 1. There’s no quick option to do this in admin (except for doing this manually for all individual products) so I just use a query; UPDATE seo_url SET is_modified = 1 WHERE route_name = 'frontend.detail.page'.


Canonicals are a bit weird in Shopware when it comes to products with variants. When creating a product, you can enter a name (“Joust Duffle Bag”) and a SEO URL (“joust-duffle-bag”). Then you can generate variants, like a black bag and a blue bag. These get named “Joust Duffle Bag Black” (“joust-duffle-bag-black”) and “Joust Duffle Bag Blue” (“joust-duffle-bag-blue”).


Once we’ve done this, we can now go to the SEO tab of the parent product (“Joust Duffle Bag”) and configure a “single canonical URL for all variants”. Great, that’s what we want! However, to my surprise, we can only choose one of the variants, and not the SEO URL of the parent. I want to use “joust-duffle-bag” as my canonical URL, not “joust-duffle-bag-blue” or “joust-duffle-bag-black”.


This can easily be solved by overriding the product-detail/meta.html.twig file to check if a parent is known for the current product, and then retrieving the SEO URL of the parent product to show in the canonical field;

{% sw_extends '@Storefront/storefront/page/product-detail/meta.html.twig' %}

{% block layout_head_canonical %}
    {% if page.product.parentId %}
        <link rel="canonical" href="{{ seoUrl('frontend.detail.page', { productId: page.product.parentId }) }}" />
    {% else %}
        <link rel="canonical" href="{{ seoUrl('frontend.detail.page', { productId: page.product.id }) }}" />
    {% endif %}
{% endblock %}

To make this easier for you, I’ve wrapped this up in a little plugin; elgentos/shopware-seo-canonical-url.

I hope this explains SEO URLs in Shopware for you!

Kyle Mathews

Written by Peter Jaap Blaakmeer @PeterJaap