Skip to content

Open-source headless CMS

Model → API.
No restart.

Shapio is a self-hosted headless CMS whose content models change in production with no rebuild or restart, and whose schema lives in git as well as in the admin. Schema and content ship together in reviewed change sets, and editors get tools that are finished.

Models change live, schema and content ship together as snapshots, and agents propose while people ship.

See how it works
  1. Model

    page · schema v14 · active

  2. REST · GraphQL

    served from the model

  3. Editors

    rich text · media · relations

  4. Ship

    change set → snapshot 214

  5. Webhooks

    signed · retried

  6. Build

    triggered → building → deployed

  7. Your site

    deployed ✓

Live modelling

Add a field in production. It's live. That's the whole feature.

Models are versioned rows in your database, not files in your repo. Adding a field, renaming a label or switching an editor activates a new schema version in one transaction, at once with Ship now or together with a change set. No dev mode, no rebuild, no PM2 restart, no downtime.

  • Required fields are validated or backfilled before they go live
  • Stable IDs: renaming a label never touches data
  • Two admins editing the same model: one wins, the other sees the conflict
Admin · Models · Page
{
"title": "string",
"slug": "slug",
"subtitle": "text",
"hero": "component"
}
GET /api/content/pageslive
{
"title": "Homepage",
"slug": "/",
"subtitle": "Small adventures.",
"hero": { … }
}

Schema in git · Change sets

Ship like code: pull, review, ship, restore.

Models export to JSON files with stable IDs and apply live from CI: changes made in the admin since your pull are kept, and a model changed on both sides is refused with both shown, like a rejected push. In the admin, a change set groups schema edits and entry drafts, is reviewed like a pull request, and ships as one numbered snapshot, so a site never sees a new field without the entries that use it. Restoring an older snapshot opens a new change set: nothing is deleted, and the schema is not rolled back.

Schema as code
shapio schema pull
Pulled 6 definition(s) at schema version 14 into schema
shapio schema apply --url https://cms.example.com --token shp_…
SCHEMA_SYNC_CONFLICT: the target changed since your last pull
model page: conflict: changed locally AND on the target
~ label: "Page" → "Landing page"

Every snapshot stays readable, and ?snapshot=N pins a build to one moment.

Editors that ship finished

Rich text, media, relations and components. In the box, done properly.

A field's data type is separate from how it's edited, so you can swap a toggle for a segmented control without touching data. Every control works with a keyboard, shows its errors inline and protects unsaved edits. Custom React editors get the same server validation as everything else.

  • Rich text with tables
  • Repeatable components
  • Dynamic zones
  • Relation picker
  • Media library
  • Localized fields
  • Custom React editors

Visual editing and preview

The draft renders on your real site beside the document: click a heading or an image to focus its field, and every save re-renders the page. Sites mark their fields with @shapio/visual, the Astro, Next.js and SvelteKit starters come set up, and the admin only frames the preview sites you connected. Preview tokens last an hour and cover one entry and locale.

  • Astro
  • Next.js
  • SvelteKit
  • @shapio/visual
The Shapio admin editing the article "Modelling content without a deploy": a cover image, the title, a strip of properties (slug, excerpt, author, published on) and the rich text body, with the content types in the sidebar and the entry marked Published.

Assist and MCP

Agents propose, people ship.

Assist uses your own model provider, hosted or running on your machine, for alt text, summaries, translations, rewrites, content-type drafts and fixes for missing alt text and locales. It is off by default, and while it is off nothing leaves your server. Every result is a field you review or a draft in a change set.

With npx -y @shapio/mcp, an agent in any MCP client models content types, writes entries and opens a change set for review. It cannot ship unless you pass --allow-ship and its role holds changes.ship.

MCP · tool calls
  1. schema_draft menuItem → change set "Spring menu"
  2. content_create ×6 → 6 drafts
  3. change_sets_add_entry ×6 → "Spring menu"
  4. change_sets_review → 0 blocking · 1 warning
  5. change_sets_ship ✗ 403 FORBIDDEN
    Your role does not allow changes.ship

