An alternative to Contentful and DatoCMS

One panel, N projects, a bill that doesn't grow with traffic

API-first, multi-tenant headless CMS. The read path never reaches the API — as visitors grow, it's your cache hit rate that rises, not your bill.

Your data lives on servers we runNo metered third-party servicesSelf-hosted installation on the Enterprise plan

The read path in numbers

Database queries per read
0
HTTP requests per page
2
Typo-tolerant search response
<50 ms
Individually disableable modules
7+

Every component on our own infrastructure

PostgreSQL
Valkey
Typesense
Garage · S3-compatible
nginx edge cache
NestJS
Drizzle
Next.js

Features

Content infrastructure, minus the headache

Everything sits behind the API; no feature is locked to the panel.

Multi-tenancy and RLS

Every project is a separate space. Isolation lives in the database, not the application: PostgreSQL row-level security keeps one tenant's data from leaking into another's even when the application layer gets it wrong.

Schema engine and field roles

Content types aren't baked into code; you define them from the panel or the API. Fields carry roles such as title, slug and body — the build pipeline and SEO generation look at the role, not the field name.

Module architecture

Media, search, webhooks, SEO, GraphQL, import, analytics — each one separate and disableable. Disabling never deletes data; turn a module back on and it picks up where it left off.

Reads served from the edge

Published content is written to an immutable address at build time and served from the nginx edge cache. Neither the number of entries nor the number of visitors raises the cost of a read.

Media library

S3-compatible object storage (Garage), images replicated across three zones and automatic derivatives. Files you upload stay on our own infrastructure — they never go to a metered CDN.

Typo-tolerant search

Multilingual full-text search powered by Typesense. A visitor who types "Istanul" still finds the right entry, and the response comes back in under 50 milliseconds.

Webhooks

Publish, update, delete — your systems hear about every event. Delivery is held in a durable queue and retried with exponential backoff; even if the process dies, the event survives.

GraphQL

The schema isn't hand-written; it's derived from the space's content types. Add a new type and it becomes queryable. Depth and length limits are mandatory, as protection against query bombs.

Type-safe SDK

@corpusctl/client with React, Vue and Next.js adapters. Block renderers ship with zero default styling — we don't impose a single line of CSS; the design is entirely yours.

Code examples

Do it from the panel, or in a single command

Everything you can do in the panel has an API equivalent; no feature is locked to the interface.

publish.sh
# 1) Define the content type — the schema isn't baked into code
curl -X PUT "$API/v1/content-types/post" \
  -H "authorization: Bearer $TOKEN" \
  -H "x-corpusctl-tenant: $TENANT" \
  -H "content-type: application/json" \
  -d '{
    "name": "post",
    "title": "Post",
    "fields": [
      { "name": "title", "type": "text",   "role": "title", "required": true },
      { "name": "slug",  "type": "slug",   "role": "slug",  "required": true },
      { "name": "body",  "type": "blocks", "role": "body" }
    ]
  }'

# 2) Create a draft
curl -X POST "$API/v1/documents" \
  -H "authorization: Bearer $TOKEN" \
  -H "x-corpusctl-tenant: $TENANT" \
  -d '{ "type": "post", "data": { "title": "Hello", "slug": "hello" } }'

# 3) Publish — validation, build and push to the edge in one call
curl -X POST "$API/v1/documents/$ID/publish" \
  -H "authorization: Bearer $TOKEN" \
  -H "x-corpusctl-tenant: $TENANT"

# → { "data": { "version": 3, "contentHash": "…", "durationMs": 42 } }

The read path never touches the database

  • Content-addressed cache. The address is the hash of the content. Since it can't change, it can be stored indefinitely — "stale data" stops being a problem.
  • Growing traffic doesn't grow the cost. Because read requests never reach the API, there's no call to meter; the bill doesn't climb along with your visitors.
  • Writing and reading live in separate classes. ReadClient carries no secret token and can run in the browser; ManagementClient is server-only. Were it a single class, leaking a token would be a one-line mistake.

Architecture

A separate write path, a separate read path

The publish button triggers a build; the visitor reads that build's output, not the system itself.

WRITE PATHREAD PATH (FAST)Panel · ManagementClientAPI — NestJSPostgreSQLRLS · partitionpublishBuild pipelineFrontend · ReadClientnginx edge cachehit → response in mson missorigin — nginxObject storage — S3-compatible<tenant>/c/<hash>.json · immutablewriteread
The two paths meet only at the object storage. Because visitor traffic never reaches the API or PostgreSQL, load on the write side is entirely independent of the read side.

Immutable address

Content is addressed by its hash. The same address never returns different content, so it can be cached for 30 days; the manifest is refreshed at publish time.

The manifest is sharded

Which manifest shard to read is computed from the slug on the client. Even in a space with ten thousand entries, only one small file is downloaded.

Versioning and scheduling

Every record is versioned and an older version can be restored into the draft. Future-dated publishing can be scheduled; the scheduler triggers the build itself.

Comparison

Where we lead, and where we don't

We don't win every row. This table exists so you can see exactly what you're choosing.

corpusctl compared with Contentful and DatoCMS — based on publicly available product information as of August 2026.
CriterioncorpusctlContentfulDatoCMS
Pricing modelFlat plan. Since read requests never reach the API, there's no call to meter.Metered per API call, record and user; overages cost extra.Metered per API call and per bandwidth.
Multi-project managementBuilt into the core. One panel, unlimited spaces; a user carries a separate role in each.Each project is its own "space"; most plans charge per space.Each project is its own environment; a plan per project.
Tenant isolationAt the database level — PostgreSQL row-level security (RLS).At the application level; details undisclosed.At the application level; details undisclosed.
Data locationServers we run; fully self-hosted on your own servers with the Enterprise plan.The vendor's cloud; region selection on higher plans.The vendor's cloud; region selection on higher plans.
Metered third-party servicesNone. PostgreSQL, Valkey, Typesense, Garage, nginx — all on our own infrastructure.Yes; search, image and CDN layers are metered.Yes; image CDN and transformations are metered.
Imposed renderingNone. Block renderers ship with zero default styling; an unknown block is skipped, never fatal.A rich text renderer is provided; styling decisions are yours.A structured text renderer is provided; styling decisions are yours.
Turning features offEvery feature is a disableable module; disabling deletes no data, and neither does a plan downgrade.Plan limits make the feature inaccessible.Plan limits make the feature inaccessible.
Global edge networkSingle-region edge cache. We have no global PoP network.Multi-region global CDN.Multi-region global CDN.
App marketplaceNone. Integrations are written with webhooks and the API.A broad, mature app ecosystem.A plugin ecosystem is available.
Enterprise maturityA young product; SSO and a formal SLA are on the roadmap.SOC 2, SSO, SLA — mature.SSO and SLA on higher plans.
If you serve a global audience at millisecond latency and need a ready-made integration marketplace, the honest answer is that Contentful is more mature today. But for teams serving Türkiye and Europe, running several projects and unwilling to let visitor counts move their bill, corpusctl is a deliberately better trade.

FAQ

Frequently asked questions

If you didn't find what you were looking for, write to [email protected].

Open your first space today

Creating an account takes a few minutes: an email address, a password and a name for your space. We don't ask for a card. If you need help migrating, write to us.