Skip to content

feat(collection): add slugFields for multi-segment collection paths - #1624

Open
tihu220 wants to merge 2 commits into
Thinkmill:mainfrom
tihu220:feat/collection-slug-fields
Open

tihu220 wants to merge 2 commits into
Thinkmill:mainfrom
tihu220:feat/collection-slug-fields

Conversation

@tihu220

@tihu220 tihu220 commented Sep 16, 2026

Copy link
Copy Markdown

Resolves #340

This implements the multi-slug API proposed by @JedWatson in the issue — keeping collection paths static and deterministic rather than resolving paths via a callback, so the on-disk location of entries is always known from config.

API

blog: collection({
  label: 'Blog',
  path: 'src/content/blog/*/*',
  slugFields: ['locale', 'title'],
  slugField: 'title',
  schema: {
    locale: fields.text({ label: 'Locale' }),
    title: fields.slug({ name: { label: 'Title' } }),
  },
}),
  • Each entry of slugFields maps onto a * segment of path, in order.
  • The slug of an entry is the segment values joined with a slash (en/my-post), so tree listing, routing, the reader and both storage backends work unchanged.
  • Fully backwards compatible: collections using slugField alone behave exactly as before.

Behaviour notes

  • Config validation: the number of * segments must equal slugFields.length, every entry must be a slug-form field, and the last entry must be the slugField (the segment shown as "the slug" in the admin UI).
  • Secondary slug fields validate as slug segments (no slashes, no ..); the primary field is additionally checked for uniqueness against the composite slug, so en/my-post and fr/my-post can co-exist while duplicate paths are still rejected.
  • Duplicating an entry appends -copy to the primary segment.
  • The reader accepts composite slugs: reader.collections.blog.read('en/my-post'), and list() returns composite slugs.
  • Existing ** glob collections are unaffected; combining slugFields with ** is a config error.

Testing

  • New unit tests in packages/keystatic/src/app/slug-fields.test.ts cover config validation, composite path mapping, slug computation, tree listing at exact depth (including ignoring too-deep entries) and reader list/read for both multi- and single-slug collections.
  • Typecheck is clean; the full unit suite shows no regressions.

/claim #340

Implements Thinkmill#340 using the static, deterministic API proposed in the issue:
collections can declare multiple slug fields that map onto the star
segments of the collection path in order.

    collection({
      path: 'src/content/blog/*/*',
      slugFields: ['locale', 'title'],
      slugField: 'title',
      schema: { locale: fields.text({ label: 'Locale' }), title: fields.slug({ name: { label: 'Title' } }) },
    })

An entry with locale `en` and title slug `my-post` is stored at
`src/content/blog/en/my-post`. The entry's slug is the segments joined
with a slash (`en/my-post`), which keeps tree listing, routing, the
reader and the GitHub/local storage backends deterministic - no
callbacks or non-serialisable config.

- fully backwards compatible: single `slugField` collections behave
  exactly as before
- config validation: the number of star segments must match
  `slugFields.length`, every entry must be a slug field and the last
  entry must be the `slugField`
- composite slug validation: secondary slug fields validate as slug
  segments while the primary field is checked for uniqueness against
  the full composite slug
- covered by new unit tests in `slug-fields.test.ts`
@changeset-bot

changeset-bot Bot commented Sep 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 588447c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@keystatic/core Minor
@keystatic/templates-astro Patch
@keystatic/templates-nextjs Patch
@keystatic/templates-remix Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@tihu220

tihu220 commented Sep 16, 2026

Copy link
Copy Markdown
Author

/claim #340

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement: more configurable collection path

1 participant