Skip to content

Modelling content

Collections

Declare a kind of content, choose the URLs its entries live at, and set how it is listed, searched, published and fed.

A collection is a kind of content: Tidewater’s landing pages, its blog posts, its help articles. This guide covers everything a collection file can say.

After reading this guide, you will know:

  • How to declare a collection, and which keys it needs.
  • How routes turn an entry into a URL, including nested pages and the home page.
  • How to give a collection a listing page and an Atom feed.
  • How publishing workflow, revisions, the sitemap, feeds, the API and search are set up.

1. A first collection

Say you want customer stories on Tidewater’s site, at /customers/<slug>. Generate the files:

bin/rails nibble:generate:collection customers

and edit site/schema/collections/customers.yml into shape:

schema: 1
title: Customer stories
icon: users
route: /customers/{slug}
index_route: /customers
index_template: customers/index
template: customers/show
blueprints: [story]
dated: true
sort: published_at:desc

Only title and blueprints are required. Everything else has a sensible default or is simply off.

Note

The file name is the collection’s handle: customers.yml declares customers. The handle is what queries, permissions and the API use, so choose it once. The title is what people see, and can change whenever you like.

2. Routes

2.1 Route patterns

route is the URL pattern for one entry. Tokens in braces are filled from the entry:

Token Becomes
{slug} the entry’s slug
{parent_uri} the parent entry’s whole URL, in a structured collection
{parent_slugs} the parents’ slugs, with a leading /
{year}, {month} from the entry’s publish date
{locale} the locale’s URL prefix
route: /blog/{year}/{slug}      # /blog/2026/late-invoices

A collection without a route has no public pages. Its entries can still be queried by other pages and read over the API — Tidewater could keep testimonials that way and show them on the home page.

2.2 Changing a route

An entry’s URL is worked out when the entry is saved or published, and stored. So a new route does not move anything by itself: each entry takes the new pattern the next time someone saves or publishes it, and at that moment Nibble records a 301 redirect from its old URL.

Warning

Until every entry has been saved again, a collection whose route changed answers at a mix of old and new URLs. After changing a route on a collection with content, re-publish its entries — or plan the change for a collection that is still empty.

2.3 Nested pages and the home page

Pages live in a tree. Tidewater’s pages collection, which Nibble ships, says so:

route: "{parent_uri}/{slug}"
structure:
  max_depth: 5
  root: true

structure lets editors drag entries under one another, up to max_depth levels. With root: true, the top-level entry whose slug is home answers at / — that is how Tidewater’s home page works.

Tip

{parent_uri} repeats the parent’s whole URL, so a collection routed at /docs{parent_uri}/{slug} would give a child /docs/docs/…. Use {parent_slugs} when the route has its own prefix: /docs{parent_slugs}/{slug}.

3. The listing page

index_route gives the collection a page of its own, rendered with index_template:

index_route: /blog
index_template: posts/index

That view receives page with the collection’s title, plus whatever its query sidecar asks for — usually a paginated list of the collection’s entries.

Note

An entry whose URL is the same as the index route wins. If someone creates a page at /blog, it replaces the listing. That is sometimes useful, and sometimes a surprise.

4. Templates and layouts

template: posts/show
layout: default

template names the view that renders one entry, and layout the frame around it. A blueprint can set its own template, and so can an entry, so an unusual page does not need an unusual collection.

5. Dates, expiry and ordering

Key What it does
dated: true entries carry a publish date, and can be scheduled for the future
expires: true entries can also carry an unpublish date
sort the default order, as field:asc or field:desc — published_at:desc, title:asc, position:asc

Tidewater’s blog is dated and newest first. Its pages are ordered by position, which is what dragging them in the control panel changes.

6. Publishing, drafts and revisions

workflow: review
revisions:
  keep: 100
  • workflow: simple (the default) lets anyone who may publish, publish.
  • workflow: review means an entry is submitted for review and approved by someone with the right role before it goes live. See Drafts and publishing.
  • revisions.keep is how many past versions each entry keeps.

Tip

Turn on review once a second writer joins the blog: an editor then approves each post before it goes out, and the approval is recorded in the post’s history.

sitemap:
  enabled: true
  priority: 0.6
  changefreq: weekly
feed:
  title: The Tidewater blog
  limit: 20
api: true
Key Default What it does
sitemap included whether entries appear in /sitemap.xml, and with what priority
feed off true, or a title and limit, publishes an Atom feed at /feed-<handle>.xml
api off whether the Content API serves this collection

Every collection with a feed also appears in /feed.xml. See SEO, sitemaps and feeds.

A collection joins a search index with its own search: key. Nibble’s pages and posts say search: site; give another collection the same to make it searchable:

# site/schema/collections/help.yml
search: site

search.yml holds each index’s settings. Write your own in site/schema/ to choose which fields are searched:

# site/schema/search.yml
schema: 1
indexes:
  site:
    fields: [title, intro, excerpt, description, body, blocks]

fields lists what is searched; a field a blueprint does not have is skipped. Run bin/rails nibble:search:rebuild after changing an index. Entries are indexed as they are published; a collection written as files on every deploy and, in development, as soon as a file changes.

Every entry of a searched collection has an Include in search toggle in its sidebar, on unless someone turns it off. Turned off, the entry is stored with search: false and left out of every index; a page written as a file says the same in its front matter. A blueprint with its own search field keeps it, and the toggle is not added. It does not touch the sitemap or noindex — those are SEO’s.

8. Taxonomies and locales

taxonomies: [topics]
localizable: true

taxonomies adds a picker for each listed taxonomy to every entry’s sidebar, even when the blueprint does not declare a field for it. localizable lets entries exist in each of the site’s locales.

9. Collections written as files

One more key changes what a collection is:

files: help

The entries are Markdown files in site/content/help/ instead of rows. That is how Tidewater’s help centre works, and it has a guide of its own: Content as files.

10. Every key

Key Type Notes
title text required
blueprints list required; the first is the default
route, index_route route see Routes
template, index_template, layout view name
structure { max_depth, root } or false
dated, expires, requires_slugs, localizable, api true / false
sort field:direction
workflow simple or review
revisions { keep }
search index handle or false the search index its entries join — see Search
sitemap { enabled, priority, changefreq }
feed true or { title, limit }
taxonomies list of handles
icon icon name shown in the control panel’s sidebar
files folder in site/content/ see Content as files

bin/rails nibble:check rejects any key not on this list, so a typo never passes silently.