Skip to Content
New to cms0? Start with the hosted, self-hosted, or app integration path.

The shape you pass to cms0<T>() becomes the shape of the client. The CLI writes local descriptor types under .cms0/generated, so every accessor is fully inferred from your schema.

src/cms0.ts
import { cms0 } from "@cms0/cms0"; type RootSchema = { homePage: { headline: string; seo: { title: string; description: string }; }; articles: Array<{ title: string; slug: string }>; }; export const cms = cms0<RootSchema>({ apiConfig: { baseUrl: process.env.CMS0_API_BASE_URL, key: process.env.CMS0_API_KEY, }, });

Accessors are generated from the TypeScript shape that the CLI publishes. If an accessor is missing, run cms0 dev or cms0 build again.

Reading root content

Root content is a singleton. Call the accessor as a function to read it.

src/example.ts
const homePage = await cms.homePage(); // ^? { headline: string; seo: { title: string; description: string } }

Reading collections

Root array fields return all items when called as a function.

src/example.ts
const articles = await cms.articles(); // ^? Array<{ title: string; slug: string }>

Reading models

Reusable model declarations are available under cms.models.

src/example.ts
const posts = await cms.models.Post(); // ^? Post[] const post = await cms.models.Post.byId("post_id"); // ^? Post | null

list() and get() are aliases for the callable accessor and byId():

src/example.ts
const posts = await cms.models.Post.list(); const post = await cms.models.Post.get("post_id");

Querying with .where()

Use .where() to filter models and root arrays by field values. The clause is fully type-inferred: keys are constrained to actual fields, and operator values are constrained to the field’s type.

Exact match

src/example.ts
const drafts = await cms.models.Post.where({ status: "draft" }); const featured = await cms.articles.where({ slug: "featured" });

String operators

src/example.ts
await cms.models.Post.where({ title: { contains: "launch" } }); await cms.models.Post.where({ title: { startsWith: "How to" } }); await cms.models.Post.where({ title: { endsWith: "guide" } }); await cms.models.Post.where({ title: { in: ["Post A", "Post B"] } }); await cms.models.Post.where({ title: { notIn: ["Draft"] } }); await cms.models.Post.where({ title: { isNull: true } });

Number operators

src/example.ts
await cms.models.Post.where({ views: { gt: 100 } }); await cms.models.Post.where({ views: { gte: 50 } }); await cms.models.Post.where({ views: { lt: 1000 } }); await cms.models.Post.where({ views: { lte: 500 } }); await cms.models.Post.where({ views: { between: [10, 100] } }); await cms.models.Post.where({ views: { in: [0, 100, 1000] } });

Boolean operators

src/example.ts
await cms.models.Post.where({ published: true }); await cms.models.Post.where({ published: { eq: false } }); await cms.models.Post.where({ published: { isNull: true } });

Dot-path fields

Filter on nested object fields using dot notation:

src/example.ts
await cms.models.Post.where({ "seo.title": { contains: "guide" } }); await cms.homePage.where({ "author.name": { startsWith: "J" } });

Combining conditions

Use AND, OR, and NOT to build complex queries:

src/example.ts
await cms.models.Post.where({ AND: [ { status: "published" }, { views: { gt: 100 } }, ], }); await cms.models.Post.where({ OR: [ { status: "draft" }, { status: "review" }, ], }); await cms.models.Post.where({ NOT: { status: "archived" }, });

Where with options

.where() accepts the same options as the callable accessor:

src/example.ts
const results = await cms.models.Post.where( { status: "published" }, { fields: ["title", "slug"], graph: { pageSize: 10, orderBy: "views", orderDir: "desc" }, response: "envelope", }, ); // results: { items: Post[], total: number }

.whereFirst()

When you expect a single result, use .whereFirst() instead of .where(). It returns the first matching item or null — no need to index into an array.

src/example.ts
const post = await cms.models.Post.whereFirst({ slug: "launch-notes" }); // ^? Post | null if (post) { console.log(post.title); }

Works on root arrays too:

src/example.ts
const article = await cms.articles.whereFirst({ slug: "featured" });

Nested field access

Chain into nested objects and arrays to read specific fields:

src/example.ts
const headline = await cms.homePage.headline(); // ^? string const seoTitle = await cms.homePage.seo.title(); // ^? string

Field projection

Control which fields are returned using fields (include) and exclude:

src/example.ts
const partial = await cms.homePage({ fields: ["headline", "seo.title"], }); // ^? { headline: string; seo: { title: string } } const withoutSeo = await cms.homePage({ exclude: ["seo"], });

Pagination and ordering

Use graph options to control pagination and sorting for array fields:

src/example.ts
const page = await cms.homePage({ graph: { page: 2, pageSize: 10, orderBy: "title", orderDir: "desc", }, });

Use pageSize: "full" to fetch all items without pagination:

src/example.ts
const all = await cms.articles({ graph: { pageSize: "full" }, });

Per-path overrides

Override pagination for specific nested arrays using graph.paths:

src/example.ts
await cms.homePage({ graph: { pageSize: "full", paths: { "seo.openGraph.images": { pageSize: 50 }, }, }, });

Use graph.search for full-text search across all text fields:

src/example.ts
const results = await cms.articles({ graph: { search: "launch" }, });

Localization

Read content for a specific locale:

src/example.ts
const french = await cms.homePage({ locale: "fr" });

Response modes

Envelope response

Use response: "envelope" to get pagination metadata:

src/example.ts
const envelope = await cms.articles({ response: "envelope", }); // ^? { items: Article[], total: number }

Include IDs

Use includeId: true to include cms0 internal object IDs:

src/example.ts
const withIds = await cms.homePage({ includeId: true });

Raw response

Use response: "raw" to skip normalization:

src/example.ts
const raw = await cms.homePage({ response: "raw" });

Mutating roots

Update a root singleton with a partial value:

src/example.ts
await cms.homePage.update({ headline: "A better headline", });

Mutating fields

Replace, update, or delete individual fields:

src/example.ts
await cms.homePage.headline.set("New headline"); await cms.homePage.headline.update("Updated headline"); await cms.homePage.headline.delete();

For nested objects, .update() patches the object:

src/example.ts
await cms.homePage.seo.update({ title: "New SEO title", });

Mutating arrays

Append, insert, or update items in root arrays:

src/example.ts
await cms.articles.append({ title: "Launch notes", slug: "launch-notes", }); await cms.articles.insert(0, { title: "Pinned post", slug: "pinned", }); await cms.articles.at(0).update({ title: "Updated title", });

Mutating models

Create, update, and delete model instances:

src/example.ts
const newPost = await cms.models.Post.create({ title: "Launch notes", slug: "launch-notes", }); await cms.models.Post.update("post_id", { title: "Updated title", }); await cms.models.Post.delete("post_id");

Abort and signals

Pass an AbortSignal to cancel in-flight requests:

src/example.ts
const controller = new AbortController(); const homePage = await cms.homePage({ signal: controller.signal, }); controller.abort();

Client metadata

Access SDK metadata through cms.meta:

src/example.ts
const { locales, defaultLocale, includeIdDefault } = cms.meta;

Error handling

Accessor calls throw on network errors, 404s, and permission failures:

src/example.ts
try { const post = await cms.models.Post.byId("missing_id"); if (!post) { console.log("Post not found"); } } catch (error) { console.error("Request failed:", error); }

Success check

Accessor calls should match your TypeScript schema. If a root field or model does not exist on the client, run cms0 dev or cms0 build again and confirm the entry file in cms0.config.ts points at the file that calls cms0<T>().