Skip to content

SDK

createClient, typed reads, generated types, preview and webhook verification.

DocsAPI

Docs menuSDK

@manifolio/sdk reads content from Next.js, Astro, Cloudflare Workers or any Node 18+ server. It has no dependencies, types every entry and verifies webhooks.

Shell
npm i @manifolio/sdk

createClient

TypeScript
import { createClient } from "@manifolio/sdk";

export const cms = createClient({
  project: process.env.MANIFOLIO_PROJECT!,   // the project id
  key: process.env.MANIFOLIO_KEY!,           // mf_live_ or mf_test_
  locale: "en",                              // default language of every request
});
OptionMeaning
projectthe project id, from Developers → API keys → Connection
keya secret key; publishable mf_pk_ keys are refused
localethe default language; otherwise the project's main language
environmentproduction or development; when set, it must match the key
previewread drafts through the Preview API (the key needs content:preview)
baseUrldefaults to https://api.manifolio.com
fetchyour own fetch, for example a Cloudflare service binding
fetchOptionsadded to every request, for example { next: { revalidate: 60 } } in Next.js

The SDK refuses to run with a secret key in the browser.

Reading

TypeScript
await cms.entries("article", {
  locale: "ro",                        // falls back to the main language, field by field
  fields: ["title", "slug", "date"],
  sort: "-date",                       // published, updated or a field; - for descending
  filter: { series: "autumn", date: { gte: "2026-01-01" }, tags: ["coffee", "events"] },
  populate: 2,                         // references become entries, two levels deep
});

await cms.page("article", { limit: 20, cursor });   // { entries, nextCursor }
await cms.entry("article", "first-post");           // by slug or id, null when missing
await cms.singleton("site_settings");               // the entry of a singleton type
await cms.types();                                  // languages and the public schema

entries follows nextCursor page by page until it has everything, or max entries. An array in filter means in.

Every entry carries _id, _type, _locale, _publishedAt and _updatedAt next to its fields.

Errors

Failed requests throw ManifolioError with status and code:

TypeScript
import { ManifolioError } from "@manifolio/sdk";

try {
  await cms.entries("article");
} catch (e) {
  if (e instanceof ManifolioError && e.code === "type_not_found") {
    // the type was renamed or removed
  }
}

entry and singleton return null for a missing entry instead of throwing.

Typed content

Shell
npx manifolio types --out src/manifolio.d.ts

The command reads MANIFOLIO_PROJECT and MANIFOLIO_KEY (also from .env and .env.local), or --project, --key and --base-url. The generated file registers your types with the SDK: cms.entries("article") returns Article entries, field names autocomplete in fields, filter and sort, and an unknown type key is a type error. Run it again after you change the structure.

Preview

TypeScript
export const preview = createClient({
  project: process.env.MANIFOLIO_PROJECT!,
  key: process.env.MANIFOLIO_PREVIEW_KEY!,   // a key with content:preview
  preview: true,
});

const draft = await preview.entry("article", "first-post");
draft?._status;   // "draft", "published" or "changed"

Use it behind your framework's draft mode, so only editors see unpublished content.

Frameworks

Next.js (App Router), with a revalidation window:

TypeScript
export const cms = createClient({
  project: process.env.MANIFOLIO_PROJECT!,
  key: process.env.MANIFOLIO_KEY!,
  fetchOptions: { next: { revalidate: 60 } },
});

Astro, read in the page frontmatter (at build time for static pages, per request with an adapter):

TypeScript
import { createClient } from "@manifolio/sdk";

const cms = createClient({ project: import.meta.env.MANIFOLIO_PROJECT, key: import.meta.env.MANIFOLIO_KEY });
const articles = await cms.entries("article", { sort: "-published" });

Cloudflare Workers, through a service binding:

TypeScript
const cms = createClient({ project: env.MANIFOLIO_PROJECT, key: env.MANIFOLIO_KEY, fetch: (req, init) => env.API.fetch(req, init) });

Webhook verification

TypeScript
import { parseWebhook, verifyWebhook } from "@manifolio/sdk";

const body = await req.text();   // the raw body, exactly as it arrived
const event = await parseWebhook(process.env.MANIFOLIO_WEBHOOK_SECRET!, body, req.headers.get("manifolio-signature"));

parseWebhook throws ManifolioError(401, "invalid_signature") when the signature does not match or is older than five minutes. verifyWebhook returns true or false instead. More in Webhooks.