How Graft works
Four ideas that explain the rest of the docs.
After Getting started you have a project that compiles and a page that reads content. This page explains what happened, so the rest of the docs make sense. It introduces four ideas, one at a time:
- Your files are the source of truth.
- Content and user data are kept apart.
- One schema checks everything.
- Errors tell you what to do next.
Your files are the source of truth
You write content as files in Git. Your app does not read those files directly,
because searching and filtering hundreds of files on every request would be
slow. Instead, graft compile reads them, checks each one, and builds an
index: a copy of your content organized for fast queries.
The index is always a copy. If it is lost, deleted, or out of date, run
graft compile and it is rebuilt from your files. This is why Graft calls the
index derived: everything in it comes from Git.
Compiling only changes what changed. Graft compares each file with what the index already holds and updates just the documents that differ, so unchanged documents keep their timestamps. The list of documents a compile added, changed, or removed is called a ChangeSet. Framework packages use it to refresh only the cached pages that changed.
Content and user data are kept apart
Some data cannot live in files. When a visitor submits a form, places an order, or leaves a comment, that data is created at runtime, by your users, and there is no file in Git to rebuild it from. Graft calls this operational data.
The two kinds are stored and written differently:
| Content | Operational data | |
|---|---|---|
| Examples | pages, docs, products | form submissions, orders, comments |
| Source of truth | files in Git | rows in Postgres |
| Who writes it | you and agents, by editing files | your users, through typed functions |
| How you declare it | a normal collection | a collection with authority: "db-authoritative" |
Operational data needs the Postgres engine, because that is
where its rows live. If a collection is declared one way and written the other,
Graft stops with AUTHORITY_MISMATCH and tells you which collection is wrong.
Operational data covers it in full.
One schema checks everything
You describe each collection once, in graft.config.ts. That one description
is used everywhere data crosses a boundary:
graft compilechecks every file's frontmatter against it.- Typed functions check their inputs against the
inputschema each one declares. That can reuse a collection's fields (input: submissions.fields) or describe something else entirely, so a collection's rules apply to a function only when its input uses them. - Reads check documents on the way out.
The schema is built on Zod, a TypeScript validation
library, so you get its types for free. There is no code generation step:
getContent("pages", "home") is typed by the same object that checked the
file. Schema shows how to write one.
Errors tell you what to do next
Every Graft error has a code, a message saying what happened, and a fix
naming the next step. The fix is written so that both you and an agent can
act on it without searching the docs:
{
"error": "INDEX_OWNERSHIP",
"message": "Refusing to project: this would remove every document in collection(s) \"products\" …",
"fix": "Each Graft project needs its own database (or branch): point DATABASE_URL at …"
}
The Error reference lists every code.
One database per project
On the Postgres engine, compiling makes the index match your current schema exactly. That includes removing collections your schema no longer declares. If two projects pointed at the same database, each compile would delete the other project's content.
Graft prevents this. If a compile would remove collections your schema does not
know about, it stops with INDEX_OWNERSHIP and writes nothing. Give each
project its own database, or its own branch of one.
If you renamed or deleted a collection on purpose, run the compile with
--prune-unknown to allow the removal.
Next steps
- Storage engines helps you choose between static and Postgres.
- Schema shows how to describe your collections.
- Reading content covers reads in every framework.