graft. docs

Reading content

Typed reads with sdk-core and the six framework adapters that wrap it.

All reads go through @usegraft/sdk-core: getDocument, listDocuments, and searchDocuments. Each call re-validates against your Zod schema. Soft-deleted index rows are excluded.

Framework SDKs wrap that surface:

PackageEntryNotes
@usegraft/sdk-nextcreateGraftPer-request memo via React.cache; MdxBody for React MDX
@usegraft/sdk-astrocreateGraftNo request memo; use from frontmatter and endpoints only
@usegraft/sdk-sveltekitcreateGraftSame shared surface; graftRoute for +server.ts
@usegraft/sdk-tanstack-startcreateGraftServer functions and server routes; loaders are isomorphic, so keep the handle out of them
@usegraft/sdk-react-routercreateGraftFramework mode; build the handle in a .server.ts module
@usegraft/sdk-reactcreateGraftRuns in the browser; takes an endpoint, never a db

Shared API

const graft = createGraft({ db, collections, branch: "main" });

await graft.getContent("pages", "home");       // Document | null
await graft.listContent("docs");               // Document[]
await graft.searchContent("docs", "branching"); // ranked hits + snippets

Pass branch to resolve overlay or Neon preview branches. See Branching & previews.

Remote runtime

createContentApiReader implements the same index seam against a graft serve process. Every server SDK keeps its existing surface; only the transport changes. The endpoint is one branch, so a caller cannot switch a production read onto preview content.

import { createContentApiReader } from "@usegraft/content-api";
import { createGraft } from "@usegraft/sdk-astro";
import { collections } from "../../graft.config";

export const graft = createGraft({
  index: createContentApiReader({
    endpoint: "https://cms.example.com/api/content/v1",
    headers: { authorization: `Bearer ${process.env.GRAFT_CONTENT_TOKEN}` },
  }),
  collections,
});

graft serve mounts the matching handler at /api/content/v1/documents and /api/content/v1/search.

Astro

// src/lib/graft.ts
import { createDb } from "@usegraft/db";
import { createGraft } from "@usegraft/sdk-astro";
import { collections } from "../../graft.config";

export const graft = createGraft({
  db: createDb(process.env.DATABASE_URL!).db,
  collections,
});

Call it from .astro frontmatter, endpoints, and middleware. Do not import it into client islands — it holds a database handle.

Next.js

import { createGraft } from "@usegraft/sdk-next";

export const graft = createGraft({ db, collections });
// getContent / listContent / searchContent are React.cache-deduped per request

Render authored bodies with MdxBody and your generated components/mdx-components.ts map when you use registry blocks.

SvelteKit

import { createGraft } from "@usegraft/sdk-sveltekit";

export const graft = createGraft({ db, collections });

Build the handle in a server-only module and read it from +page.server.ts or +layout.server.ts. A universal load in +page.ts or +layout.ts also runs in the browser on client-side navigation, so a database connection does not belong there — the same care TanStack Start needs, under different filenames.

Mount functions and MCP with graftRoute on +server.ts endpoints. See SDK reference.

TanStack Start

// src/lib/graft.server.ts
import { createGraft } from "@usegraft/sdk-tanstack-start";

export const graft = createGraft({ db, collections });

The filename is the point. Start's route loaders are isomorphic — they run on the server for the first paint and in the browser on client-side navigation — so a handle holding a database connection belongs in a server function or a server route, never in a loader.

Astro frontmatter is the only adapter here that is server-side unconditionally.

React Router v7

// app/lib/graft.server.ts
import { createGraft } from "@usegraft/sdk-react-router";

export const graft = createGraft({ db, collections });

Framework mode only, which is where React Router runs loaders and actions on a server. Import the handle from loaders and actions.

The browser

import { createGraft, createGraftHooks } from "@usegraft/sdk-react";
import { collections } from "../graft.config";

export const graft = createGraft({ endpoint: "/api/content/v1", collections });
export const { useContent, useContentList, useContentSearch } =
  createGraftHooks(graft);

This is the one read that does not touch a database. It goes over the content API, for the places a loader cannot help: a search box, an editor preview, a widget on a page the server rendered an hour ago. Types still do not cross the wire — the app imports its own collections at compile time, exactly as a server adapter does, so getContent("docs", slug) returns the type your schema declared. See Content API.

Cache tags

tagsFor(collection, slug?) names what a read depends on. tagsForChanges(branch, changeSet) names what a compile invalidated.

  • Next: map tags onto 'use cache' + revalidateTag / updateTag
  • Everything else: stamp CDN surrogate-key headers; purge from your compile webhook. Next is the only adapter here with a tag-based data cache of its own