These guides are primarily for agent readers. They explain how agents use Sails. If you are a person, start at sails.app or Add Sails.

Collections

A collection is a named list of other pages on the same site. The compiler fills it when the page is first requested. It is not Postgres, not a JSON store, and not something you INSERT into.

For app records (contacts, tasks, subscribers), use sql --db. See Keep app data in a database.

Done looks like

An index page lists every matching .page.md / .page.html. A new post file appears in that list after autosave, without a rebuild command.

Worked example: a blog index

This site already does it. pages/index.page.html and pages/how-to/index.page.html both declare a glob.

---
title: How to
howtos:
  glob: pages/how-to/*.page.md
  where: draft != true
  sort: nav_order
  order: asc
---
{% for page in howtos %}
  <a href="/guides/{{ page._url }}">{{ page.title }}</a>
{% endfor %}

From a fresh site:

cp -r /templates/static-blog ./site

site/pages/index.page.html already has posts: with glob: pages/blog/**. Add site/pages/blog/launch.page.md:

---
title: Launch
date: 2026-08-19
draft: false
---
<site-shell title="{{ title }}">
# Launch

Shipped.
</site-shell>

Ship once (ship create site --path site --policy 644). Open /. The homepage loop over posts includes Launch. Edit the file; autosave is enough.

On this guides mount the public prefix is /guides, so links are /guides/ plus the page _url. A site published at folder root uses / plus _url (that is what /templates/static-blog does).

Front-matter keys

Declare the list under any name (posts, howtos, features). Each collection must set glob: (other pages) or filter: (JSON / front-matter files), not both.

KeyWhat it does
globPage files relative to the site root (the folder with sails.site.json). Example: pages/blog/**
filterJSON / front-matter files (same set as the filter command). Example: data/invoices/**. Items get _path, not site _url
whereSQL-like filter on fields. Missing fields are null
sortField name (date, nav_order, title)
orderasc or desc
paginatePage size; totals on _pagination.<key>
searchtrue enables ?search= / ?q= within the set

where examples that work:

where: draft != true
where: draft != true and date != null
where: id = $threadId

$threadId is a splat from a file like pages/threads/$threadId.page.html. The captured value is also a template variable named threadId.

Each item is the other page’s front matter plus compile fields such as _url (public path without a leading slash). Read title, date, and _url on each item.

What is not a collection

Tempting file / keyReality
sails.data.jsonDo not use. Not how app records work
Page from: or page sql:Do not use for records
collection query contactsNot a workspace command for this. Query with sql --db
Writing a row from a formHandler talks to sql --db, not to a glob

Copy /templates/crm-app (or remix-app / table-app) when the job is rows.

Failure modes

What you seeWhat it meansWhat to do
Loop is emptyGlob/filter missed, or where dropped every itemPath is from the site root (pages/blog/** or data/invoices/**). draft: true pages are excluded by draft != true. A page with no draft key is null, so draft != true still includes it
New post missing on the live URLCache still has the old collectionAutosave the new file. The page recompiles when collection files change. Do not ship create again
You used filter: and expected page _urlfilter: items are files, not pagesRead _path. For page URLs use glob:
You put contacts in sails.data.jsonWrong store.db bundle + sql --db. See Keep app data in a database
check --path template errorThe template failed the same way publish wouldFix the loop syntax and quotes. Do not HTML-escape quotes inside if expressions

How to: Publish a site, Keep app data in a database. Also Static sites, Filter, SQL.