<h2>Using routes</h2>
<p>You can define a route like this in your <code>config/routes.php</code>:</p>
<pre class="lang-php prettyprint-override"><code>return [
    '&lt;country:{slug}&gt;/&lt;city:{slug}&gt;/&lt;productType:{slug}&gt;' =&gt; ['template' =&gt; '_category'],
];
</code></pre>
<p>This will match any URL with three slug-like segments in the path and load the specified template. Your template will have access to the named parameters (<code>country</code>, <code>city</code> and <code>productType</code>), but Craft will <em>not</em> check if those are actual categories. That's on you to check in the template, for example:</p>
<pre class="lang-twig prettyprint-override"><code>{% set countryCategory = craft.categories().slug(country).one() %}
{% set cityCategory = craft.categories().slug(city).ancestorOf(countryCategory).one() %}
{% set productTypeCategory = craft.categories().slug(productType).one() %}

{% if not countryCategory and not cityCategory and not productTypeCategory %}
    {% exit 404 %}
{% endif %}
</code></pre>
<p>To support URLs with only one or two segments in the path, you have two options:</p>
<ul>
<li>Define two additional URL rules similar to this one with only one and two segments, respectively.</li>
<li>Use <a href="https://www.yiiframework.com/doc/guide/2.0/en/runtime-routing#default-parameter-values" rel="nofollow noreferrer">Default Parameter Values</a> in the route definition (I never used that, but it should work).</li>
</ul>
<h2>Using URI formats (Original answer)</h2>
<p><em>This only works if the categories are related and each category has a unique URL, see comments.</em></p>
<p>You don't need a custom route, you can do this with the <a href="https://craftcms.com/docs/3.x/entries.html#entry-uri-formats" rel="nofollow noreferrer">Entry URI format</a> (or Category URI format for categories) setting of the category group. The entry URI template can be a full Twig template and has access to all fields on the entry. For example, if the first category group has a field <code>secondCategory</code> you can pull the slug their slug in like this:</p>
<pre class="lang-twig prettyprint-override"><code>{{ object.parent.uri ?? '' }}/{{ object.slug }}/{{ object.secondCategory.one().slug ?? '' }}
</code></pre>
<p><code>{{ object.parent.uri ?? '' }}</code> adds the parent's URI to the current categories URI, this is how you get hierarchical URLs for nested structures.<br />
<code>{{ object.secondCategory.one().slug ?? '' }}</code> adds the slug of the second category related to the first category as the last URL segment.</p>

CC BY-SA 4.0
2023-08-02T10:08:34.770Z
Thanks but I can't get it functional yet. Following my example, I used this for the category URI field in both categories:

`{{ object.parent.uri ?? '' }}/{{ object.slug }}/{{ object.123.one().slug ?? '' }}`

throws error "template not found"
Jake
CC BY-SA 4.0
2023-08-02T10:18:11.393Z
Maybe I should add this is needed for a category page, which is listing all entries related to a category. Each entry has a category ("abc"), an optional subcategory ("xyz") and mandatory second categorygroup category ("123").  I suppose I need to change the Category URI Format and not the Entry URI
Jake
CC BY-SA 4.0
2023-08-02T10:19:48.990Z
@Jake What is `object.123` supposed to do here? Yes, if `/abc/xyz/123` is the URL of a category page, you need to set the URI format on the category group.
MoritzLost
CC BY-SA 4.0
2023-08-02T10:20:21.763Z
In my example, `secondCategory` is the handle of the field connecting the categories to each other, not the slug of a specific category.
MoritzLost
CC BY-SA 4.0
2023-08-02T10:21:56.237Z
If those two categories aren't related, I'm not sure your approach makes sense. If categories `abc` and `123` aren't related at all, how would you know which combinations of categories to use in your URI format?
MoritzLost
CC BY-SA 4.0
2023-08-02T10:22:59.647Z
Sounds like you _do_ need a custom route if you want to load all possible combinations of categories, but I'm not sure that's a good idea. This will result in an huge number of URLs, since you will have m × n categories, where m and n are the number of categories in each group.
MoritzLost
CC BY-SA 4.0
2023-08-02T10:35:08.253Z
abc and 123 are not related by any field. An individual entry has two separate fields to set a "abc" category and a "123" category. I indeed want each combo to have their own url. To be more specific, abc and xyz actually are country+city combo, and 123 is a product type. So the url would need to be `site.com/france/paris/books` instead of `site.com/france/paris?p=books`. The page site.com/france would list all entries related to france, any city inside france, all product types. And so on for every combo.
Jake
CC BY-SA 4.0
2023-08-02T10:56:43.917Z
@Jake Ok, in this case you need a route. If categories can have multiple URLs, the URI format doesn't work, since this always results in a single URL for the given element. I've updated my answer with a solution that uses routes.
MoritzLost
CC BY-SA 4.0
2023-08-04T12:26:59.857Z
I marked as solved as your answer is correct and helpful, however it did not fully work out for my situation as there was an issue in case an url had only a segment from the latter category group (`site.com/books`). In the end, I opted to go for a regular query parameter instead after all.
Jake
CC BY-SA 4.0
2023-08-04T12:54:38.897Z
@Jake Good to know! Yeah, there are some edge-cases with this solution, but those depend on details of the business requirements. Using regex parameters is a good alternative for more flexibility.
MoritzLost
CC BY-SA 4.0
2023-08-02T09:55:34.107Z
2023-08-02T10:55:49.867Z
2023-08-02T10:55:49.867Z
MoritzLost
<p>I'm confused by the syntax for URL routes. Using Craft 3. I'm working with two separate category groups and have urls like this:</p>
<pre><code>site.com/?parameter=123   
site.com/abc?parameter=123
site.com/abc/xyz?parameter=123
</code></pre>
<p>where 'abc' and 'xyz' are parent and child slugs from the same category group. And where 123 is the category slug from the second separate category group. Actual slugs consist of alphanumeric names with dashes replacing spaces.</p>
<p>Now I'd like to replace the parameter style url with a segment style url</p>
<pre><code>site.com/123
site.com/abc/xyz
site.com/abc/xyz/123
</code></pre>
<p>Both categories use the same _category.html template. I need to have access to all category variables inside the template.
I got as far as knowing I need to set up custom routes in config/routes.php but am lost on the syntax.</p>

