@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.
npm i @manifolio/sdkcreateClient
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
});| Option | Meaning |
|---|---|
project | the project id, from Developers → API keys → Connection |
key | a secret key; publishable mf_pk_ keys are refused |
locale | the default language; otherwise the project's main language |
environment | production or development; when set, it must match the key |
preview | read drafts through the Preview API (the key needs content:preview) |
baseUrl | defaults to https://api.manifolio.com |
fetch | your own fetch, for example a Cloudflare service binding |
fetchOptions | added to every request, for example { next: { revalidate: 60 } } in Next.js |
The SDK refuses to run with a secret key in the browser.
Reading
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 schemaentries 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:
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
npx manifolio types --out src/manifolio.d.tsThe 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
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:
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):
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:
const cms = createClient({ project: env.MANIFOLIO_PROJECT, key: env.MANIFOLIO_KEY, fetch: (req, init) => env.API.fetch(req, init) });Webhook verification
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.