Skip to content
Modularcms

■  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.

npm · binary · image

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.

plan → apply

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.

plain Git · GitHub

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
  • Email
  • 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.