<craft3><url><routes><regex>
Syntax for URL route instead of using url parameter
# Syntax for URL route instead of using url parameter

(Asked by: username_0 on 2023-08-02)

I'm confused by the syntax for URL routes. Using Craft 3. I'm working with two separate category groups and have urls like this:
site.com/?parameter=123   
site.com/abc?parameter=123
site.com/abc/xyz?parameter=123

where 'abc' and 'xyz' are parent and child slugs from the same category group. And where 123 is the category slug from the second separate category group. Actual slugs consist of alphanumeric names with dashes replacing spaces.
Now I'd like to replace the parameter style url with a segment style url
site.com/123
site.com/abc/xyz
site.com/abc/xyz/123

Both categories use the same _category.html template. I need to have access to all category variables inside the template.
I got as far as knowing I need to set up custom routes in config/routes.php but am lost on the syntax.

**Tags:** craft3, url, routes, regex


---

## 1 answers

### Answer by username_1 on 2023-08-02. Score: 3 | Accepted answer

Using routes
You can define a route like this in your config/routes.php:
return [
    '//' => ['template' => '_category'],
];

This will match any URL with three slug-like segments in the path and load the specified template. Your template will have access to the named parameters (country, city and productType), but Craft will not check if those are actual categories. That's on you to check in the template, for example:
{% set countryCategory = craft.categories().slug(country).one() %}
{% set cityCategory = craft.categories().slug(city).ancestorOf(countryCategory).one() %}
{% set productTypeCategory = craft.categories().slug(productType).one() %}

{% if not countryCategory and not cityCategory and not productTypeCategory %}
    {% exit 404 %}
{% endif %}

To support URLs with only one or two segments in the path, you have two options:

Define two additional URL rules similar to this one with only one and two segments, respectively.
Use Default Parameter Values in the route definition (I never used that, but it should work).

Using URI formats (Original answer)
This only works if the categories are related and each category has a unique URL, see comments.
You don't need a custom route, you can do this with the Entry URI format (or Category URI format for categories) setting of the category group. The entry URI template can be a full Twig template and has access to all fields on the entry. For example, if the first category group has a field secondCategory you can pull the slug their slug in like this:
{{ object.parent.uri ?? '' }}/{{ object.slug }}/{{ object.secondCategory.one().slug ?? '' }}

{{ object.parent.uri ?? '' }} adds the parent's URI to the current categories URI, this is how you get hierarchical URLs for nested structures.
{{ object.secondCategory.one().slug ?? '' }} adds the slug of the second category related to the first category as the last URL segment.

> **Comments:**
> **username_0**: Thanks but I can't get it functional yet. Following my example, I used this for the category URI field in both categories:  `{{ object.parent.uri ?? '' }}/{{ object.slug }}/{{ object.123.one().slug ?? '' }}`  throws error "template not found"  
> **username_0**: Maybe I should add this is needed for a category page, which is listing all entries related to a category. Each entry has a category ("abc"), an optional subcategory ("xyz") and mandatory second categorygroup category ("123").  I suppose I need to change the Category URI Format and not the Entry URI  
> **username_1**: @username_0 What is `object.123` supposed to do here? Yes, if `/abc/xyz/123` is the URL of a category page, you need to set the URI format on the category group.  
> **username_1**: In my example, `secondCategory` is the handle of the field connecting the categories to each other, not the slug of a specific category.  
> **username_1**: If those two categories aren't related, I'm not sure your approach makes sense. If categories `abc` and `123` aren't related at all, how would you know which combinations of categories to use in your URI format?  
> **username_1**: Sounds like you _do_ need a custom route if you want to load all possible combinations of categories, but I'm not sure that's a good idea. This will result in an huge number of URLs, since you will have m × n categories, where m and n are the number of categories in each group.  
> **username_0**: abc and 123 are not related by any field. An individual entry has two separate fields to set a "abc" category and a "123" category. I indeed want each combo to have their own url. To be more specific, abc and xyz actually are country+city combo, and 123 is a product type. So the url would need to be `site.com/france/paris/books` instead of `site.com/france/paris?p=books`. The page site.com/france would list all entries related to france, any city inside france, all product types. And so on for every combo.  
> **username_1**: @username_0 Ok, in this case you need a route. If categories can have multiple URLs, the URI format doesn't work, since this always results in a single URL for the given element. I've updated my answer with a solution that uses routes.  
> **username_0**: I marked as solved as your answer is correct and helpful, however it did not fully work out for my situation as there was an issue in case an url had only a segment from the latter category group (`site.com/books`). In the end, I opted to go for a regular query parameter instead after all.  
> **username_1**: @username_0 Good to know! Yeah, there are some edge-cases with this solution, but those depend on details of the business requirements. Using regex parameters is a good alternative for more flexibility.  

---

