Tables
A table is an ordinary page whose frontmatter declares type: table and a
table: config. It lists in folders and spaces, inherits the same grants, and
syncs through the same pipeline as any other page — there are no new containers.
The body of a table page is regular prose; the table: config drives a live view
rendered above it.
See also Typed pages and properties for how the pages a table reads get their types and properties.
Creating a table
Make a page and write type: table plus a table: block in its frontmatter:
type: table
table:
source:
types: [issue]
scope: folder
filters:
- { prop: status, op: in, value: [open, in_progress] }
views:
- name: Board
layout: board
group_by: status
sort: [{ prop: due, dir: asc }]
- name: This week
layout: table
columns: [title, status, due, assignee]
filters: [{ prop: due, op: within, value: 7d }]
new_page:
folder: same
properties: { status: open }Source: what the table reads
source selects which pages appear:
types— page types to include (e.g.[issue]). Omit to include all types.scope— how wide to look:workspace,space, orfolder(defaultfolder, anchored to the table page's own folder).filters— a base filter applied to every view (see Operators).
Views and layouts
Each entry in views is a tab. Three layouts:
board— Kanban columns grouped bygroup_by(e.g.status). Rows missing that property collect in aNo <prop>lane.table— a grid;columnspicks which properties to show (thetitlecolumn links to the row's page).list— a compact list.
A view can add its own filters (merged with the source filters) and sort (up
to 3 keys). Sort is stable; rows missing the sort property sort last.
Date filters
The within operator filters by a relative date window. { prop: due, op: within, value: 7d } matches rows whose due falls in the last 7 days up to now —
a stand-up "due this week" view is within: 7d, "due today" is within: 1d.
Future dates do not match.
Operators
eq ne in nin contains exists gt gte lt lte between within
in/nintake arrays.betweentakes[min, max], inclusive both ends.existstakes a boolean —exists: falsematches a missing property.withintakesNd(1d/7d/30d).- the rest take a single value.
A missing property is excluded by default for every operator except
exists: false. String compares (eq/ne/in/nin/contains) are
case-insensitive, so Open and open group together.
New rows
If new_page is set, the table's New button creates a page with
type: <first source type> and new_page.properties as starting frontmatter,
placed in new_page.folder (same = the table's folder, or a folder id). The new
row appears in the table live.
Editing rows in place
Board drag and the status cell edit the row page's frontmatter directly: the
edit is a CAS patch on that page's source (body bytes preserved), so it flows
through history and sync like any other save. Moving a card to another lane writes
the new status on that page.
Configure
The table's Configure action opens the normal prose editor on the table page,
so editing the table: config is just a body edit — CAS, version history, and
sync all apply. A malformed table: config shows a friendly "broken table" state
instead of crashing; fix the YAML and save.
Limits
≤ 20 filters · ≤ 10 views · ≤ 20 columns per view · ≤ 3 sort keys per view. A violation or malformed config produces the broken-table state.
Public tables
A publicly shared type: table page shows its prose body plus a "Table views are
member-only" placeholder — the view config and rows never render publicly. Public
table rendering is future work.
automation: (reserved)
table: pages may carry an automation: key. It is reserved and inert in v1:
its shape is validated and stored, but it has no behavior yet. It is the forward
contract for feed-driven automations (e.g. running an agent when a page matching
the table's source is created), so the key is stable for when that work lands.
Like table, it is excluded from the generic property projection.