URL aliases and path patterns
The real address of a piece of content is its slug and it never changes. Readable addresses such as /blog/2026/hello-world, which contain a category or date, are produced as aliases. This distinction is deliberate: an alias is a routing layer, not the identity of the content.
Components
| Part | Job |
|---|---|
UrlAlias record |
Links a path (and language) to a piece of content |
PathAliasPattern |
The path pattern for a content type (/blog/[node:created:Y]/[node:title]) |
PathAliasGenerator |
Applies the pattern to content, finds a unique path, writes the alias |
UrlAliasListener |
Looks up in the alias table a request for which no route matches and issues a 301 to the canonical address |
Patterns are managed from the Structure → Path patterns screen. Each pattern is tied to a content type (bundle), can be switched on and off and is exported as a configuration file:
# cp-content/config/sync/path_pattern.post.yaml
pattern: '/blog/[node:created:Y]/[node:title]'
enabled: true
How a path is produced
- The pattern is resolved with the token replacer:
[node:title],[node:created:Y],[node:category],[node:author]… - The resulting text is split on
/and each segment is converted to URL form separately (lowercase, Turkish characters to ASCII). "Merhaba Dünya!" →merhaba-dunya; fixed segments (blog) stay as they are. - Segments left empty are dropped. For content without a category,
[node:category]does not leave a double slash, it silently disappears. - If the path already exists in another piece of content in that language,
-2,-3is appended. - If the same path is already registered for the same content, nothing is written: the operation is idempotent.
Why old addresses do not return 404
When you change the title, a new alias is produced and the old one is not deleted. Every alias is resolved at request time to the content's current slug. Result: the old address redirects to the new address with a 301 forever; you do not need a separate redirect module for the "the alias changed, the old address is 404" trap of Drupal's Pathauto.
UrlAliasListener listens to the kernel.exception event, so it only takes effect on requests for which no route matches (which would be a 404). Modules' own routes (/forum, /docs) cannot be shadowed by aliases.
Bulk generation for existing content
Adding a pattern does not automatically beautify old content. The regenerate button on the path patterns screen produces aliases for all content of the pattern's type. It is not mandatory: old links continue to work with the slug anyway; this is only a convenience.
For your module
- If your content type is a
Node, it is included in the pattern system automatically; no separate code is needed. - To add your own token type, implement
TokenValueProviderInterfaceand attach it with thecp.token_value_providertag (Extension points). - Do not use aliases for your own routes;
#[Route]is already definitive.
Common mistakes
- Writing the slug at the end of the pattern: the slug already comes from the title;
[node:title]is enough. - Expecting the same path in two languages: uniqueness is on the
(path, language)pair; the same path can exist in different languages. - Thinking addresses will go away when you delete the pattern: a deleted pattern stops producing new aliases; existing ones keep working.