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 |
Find one
Section titled “Find one”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 |
Find by ID
Section titled “Find by ID”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.
Create
Section titled “Create”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.
Create many
Section titled “Create many”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.
Upsert
Section titled “Upsert”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.
Update
Section titled “Update”const post = await cms.posts.update("abc123", { title: "Updated Title",});Only include fields you want to change. Returns the updated document.
Delete
Section titled “Delete”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.
Delete many
Section titled “Delete many”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.
Publish / unpublish
Section titled “Publish / unpublish”await cms.posts.publish("abc123");await cms.posts.unpublish("abc123");Discard draft
Section titled “Discard draft”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.
Schedule
Section titled “Schedule”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).
Versions
Section titled “Versions”const versions = await cms.posts.versions("abc123");// → [{ version: 5, createdAt: "...", snapshot: {...} }, ...]
await cms.posts.restore("abc123", 5);Translations
Section titled “Translations”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).
Access context
Section titled “Access context”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.
Introspection
Section titled “Introspection”cms.meta.getCollections(); // All collections with metadatacms.meta.getFields("posts"); // Field definitions for a collectioncms.meta.getCollection("posts"); // Full collection configcms.meta.getRouteForDocument("posts", doc); // Public URL for a documentcms.meta.getLocales(); // { default, supported }cms.meta.isTranslatableField("posts", "title"); // true/falsecms.meta.getConfig(); // Full CMS config objectBackground task helpers also exist as cms.tasks.enqueue, cms.tasks.drain, cms.tasks.tick, and cms.tasks.prune. See Background Tasks.
Usage in Astro pages
Section titled “Usage in Astro pages”---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.