SDK reference

Which package to use, and what differs between frameworks.

Reference5 min read

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.

PackageForNotes
@usegraft/sdk-coreany JSgetDocument / listDocuments / searchDocuments; branch-aware; cache-tag helpers. Postgres lives behind @usegraft/sdk-core/db
@usegraft/sdk-nextNext (RSC)React.cache memo; MdxBody; revalidateContent / updateContent
@usegraft/sdk-astroAstroNo request memo; graftRoute for endpoint mounts
@usegraft/sdk-sveltekitSvelteKitgraftRoute for +server.ts; CDN surrogate keys for tags
@usegraft/sdk-tanstack-startTanStack StartgraftRoute for server routes; build the handle in a server-only module
@usegraft/sdk-react-routerReact Router v7Framework mode; graftRoute returns both action and loader
@usegraft/sdk-nuxtNuxt 4.6+ and 5A module: useGraftContent, useGraftList and useGraftSearch for pages, useGraft() in server/, <GraftMdx>, and the content API, functions and MCP routes
@usegraft/sdk-nitroNitro 3, and Nuxt apps that mount their own routesgraftHandler for event handlers on h3 1 and h3 2; records the caller's address for rate limits
@usegraft/sdk-reactthe browserReads over HTTP; createGraftHooks; no db option, by design
@usegraft/sdk-vueVueReads 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

HelperUse
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

  • MdxBody is Next-only. It evaluates MDX in a Server Component, which no other adapter here has. @usegraft/sdk-vue renders MDX the Pages Router way: prepareMdx on the server, MdxContent on the page. The Next.js Pages Router compiles with prepareMdx in getStaticProps and renders with MdxContent instead. @usegraft/sdk-sveltekit prepares a body into a tree with prepareMdx from @usegraft/sdk-sveltekit/server and renders it with MdxContent from @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.cache equivalent 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-core tag contract onto HTTP: stamp tagsFor(...) into a CDN header and purge tagsForChanges(...) at the CDN when content changes.
  • sdk-react has no cache and no db option, by design. See above.
  • Next.js apps that mount the MCP route need withGraft(nextConfig). It keeps @usegraft/registry out of the server bundle, because that package reads its files from disk at runtime.