Skip to content

Make the permalinks config less verbose and more flexible #14744

@Quantumplate

Description

@Quantumplate

The Why

When working with permalinks, the configuration can become overly verbose, when wanting to use the :sectionslugs token in the config, with the slug: field in the section header frontmatter, instead of the generated slug from the directory name, when the Hugo module has multiple sibling sections.

The Proposal

Instead of requiring the module to have every section explicitly listed with it's permalinks pattern, we would allow a _default parameter in the config for permalinks.page or permalinks.section.

For example:

permalinks:
  page:
    _default: /:sectionslugs/:slug
    posts: /:year/:slug
  section:
    _default: /:sectionslugs

Then if there is a section with a permalinks pattern which differs from the rest of the site, it can be added explicitly.

The How

I've confirmed that the following change will give the desired result.

diff --git a/resources/page/permalinks.go b/resources/page/permalinks.go
index adaa9faee..251859d6b 100644
--- a/resources/page/permalinks.go
+++ b/resources/page/permalinks.go
@@ -142,7 +142,8 @@ func (l PermalinkExpander) ExpandPattern(pattern string, p Page) (string, error)
 }
 
 // Expand expands the path in p according to the rules defined for the given key.
-// If no rules are found for the given key, an empty string is returned.
+// If no rules are found for the given key, checks for a _default pattern.
+// If no rules are found for the given key or _default, an empty string is returned.
 func (l PermalinkExpander) Expand(key string, p Page) (string, error) {
 	expanders, found := l.expanders[p.Kind()]
 	if !found {
@@ -151,7 +152,11 @@ func (l PermalinkExpander) Expand(key string, p Page) (string, error) {
 
 	expand, found := expanders[key]
 	if !found {
-		return "", nil
+		// Fall back to _default if no specific pattern is found.
+		expand, found = expanders["_default"]
+		if !found {
+			return "", nil
+		}
 	}
 
 	return expand(p)

diff --git a/docs/content/en/configuration/permalinks.md b/docs/content/en/configuration/permalinks.md
index cd1c38083..658b9ab51 100644
--- a/docs/content/en/configuration/permalinks.md
+++ b/docs/content/en/configuration/permalinks.md
@@ -15,6 +15,18 @@ Define a URL pattern for each top-level section. Each URL pattern can target a g
 > [!note]
 > The [`url`] front matter field overrides any matching permalink pattern.
 
+## Default pattern
+
+Use the special `_default` key to define a pattern that applies to all sections unless explicitly overridden:
+
+{{< code-toggle file=hugo >}}
+[permalinks.page]
+_default = '/:year/:month/:slug/'
+posts = '/articles/:slug/'
+{{< /code-toggle >}}
+
+In this example, all pages will use the date-based hierarchy except those in the "posts" section, which will use the `/articles/:slug/` pattern.
+
 ## Monolingual example
 
 With this content structure:

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions