■ Developers
An API you can cache, a model you can diff.
Your public sites stay your own applications, in whatever framework you choose. ModularCMS gives them a versioned Delivery API, typed clients, one tag for widgets, and a CLI that puts structure in Git.
§ 01 Delivery API
One envelope. Every endpoint cacheable.
Responses carry ETag, Last-Modified, and Cache-Control, are keyed on the site's publish version, and never vary per visitor. Errors come in the same { data, errors, meta } envelope.
/delivery/api/v1
- GET
/content/item/{id | path | alias:…}
One item with blocks inline and references expanded to the depth you ask for
- GET
/content?fetch=&filter=&sort=
Children, descendants, or ancestors, filtered and sorted on indexed fields
- GET
/queries/{alias}
A saved query editors maintain, served by alias
- GET
/search?q=
Search over published content, tuned per site
- GET
/media/item/{id | path}
Assets with renditions, crops, focal points, and captions
- GET
/menus/{alias} · /taxonomies/{alias}
Navigation and vocabularies, per language
- GET
/redirects · /sitemap.xml · /robots.txt
Everything your router and crawlers need
- GET
/feeds/{alias}
RSS, Atom, and JSON Feed
- POST
/forms/{alias}/submissions
Submissions with spam protection and uploads
- POST
/personalize
Uncached decisions: segments, variants, and recommendations
- GET
/recommendations/{slot}
The candidate pool for a slot, the same for every visitor
200 content/item/news/2026/winter-season
{
"data": {
"id": "6f1c2a7e-2f6e-4f1d-9a8e-3f2b0c1d4e55",
"contentType": "article",
"name": "Winter season",
"route": { "path": "/news/2026/winter-season" },
"language": "en-US",
"translations": [{ "language": "ja", "path": "/ja/news/2026/winter-season" }],
"properties": {
"title": "Alpenglow opens its winter season",
"body": { "type": "richText", "document": { … }, "html": "<p>…</p>" },
"heroImage": { "url": "https://cdn.example.com/…", "focalPoint": { "x": 0.5, "y": 0.3 } },
"sections": { "type": "blockList", "items": [
{ "content": { "contentType": "quoteBlock", "properties": { … } },
"settings": { "properties": { "align": "left" } },
"consentCategory": null }
] }
},
"seo": { "title": "…", "canonical": "…", "structuredData": [ … ], "alternates": [ … ] }
},
"errors": [],
"meta": { "preview": false, "cache": { "etag": "\"a1b2\"" } }
}- Rich text both ways. The TipTap document for custom rendering, plus HTML when you just need it.
- Consent in the payload. Blocks that load third-party content name their consent category.
- Nothing leaks. Restricted properties never appear for callers without access; member responses are private and no-store.
§ 02 SDK and adapter
Typed clients, generated, not hand-written.
The SDK, the backoffice client, and the reference all come from one OpenAPI document, so they cannot drift from the API.
- @modularcms/delivery
- Framework-agnostic client for any JavaScript runtime: content, media, search, menus, forms, personalization, and preview.
- @modularcms/next
- App Router adapter: draft mode, revalidation, the Image loader, RichText and Blocks, SEO metadata and JSON-LD, sitemaps, feeds, member sign-in, and personalization slots.
- modularcms types
- Generates TypeScript types for your own content model from the instance.
app/[...slug]/page.tsx
import { Blocks, getDeliveryClient, seoToMetadata, cacheTags } from "@modularcms/next";
import { expand } from "@modularcms/delivery";
import { Hero, CardGrid, Quote } from "@/components/blocks";
export async function generateMetadata({ params }) {
const path = "/" + (await params).slug.join("/");
const page = await getDeliveryClient().content.item(path);
return page ? seoToMetadata(page.seo, page) : {};
}
export default async function Page({ params }) {
const path = "/" + (await params).slug.join("/");
const cms = getDeliveryClient({ tags: [cacheTags.path(path)] });
const page = await cms.content.item(path, { expand: expand({ blocks: true }) });
return (
<Blocks
items={page?.properties.sections?.items}
components={{ hero: Hero, cardGrid: CardGrid, quoteBlock: Quote }}
/>
);
}The adapter wires draft mode, cache tags, SEO metadata, images, rich text, menus, and personalization into the App Router. The same page code renders drafts and published content.
§ 03 CLI, structure as code, and Git
Put the content model in Git. Promote it like code.
Content types, element types, and settings export to files. Plans show exactly what will change; drift detection shows what someone changed by hand.
The modularcms CLI
Structure, packages, staging promotion, snapshots, migrations, keys, webhooks, search, cache, and jobs, with --json output for pipelines. Ships as a Node package, a single binary, and a container image.
Structure as code
Export, diff, plan, and apply. A site can be set so structure is only changed from files, and the backoffice shows it read-only with the drift.
Git sync
Mirror published content, approved drafts, and structure to a repository. Structure syncs both ways; releases open pull requests on GitHub that merge when they publish.
§ 04 Management API
The backoffice is just a client.
Everything an editor can do, an integration can do: content, media, structure, users, settings, and operations, with the same permissions.
- · Optimistic concurrency with If-Match on every write
- · Idempotency keys for safe retries
- · Long operations as background jobs with progress you can follow
- · Management API keys whose rights are the key’s scopes intersected with the owner’s current rights
- · An in-backoffice explorer with Try it, and curl, fetch, and SDK snippets
§ 05 Webhooks
Know the moment something changes.
Per-site webhooks for publishing, media, members, forms, and more, signed with rotating secrets and retried from a durable queue.
- · Payloads carry the new publish version for exact revalidation
- · Overlapping signatures while a secret rotates
- · Delivery log with redelivery from the backoffice or the CLI
- · Targets on private addresses refused by default
§ 06 Extend
Plugin points, not forks.
Every capability that touches infrastructure sits behind an interface with a self-hostable baseline and a shared contract test suite. Plugins are signed packages, installed per instance, with their own configuration schema.
- Storage
- CDN
- Secrets
- Search
- Analytics
- Git
- Chat channels
- Marketing platforms
- External data sources
- Migration sources
- Form spam protection
- Form actions
- Automation actions
- Dashboard widgets
- Field types
- Malware scanning
- AI (Phase 3)
Signed packages
ECDSA-signed packages from publishers on a platform allow list. Instance administrators install; the platform loads them in-process with a rolling restart.
Backoffice extensions
Plugins add pages, field types, and API endpoints. Client code loads as ES modules with subresource integrity under the backoffice's strict CSP.
Contract tests
Every implementation of a plugin point must pass that point's contract suite, so a third-party storage backend behaves like the bundled one.
■ Ready for press
See your content set in modules.
We’ll walk through the backoffice with your own site in mind, or import a copy of your Umbraco site and show you the result.