SDK reference
Which package to use, and what differs between frameworks.
Use this page to look up a package, compare frameworks, mount Graft's routes, or wire up cache tags. To learn the three reads themselves, start with Reading content. Your framework's guide covers installing and creating the client.
Every package reads through @usegraft/sdk-core. The framework packages wrap
it, adjust it to how each framework works, and rename the reads
(getDocument becomes getContent, and so on). The same graft.config.ts
types all of them.
| Package | For | Notes |
|---|---|---|
@usegraft/sdk-core | any JS | getDocument / listDocuments / searchDocuments; branch-aware; cache-tag helpers. Postgres lives behind @usegraft/sdk-core/db |
@usegraft/sdk-next | Next (RSC) | React.cache memo; MdxBody; revalidateContent / updateContent |
@usegraft/sdk-astro | Astro | No request memo; graftRoute for endpoint mounts |
@usegraft/sdk-sveltekit | SvelteKit | graftRoute for +server.ts; CDN surrogate keys for tags |
@usegraft/sdk-tanstack-start | TanStack Start | graftRoute for server routes; build the handle in a server-only module |
@usegraft/sdk-react-router | React Router v7 | Framework mode; graftRoute returns both action and loader |
@usegraft/sdk-nuxt | Nuxt 4.6+ and 5 | A module: useGraftContent, useGraftList and useGraftSearch for pages, useGraft() in server/, <GraftMdx>, and the content API, functions and MCP routes |
@usegraft/sdk-nitro | Nitro 3, and Nuxt apps that mount their own routes | graftHandler for event handlers on h3 1 and h3 2; records the caller's address for rate limits |
@usegraft/sdk-react | the browser | Reads over HTTP; createGraftHooks; no db option, by design |
@usegraft/sdk-vue | Vue | Reads over HTTP; createGraftComposables; MdxContent for MDX, compiled by prepareMdx from @usegraft/sdk-vue/server |
Every adapter is typed structurally, so none of them takes a dependency on
the framework it adapts and there is no peer version to keep in step. The
exception is @usegraft/sdk-nuxt: a Nuxt module builds on @nuxt/kit, and
supports Nuxt 4.6 and newer.
The read API
const graft = createGraft({ db, collections, branch: "main" });
await graft.getContent("pages", "home");
await graft.listContent("docs");
await graft.searchContent("docs", "branching");
Unknown collection names are compile-time errors when you pass your
collections map. Reads re-validate; a poisoned index row throws.
Every framework package accepts db directly, as above. createClient in
@usegraft/sdk-core does not: it takes an index. For Postgres without a
framework, use createDbClient from @usegraft/sdk-core/db. Keeping the
database code on its own entry point means a browser install never pulls in a
Postgres driver.
Pass index instead of db to read a static SQLite artifact or a remote
graft serve through createContentApiReader. The SDK surface does not
change. See Reading content.
@usegraft/sdk-react is the exception, and deliberately so: it takes an
endpoint, never a db, because a Postgres handle in a browser bundle is a
database URL in a browser bundle. It also refuses branch with a
CONFIG_INVALID, since a graft serve endpoint pins its own branch and ignores
the query param. Point at the preview's own endpoint instead. Its hooks come
from createGraftHooks(graft) rather than being importable directly, which is
what keeps them typed to your collections.
const graft = createGraft({ endpoint, collections });
const { useContent } = createGraftHooks(graft);
const { data, error, loading, refresh } = useContent("docs", slug);
They are a binding, not a cache: no deduplication, no retries, no
stale-while-revalidate. graft.getContent is a plain async function, so compose
it with TanStack Query or SWR if you want those.
Mount Graft's routes in your app
Handlers are Web-standard. Mounts are thin:
// Astro: src/pages/api/fn/[name].ts
export const POST = graftRoute(functionsHandler);
// SvelteKit: src/routes/api/fn/[name]/+server.ts
export const POST = graftRoute(functionsHandler);
// TanStack Start: src/routes/api/fn/$name.ts
export const ServerRoute = createServerFileRoute("/api/fn/$name").methods({
POST: graftRoute(functionsHandler),
GET: graftRoute(functionsHandler), // answers 405
});
// React Router v7: app/routes/api.fn.$name.ts
export const action = graftRoute(functionsHandler); // POST
export const loader = graftRoute(functionsHandler); // GET, answers 405
// Nuxt or Nitro: server/api/fn/[name].ts
export default defineEventHandler(graftHandler(functionsHandler));
// Next: app/api/fn/[name]/route.ts
export const POST = functionsHandler;
MCP mounts the same way with createGraftMcpHandler.
React Router is the one that looks wrong and is not. It splits a route by method
into two exports rather than naming the method, so the handler is mounted twice.
Exporting loader is what makes a GET answer with Graft's 405 and its Allow
header and fix, instead of React Router's own "no loader" error, which teaches
the caller nothing.
Cache tags
| Helper | Use |
|---|---|
tagsFor(branch, collection, slug?) | The tags a read depends on |
tagsForChanges(branch, changes) | The tags to refresh after content changes |
In the Next.js App Router, tag cached reads and refresh them with
revalidateContent. On Next.js 16, tag reads with 'use cache' and
cacheTag. On Next.js 15, wrap them in unstable_cache with tags
(how).
Every other framework sends the tags in a CDN header and purges them at the
CDN. Unchanged documents are never refreshed, and a change on a preview branch
never refreshes main.
Where the frameworks differ
MdxBodyis Next-only. It evaluates MDX in a Server Component, which no other adapter here has.@usegraft/sdk-vuerenders MDX the Pages Router way:prepareMdxon the server,MdxContenton the page. The Next.js Pages Router compiles withprepareMdxingetStaticPropsand renders withMdxContentinstead.@usegraft/sdk-sveltekitprepares a body into a tree withprepareMdxfrom@usegraft/sdk-sveltekit/serverand renders it withMdxContentfrom@usegraft/sdk-sveltekit/mdx, evaluating nothing. Astro authors bodies for@astrojs/mdx; TanStack Start and React Router render authored source with their own pipeline.- No
React.cacheequivalent outside Next. Call sites own memoization. TanStack Start route loaders are isomorphic, running on the server for first paint and in the browser on client navigation, so a handle holding a database connection belongs in a server function or server route and never in a loader. - No tag-based data cache outside Next. Everything else maps the
@usegraft/sdk-coretag contract onto HTTP: stamptagsFor(...)into a CDN header and purgetagsForChanges(...)at the CDN when content changes. sdk-reacthas no cache and nodboption, by design. See above.- Next.js apps that mount the MCP route need
withGraft(nextConfig). It keeps@usegraft/registryout of the server bundle, because that package reads its files from disk at runtime.