# SDK reference

> Every Graft SDK package, what each one adds, how to mount Graft routes, cache tags, and where the frameworks differ.

*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](/docs/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

```ts
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](/docs/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.

```ts
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:

```ts
// 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](/docs/next#refresh-pages-when-content-changes)).
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.