Also in the box

The things other CMSs put behind an enterprise plan.

REST and GraphQL serve every model from the active schema with the same filters, sorting, paging, population and field selection, and public delivery only ever sees published content.

REST
GET /api/content/pages?locale=fr&filters[slug][$eq]=/&fields=title,hero,featured&populate=featured
GraphQL
query {
pages(locale: "fr", filter: { slug: { eq: "/" } }) {
nodes { title hero { heading } featured { title } }
}
}
  • Many sites

    One schema and one login. Each site has its own content, media, tokens, snapshots, change sets and app users.

  • Roles and audit log

    Roles per model, action and field, on one site or all sites. An audit log of who changed what, agents included.

  • History and scheduling

    Every save is a revision you can restore. Ship a change set, or publish one entry, at a set time.

  • Localization

    Localized and shared fields, fallback chains, publishing per locale.

  • End-user accounts

    Sign-up, Google and GitHub sign-in, roles, owner-only writes. Your app's users, per site.

  • Deployments and webhooks

    Cloudflare Pages, Vercel, Netlify or a signed build hook, with real build status and retry. Webhooks are signed and retried.

  • Content health and usage

    An Inbox of images without alt text and missing locales, and which tokens read which fields before a breaking change ships.

  • Media

    Local disk or S3 and R2, private files with signed URLs, WebP variants.

  • Site starters

    Astro, Next.js or SvelteKit, from create-shapio: a blog in two languages, seeded live, with preview and visual editing.

  • Importers

    WordPress (WXR) and Strapi 5 exports become schema files to apply, then drafts. What was published lands in a change set.

  • Export and import

    Content and media bundles with a dry-run plan. Imports keep IDs and resume.

  • Tokens, types, extensions

    Scoped delivery tokens, OpenAPI and TypeScript types from the active schema, and hooks, routes and jobs loaded at runtime.

FAQ

Questions

  • Does changing a model need a rebuild?

    No. A model change activates a new schema version in the running server, and REST and GraphQL serve it on the next request; only environment variables, code extensions and upgrading Shapio itself need a restart.

  • Which databases does it run on?

    PostgreSQL 16 or later, or SQLite. Use PostgreSQL for several instances, a separate worker or many editors writing at once, and move from SQLite to PostgreSQL later with export and import.

  • Do I need the AI assist?

    No. Assist is off until you set AI_PROVIDER, and while it is off nothing leaves your server; when you turn it on, it uses your own model provider, hosted or running on your machine.

  • Can I run it free on one small server?

    Yes: every feature is in the Apache-2.0 core, and with SQLite the whole instance is one Node process and one database file. Keep that file on a persistent local disk, not a network file system, and back it up with shapio backup while Shapio runs.

  • How do I move from WordPress or Strapi?

    shapio import wordpress or shapio import strapi reads a WXR or Strapi 5 export and writes schema files you review and apply live. A second step uploads the media and creates every entry as a draft, with the ones published at the source in a change set you review and ship.

  • What is the licence?

    Apache-2.0, for the server, the admin and the packages. There is no hosted account and no telemetry: Shapio sends nothing anywhere unless you configure it.

Running in two minutes, your way.

One Node process and PostgreSQL or SQLite. Run it with npm and PM2, or with Docker. Both paths are first-class and get the same admin, API and live modelling.

  • npm + PM2
    npx create-shapio@latest my-cms --database-url sqlite:./shapio.db
    cd my-cms && pm2 start ecosystem.config.cjs
    ✓ http://localhost:4300 · open /admin/ to create the owner
  • Docker
    curl -O https://raw.githubusercontent.com/mybrokengnome/shapio/main/docker-compose.yml
    docker compose up -d
    ✓ shapio + postgres on :4300 · open /admin/ to create the owner