Getting started

Go from an empty folder to your content on a page in about 10 minutes.

Get started6 min read

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.

  1. 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/cli
  2. Initialize Graft

    npx @usegraft/cli@latest init

    This scaffolds graft.config.ts, creates sample content in content/pages/home.mdx, and sets up the static index.

  3. Compile your content

    npx graft compile

    This 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.

  4. Create the Graft client

    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 from app/page.tsx fails the build's type check.

  5. Read content in a Server Component

    Replace app/page.tsx to 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.data is typed from your schema: hover title in your editor and you will see string. MdxBody renders the Markdown body on the server.

    Start your dev server and open http://localhost:3000:

    npm run dev

    You should see the heading Hello, Graft, its tagline, and the body text from content/pages/home.mdx. Edit that file, run npx graft compile again, and refresh to see the change.

Plain TypeScript (No Framework)

Goal: read a compiled document from a script, with no framework involved.

  1. Scaffold a project folder

    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 init

    type=module makes the project an ES module, so the script in the last step can use await at the top level.

  2. Compile content

    npx graft compile

    This compiles content/pages/home.mdx into .graft/index.db.

  3. Read and verify output

    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-core directly, whose read method is getDocument. The framework packages wrap the same call and name it getContent. Both return the same typed document.

    Run the script:

    npx tsx read.ts

    You 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.

  1. Install the Astro SDK and CLI

    npm i @usegraft/sdk-astro @usegraft/db
    npm i -D @usegraft/cli
  2. Initialize Graft

    npx @usegraft/cli@latest init
  3. Compile your content

    npx graft compile
  4. Read content in an Astro page

    In 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:

FrameworkPackageGuide
Next.js (App Router or Pages Router)@usegraft/sdk-nextNext.js
Astro@usegraft/sdk-astroAstro
SvelteKit@usegraft/sdk-sveltekitSvelteKit
TanStack Start@usegraft/sdk-tanstack-startTanStack Start
React Router (framework mode)@usegraft/sdk-react-routerReact Router
Nuxt 4.6+ or 5@usegraft/sdk-nuxtNuxt
Nitro 3, or Nuxt before 4.6@usegraft/sdk-nitroNitro
React in the browser@usegraft/sdk-reactReact
Vue@usegraft/sdk-vueVue
Plain Node or TypeScript@usegraft/sdk-coreSDK 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 in graft.config.ts.
  • The slug is the document's name inside the collection. content/pages/about.mdx has the slug about.
  • 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:

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 codeWhat it meansWhat to do
STATIC_INDEX_NOT_FOUNDYour app read the index before it was compiledRun npx graft compile, then start your app
STATIC_INDEX_UNSUPPORTEDYour Node.js version is older than 22.16Upgrade Node.js
SCHEMA_VALIDATION_FAILEDA file's frontmatter does not match its schemaThe message names the file and field. Fix it and compile again
NEEDS_DATABASEYour config uses a feature that needs PostgresSee 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: