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:
| Package | Entry | Notes |
|---|---|---|
@usegraft/sdk-next | createGraft | Per-request memo via React.cache; MdxBody for React MDX |
@usegraft/sdk-astro | createGraft | No request memo; use from frontmatter and endpoints only |
@usegraft/sdk-sveltekit | createGraft | Same shared surface; graftRoute for +server.ts |
@usegraft/sdk-tanstack-start | createGraft | Server functions and server routes; loaders are isomorphic, so keep the handle out of them |
@usegraft/sdk-react-router | createGraft | Framework mode; build the handle in a .server.ts module |
@usegraft/sdk-react | createGraft | Runs 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