ProductsFor your stack
SDK & CLITyped from your models. Down to the field.
@capacms/sdk is Capa's TypeScript client: REST and GraphQL reads typed from your schema, Next.js cache helpers that refresh on publish, the visual editing overlay, and a CLI for codegen and persisted queries.
import { createClient } from "@capacms/sdk/next";
import type { CapaQuery } from "./capa-graphql";
const capa = createClient<CapaQuery>({ baseUrl, apiKey, version: "2026-10-01" });
const { data } = await capa.graphql.query({
articles: {
args: { first: 5, sort: ["publishedAt_DESC"] },
nodes: { titeltitle: true, author: { name: true } },
},
});
data?.articles?.nodes[0].title;
// data?.articles?.nodes[0].body would not compile: it was not selected(property) title: string | nullWrite the read. Let the types follow.
One client per key, the platform version pinned in code, and the result typed from exactly what you selected.
Typed reads
Pass the types capa-codegen wrote and fields come back typed as the API returns them. Name a field you did not select and it fails to compile.
Next.js, cached and fresh
graphql() keeps a published read in Next's data cache under one tag per model. A publish revalidates exactly those tags.
One webhook route
Verify the signature, then revalidateFromWebhook refreshes the entry, model and media tags the change touched.
Errors you can branch on
A refused request throws CapaError with the API's code, hint and docs link. get() returns null for a missing entry.
import { createClient } from "@capacms/sdk/next";
const capa = createClient({
baseUrl: process.env.CAPA_API_URL!, // https://cdn.capacms.com
apiKey: process.env.CAPA_KEY!,
version: "2026-10-01",
});
const page = await capa.entries.list("articles", {
select: ["title", "views", { author: ["name"] }],
filter: { views: { gte: 10 }, tags: { hasAny: ["news", "launch"] } },
sort: ["-views"],
limit: 25,
});
for await (const entry of capa.entries.iterate("articles", { select: ["title"] })) {
console.log(entry.fields.title);
}Three commands for your build.
The capa and capa-codegen commands ship in the package. They read the same CAPA_API_URL and CAPA_KEY as your app, from your shell or your .env files.
$ capa-codegen --graphql --out src/capa-graphql.ts # one typed module: every model, every #graphql document $ capa persist --release "$GIT_COMMIT" --env production 835d679d63… Latest stored, pinned 1f0c9e2ab4… BlogIndex stored, pinned a77e31c0d8… ArticlePage stored, pinned capa persist: 3 of 3 documents stored, pinned. $ capa-codegen --graphql --out src/capa-graphql.ts --check # exit 0 when current, exit 2 when a model moved
capa-codegenTypes for every model and every GraphQL document in your project. --watch rewrites them as you edit.
capa persistRegisters each document by its sha256, pinned to the release, so the production key only ever sends a hash.
--check in CIFails the build with exit code 2 when a model changed and the committed types did not.
Five imports. Use only what you need.
| Import | What it is |
|---|---|
| @capacms/sdk/next | The client for /api/: entries, GraphQL, pages and preview. Start here. |
| @capacms/sdk/nextjs | Next.js helpers: graphql() in a server component, cache tags, draft and edit mode, webhook revalidation. |
| @capacms/sdk/nextjs/overlay | The visual editing overlay as a Next.js client component. |
| @capacms/sdk/overlay | The same overlay without Next, for any framework. |
| @capacms/sdk | The client for the /v2 API your sites may already use, and the webhook signature check. |
Node 18 or later. Types need TypeScript 5.0 or later, with strict on or off.
The small things a big site needs.
- Builder
Queries as objects
Write GraphQL as a typed object and misspelled fields, arguments and sort values fail to compile, each error naming where it was written.
- Codegen
Commit the output
Generated types live in your repo, so a model change is a reviewable diff. Re-running when nothing moved costs one conditional request.
- Fetch
Plain fetch underneath
The client returns plain data from plain fetch, so Next's cache, React Router loaders and your own wrappers work as they are.
- Shape
Flat when you want it
Ask for shape=flat to get each related entry once, and inflate() turns it back into the nested tree.
- Preview
Edit mode built in
capaAttrs tags what an editor can click. Visitors' pages carry no tags and never download the overlay.
- Agents
Same names as the MCP server
The MCP server writes code against this SDK, with the same environment variables, so what an agent writes runs here.
One install. Typed all the way down.
pnpm add @capacms/sdk@next and your models are types.