Skip to content

Local API

Import the generated API and call it directly without HTTP overhead. All queries and return types are fully typed based on your collection definitions.

import { cms } from "@/cms/.generated/api";

Every collection is available as cms.<collection-slug>. For example, if you have collections posts, pages, and users:

cms.posts.find({ ... })
cms.pages.findOne({ slug: "about" })
cms.users.findById("abc123")
const posts = await cms.posts.find({
where: { category: "tech" },
sort: { field: "_updatedAt", direction: "desc" },
limit: 10,
offset: 0,
status: "published",
locale: "fi",
});
Option Type Default Description
where Record<string, unknown> Filter by field values
sort { field, direction } Sort by field, "asc" or "desc"
limit number Max documents to return
offset number Skip N documents
status "draft" | "published" | "scheduled" | "any" "published" (draft-enabled collections) / "any" (others) Filter by status
locale string default locale Language code for translations
availability "fallback" | "exact" "fallback" With locale: "fallback" returns every document, overlaying its translation when one exists; "exact" returns only documents that exist in that locale (their _sourceLocale or a translation). Use "exact" for listings, "fallback" for single-document routes
search string Substring search across text, slug, email, and select fields
const post = await cms.posts.findOne({
slug: "hello-world",
locale: "fi",
status: "any",
});
Option Type Default Description
field filters varies Filter by any field (e.g. slug, email)
locale string default locale Language code
availability "fallback" | "exact" "fallback" See Find
status "draft" | "published" | "scheduled" | "any" "published" (draft-enabled collections) / "any" (others) Status filter
const post = await cms.posts.findById("abc123", {
locale: "fi",
status: "any",
});
Option Type Default Description
locale string default locale Language code
status "draft" | "published" | "scheduled" | "any" see below Status filter

findById filters by status only when status is passed explicitly: a value other than "any" returns null unless the document has that status. By default the document is returned regardless of status. For draft-enabled collections, the default also overlays the last-published snapshot values on the result (the same happens with an explicit "published"). Pass status: "any" to read the current working values without filtering.

const post = await cms.posts.create({
title: "New Post",
body: { type: "root", children: [...] },
_status: "published", // optional, defaults to "draft" if drafts enabled
});

Pass field values as properties. Returns the created document. You can supply your own _id to make imports idempotent; otherwise one is generated.

const posts = await cms.posts.createMany([{ title: "A" }, { title: "B" }]);

Runs the documents through create sequentially and returns the created documents. See Migrations for bulk-import patterns.

await cms.posts.upsert({ _id: "abc123", title: "Hello" });

Updates when a document with data._id exists, otherwise creates. Combined with caller-supplied _ids this makes imports re-runnable without a wipe-first step.

const post = await cms.posts.update("abc123", {
title: "Updated Title",
});

Only include fields you want to change. Returns the updated document.

await cms.posts.delete("abc123");

Cascades: removes translations, versions, and the document. Returns true if a row was removed, false if the document was not found. The last remaining admin in an auth collection can’t be deleted.

const removed = await cms.posts.deleteMany({ category: "tech" });

Deletes every document matching the filter (all documents when the filter is omitted) and returns the number removed. Like discardDraft, it is not yet in the generated types, so TypeScript flags the call although it works.

await cms.posts.publish("abc123");
await cms.posts.unpublish("abc123");
await cms.posts.discardDraft("abc123");

Reverts a published document’s pending changes back to the last-published content. Only works on published documents that have been edited. Not yet in the generated types.

await cms.posts.schedule(
"abc123",
"2025-06-01T00:00:00Z", // publishAt (required)
"2025-07-01T00:00:00Z", // unpublishAt (optional)
);

Sets status to "scheduled". A cron job publishes it at the given time: built in on Cloudflare, set up by you on Node. See Deploy.

const total = await cms.posts.count({ status: "published" });

Accepts same filter options as find (without limit, offset, sort).

const versions = await cms.posts.versions("abc123");
// → [{ version: 5, createdAt: "...", snapshot: {...} }, ...]
await cms.posts.restore("abc123", 5);
const translations = await cms.posts.getTranslations("abc123");
// → { fi: { title: "...", body: {...} } }
await cms.posts.upsertTranslation("abc123", "fi", {
title: "Hei maailma",
body: { type: "root", children: [...] },
});

upsertTranslation inserts or updates. Only include translatable fields. It refuses the document’s own content language (_sourceLocale) — that text lives on the document itself; see Content language.

Every read result carries _sourceLocale and _availableLocales (source locale first, then every locale with a translation).

Every method accepts an optional context object as its last argument. Pass the signed-in admin user to enforce field-level access, or _system: true to bypass all access rules (for every operation: create/update/delete/read) in trusted server code such as public API routes, task handlers, and seed scripts:

await cms.posts.find({}, { user });
await cms["form-submissions"].create(data, { _system: true });

_skipSearch: true is a runtime-only bulk-import flag that skips per-document search indexing. It is not part of the generated context type.

cms.meta.getCollections(); // All collections with metadata
cms.meta.getFields("posts"); // Field definitions for a collection
cms.meta.getCollection("posts"); // Full collection config
cms.meta.getRouteForDocument("posts", doc); // Public URL for a document
cms.meta.getLocales(); // { default, supported }
cms.meta.isTranslatableField("posts", "title"); // true/false
cms.meta.getConfig(); // Full CMS config object

Background task helpers also exist as cms.tasks.enqueue, cms.tasks.drain, cms.tasks.tick, and cms.tasks.prune. See Background Tasks.

---
import { cms } from "@/cms/.generated/api";
const post = await cms.posts.findOne({ slug: Astro.params.slug });
if (!post) return Astro.redirect("/404");
---
<h1>{post.title}</h1>

Rendering patterns, live preview, and caching are in Public Pages.