graft. docsbeta

React

Browser reads over HTTP. No db. Hooks stay a typed binding.

pnpm add @usegraft/sdk-react
npm i @usegraft/sdk-react
bun add @usegraft/sdk-react

Every other Graft SDK reads on a server. This one reads in the browser, for the places a loader cannot help: a search box, a client-side editor preview, a widget on a page the server rendered an hour ago.

react is a peer dependency, 18 or 19.

Create the handle

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

export const graft = createGraft({
  endpoint: "https://cms.example.com/api/content/v1",
  collections,
});

export const { GraftProvider, useGraft, useContent, useContentList, useContentSearch } =
  createGraftHooks(graft);

Types do not cross the wire. Your app imports its own collections at compile time, exactly as a server adapter does. The endpoint supplies data at runtime.

The hooks come out of a factory rather than being importable directly, and that is what keeps the reads typed.

Read one document

import { useContent } from "../graft";

function Doc({ slug }: { slug: string }) {
  const { data, error, loading } = useContent("docs", slug);

  if (loading) return <Spinner />;
  if (error) return <Problem error={error} />;
  if (!data) return <NotFound />;
  return <article>{data.data.title}</article>;
}

Each hook reports { data, error, loading, refresh }. Changing the slug clears data rather than showing the previous document while the next one loads.

endpoint points at a mount of Graft's read-only content API — what graft serve exposes at /api/content/v1, or your own mount of createContentApiHandler. See Content API.

Mount functions and MCP

There is no graftRoute. Mounting a request handler is a server's job. Use the adapter for the framework that serves the page.

Cache and invalidation

There is none, by construction. No deduplication, no retry, no stale-while-revalidate. graft.getContent is a plain async function, so compose it with TanStack Query or SWR if you want those.

useQuery({
  queryKey: ["docs", slug],
  queryFn: () => graft.getContent("docs", slug),
});

The caveat you cannot infer

There is no db option. A database handle in a browser bundle is a database URL in a browser bundle.

There is also no branch option. An endpoint is pinned to one branch on the server. Passing branch throws CONFIG_INVALID. Point at the preview deployment's own endpoint instead.

useEffect does not run during server rendering, so on the server these hooks report their loading state and nothing else. Content that has to be in the HTML belongs in a loader or a server adapter.

See SDK reference for the shared surface and the honest gaps.