Getting started
Go from an empty folder to your content on a page in about 10 minutes.
In this guide you will:
- Set up Graft in a project with
graft init - Compile your content, which checks each file and builds the index your app reads from
- Read a document in your app, with full TypeScript types
Before you start
You need Node.js 22.16 or newer. Graft's static index uses the node:sqlite
module that ships with it. You do not need a database or a cloud account.
Set up your first project
Pick the tab that matches your project. Each one runs the same three steps: set up, compile, read. The Next.js and Astro paths end with a rendered page. The plain TypeScript path prints the document in your terminal, with no framework involved. Other frameworks are listed after the tabs.
Next.js (App Router)
Goal: render a compiled document in a Server Component.
Install the Next.js SDK and CLI
The SDK supports Next.js 15 and 16. Check yours with
npx next --version. Releases before 15.5.24 on 15, or 16.3.3 on 16, have critical security advisories, so upgrade if you can: see Check your Next.js version. Next.js 14 is not supported, because it will not get fixes for them. These steps use the App Router; the Next.js guide covers the Pages Router.npm i @usegraft/sdk-next @usegraft/db npm i -D @usegraft/clinpx @usegraft/cli@latest initThis scaffolds
graft.config.ts, creates sample content incontent/pages/home.mdx, and sets up the static index.npx graft compileThis validates your markdown files against the schema and writes an embedded SQLite index to
.graft/index.db. The index is derived state: your files in Git stay authoritative, and recompiling rebuilds it at any time.The client is the object your pages read through. Create it once, in its own module, so every page shares it:
lib/graft.ts import { openStaticIndex } from "@usegraft/db"; import { createGraft } from "@usegraft/sdk-next"; import { collections } from "@/graft.config"; export const graft = createGraft({ index: await openStaticIndex(".graft/index.db"), collections, });It lives outside
app/on purpose. A Next.js page file may only export the page and its route options, so a client exported fromapp/page.tsxfails the build's type check.Read content in a Server Component
Replace
app/page.tsxto read the sample page and render it:app/page.tsx import { MdxBody } from "@usegraft/sdk-next"; import { notFound } from "next/navigation"; import { graft } from "@/lib/graft"; export default async function Page() { const home = await graft.getContent("pages", "home"); if (!home) notFound(); return ( <main> <h1>{home.data.title}</h1> <p>{home.data.tagline}</p> <MdxBody source={home.body} /> </main> ); }home.datais typed from your schema: hovertitlein your editor and you will seestring.MdxBodyrenders the Markdown body on the server.Start your dev server and open
http://localhost:3000:npm run devYou should see the heading Hello, Graft, its tagline, and the body text from
content/pages/home.mdx. Edit that file, runnpx graft compileagain, and refresh to see the change.
Plain TypeScript (No Framework)
Goal: read a compiled document from a script, with no framework involved.
mkdir my-graft-site && cd my-graft-site npm init -y npm pkg set type=module npm i @usegraft/db @usegraft/sdk-core npm i -D @usegraft/cli tsx typescript @types/node npx @usegraft/cli@latest inittype=modulemakes the project an ES module, so the script in the last step can useawaitat the top level.npx graft compileThis compiles
content/pages/home.mdxinto.graft/index.db.Create
read.ts:read.ts import { openStaticIndex } from "@usegraft/db"; import { createClient } from "@usegraft/sdk-core"; import { collections } from "./graft.config"; const index = await openStaticIndex(".graft/index.db"); const graft = createClient({ index, collections }); const home = await graft.getDocument("pages", "home"); console.log("Title:", home?.data.title); console.log("Tagline:", home?.data.tagline);Without a framework you use
@usegraft/sdk-coredirectly, whose read method isgetDocument. The framework packages wrap the same call and name itgetContent. Both return the same typed document.Run the script:
npx tsx read.tsYou will see the values from
content/pages/home.mdx:Title: Hello, Graft Tagline: Content lives in git; the index is a file.
In your Astro project
Goal: render a compiled document in a zero-JavaScript Astro page.
npm i @usegraft/sdk-astro @usegraft/db npm i -D @usegraft/clinpx @usegraft/cli@latest initnpx graft compileIn
src/pages/index.astro:--- import { openStaticIndex } from "@usegraft/db"; import { createGraft } from "@usegraft/sdk-astro"; import { collections } from "../../graft.config"; const graft = createGraft({ index: await openStaticIndex(".graft/index.db"), collections, }); const home = await graft.getContent("pages", "home"); --- <main> <h1>{home?.data.title}</h1> <p>{home?.data.tagline}</p> </main>
Your framework
Every framework reads the same index with the same typed calls. Pick your guide:
| Framework | Package | Guide |
|---|---|---|
| Next.js (App Router or Pages Router) | @usegraft/sdk-next | Next.js |
| Astro | @usegraft/sdk-astro | Astro |
| SvelteKit | @usegraft/sdk-sveltekit | SvelteKit |
| TanStack Start | @usegraft/sdk-tanstack-start | TanStack Start |
| React Router (framework mode) | @usegraft/sdk-react-router | React Router |
| Nuxt 4.6+ or 5 | @usegraft/sdk-nuxt | Nuxt |
| Nitro 3, or Nuxt before 4.6 | @usegraft/sdk-nitro | Nitro |
| React in the browser | @usegraft/sdk-react | React |
| Vue | @usegraft/sdk-vue | Vue |
| Plain Node or TypeScript | @usegraft/sdk-core | SDK reference |
Add your own content
Each document is a file at content/<collection>/<slug>.mdx:
- A collection is a named group of documents, like
pages. Its fields are declared ingraft.config.ts. - The slug is the document's name inside the collection.
content/pages/about.mdxhas the slugabout. - Frontmatter is the block between the
---lines at the top. Its fields must match the collection's schema, or the compile stops and says which field is wrong. - The rest of the file is the body, in Markdown or MDX (Markdown that can include components).
Create content/pages/about.mdx:
---
title: About us
tagline: We build with code and AI agents.
---
Welcome! This page lives in Git, next to your code.Then compile again, or leave graft dev running so it recompiles whenever a
file changes:
npx graft compile # compile once
npx graft dev # recompile on every change
Read it the same way you read home: graft.getContent("pages", "about").
When you need Postgres
The project you just made uses the static engine, which needs nothing running. Move to Postgres when users of your site write data at runtime, such as comments, orders, or form submissions. Git cannot rebuild that data, so it needs a database. Preview branches need Postgres too.
Switching is one line in graft.config.ts (export const index = "postgres")
plus a database URL. Storage engines walks you through it.
If something goes wrong
These are the errors people hit most often while getting started:
| Error code | What it means | What to do |
|---|---|---|
STATIC_INDEX_NOT_FOUND | Your app read the index before it was compiled | Run npx graft compile, then start your app |
STATIC_INDEX_UNSUPPORTED | Your Node.js version is older than 22.16 | Upgrade Node.js |
SCHEMA_VALIDATION_FAILED | A file's frontmatter does not match its schema | The message names the file and field. Fix it and compile again |
NEEDS_DATABASE | Your config uses a feature that needs Postgres | See When you need Postgres |
Every error also prints a fix line with the exact next step. The
Error reference explains all of them.
Next steps
You have content in Git, a compiled index, and a typed read. From here: