Open source · MIT · copy-in, you own the code

Your client edits
the live site.
You get a pull request.

CMSBar is a Git-as-CMS bar you drop into your own codebase — five React hosts (Next.js, React Router 7, TanStack Start, a Vite SPA, Astro) plus native SvelteKit and Nuxt UIs on the same protocol. Editors click anything on the page and change it in place — every save becomes a branch and a PR in your repo. No database, no dashboard to host, no vendor.

Try it live →

Extracted from production. ~50 files copied into your repo — delete it any time and your content is still just JSON in Git.

bakery.example.com logged in as editor

Open daily from 7am

Sourdough worth waking up for

Small-batch loaves, baked in Osijek every morning.

Order for pickup
Live site editor
click New draft, then click the headline and type

↑ A pixel-for-pixel preview of the real workflow: one draft = one branch = one PR = one version. Open the live playground to try it for real.

Open the live playground →

In 22 seconds

The whole idea, start to merge.

No database· No hosted dashboard· No vendor lock-in· Review-first by default· Stateless containers· MIT licensed· You own the code

How it works

Your repo is the CMS.

01

Click

Editors log in on the live site. Every wrapped element — <T path="home.hero.headline"/> — gets an editing ring. Click it, type. Images, video, rich text, lists: same gesture.

02

Save

All changes batch into one commit on a cms/* branch via the GitHub Git Data API. Unsaved work survives reloads — localStorage for text, IndexedDB for uploads.

03

Review

Each draft is an open PR titled [CMS draft] …. Preview any branch live, fork it, or lock it with an approved label. It's just Git — your reviews, your CI, your history.

04

Merge = deploy

Merging redeploys with the new content baked into the build. The production container never writes to disk — restart it, lose nothing.

Works with your framework

One core. Seven hosts.

A framework-neutral core (~64% of the code, untouched per host) plus a thin per-host seam: one client host (pathname, navigate, image, apiBase — with DOM defaults) and one server mount. The whole API is a single dispatcher — handleCmsRequest / createCmsApi — mounted as one catch-all route. The seam is frozen and validated across all five React hosts; a 156-test handler-level harness backs it. Two non-React UIs — SvelteKit (Svelte 5) and Nuxt (Vue 3) — reuse that same core + handlers, with the component layer rebuilt natively.

Each shipped host has a live example in the repo that builds and runs: examples/.

FrameworkStatusWhat the adapter mounts
Next.jsApp Router shipped Full copy-in: route handlers, login page, RSC layout wiring. examples/next
React Router 7framework mode shipped One resource route → handleCmsRequest; root-loader session; nav hooks. examples/react-router
TanStack Start shipped One server route → handleCmsRequest; root-loader session; head() meta. Needs a modern Node (≥ 20.19 / 22.12). examples/tanstack-start
Vite + React SPA+ Hono companion shipped DOM-default client host + a small companion server (createCmsApi); boot-time session fetch. No SSR — SEO/teaser limited (see docs). examples/vite-spa
AstroReact islands shipped One endpoint → createCmsApi; editor-gated hydration — public visitors get static zero-JS HTML; the page hydrates only when an editor cookie is present. examples/astro
Next.js Pages Router community A small (req,res) shim over the neutral handlers. Seam published; community-adapter tier.
SvelteKitnative Svelte 5 UI shipped The neutral core + handlers stay; the whole component layer is rebuilt in Svelte 5. One catch-all +server.tscreateCmsApi; SSR. Feature parity, browser-verified. examples/sveltekit
Nuxtnative Vue 3 UI shipped Same protocol, component layer rebuilt in Vue 3. One Nitro catch-all route → createCmsApi; SSR. Feature parity, browser-verified. examples/nuxt

Fresh site

$ npx cmsbar new my-site --framework astro

  • Scaffolds a per-host starter + assembles the neutral core into it
  • --framework next | react-router | tanstack-start | vite | astro | sveltekit | nuxt

Existing project

$ npx cmsbar init --framework react-router

  • Next: full copy-in. Others: core + adapter glue + printed wiring steps
  • You own the code afterward — nothing depends on the CLI

Per-framework quickstarts and the host seam live in the docs; the non-React contract is in docs/PROTOCOL.md, and every host has a runnable example.

The mental model

Editors see drafts.
You see Git.

The editor seesUnder the hood
“New draft”branch cms/<slug> + PR [CMS draft] <title>
“Save”one commit, all changes batched
“Versions”open PRs where head ∈ cms/*
“Preview”render site with that branch's JSON
“Approved · locked”reviewer added the approval label
“Fork”new branch off any version's HEAD

Publishing

Review-first by default.
Direct when you want speed.

Every save opens a pull request — that's the default, and for client work it's the point. But not every site needs review. One config key makes Save commit straight to the base branch instead: no draft, no PR, deploy on save.

publishing: { mode: "direct" } // default: "review"

mode: "review" — default

  • Drafts, previews, forks, the approval label
  • Every change is a PR someone can read
  • For client sites and anything with stakes

mode: "direct"

  • Save = one commit on the base branch = deploy
  • Same editor, same primitives, history still git log
  • For your own site, prototypes, trusted editors

One line in cms.config.ts. Flip it back the day the stakes change.

What's in the box

Everything the editor needs.
Nothing you have to host.

TEditable primitives

Inline text with a rich-text toolbar, images, video, icon lists. Paste a YouTube, Instagram, or Maps link — it becomes the right embed. One path prop each.

Rich text that shows up

Toolbar output is styled out of the box — headings, lists, and editable links via .cmsbar-prose, declared in @layer base so your site's CSS always wins.

Images that crop right

Drag the focal point on any cropping image — media slots included. The library browses your repo's folders: create, upload, delete, all staged into the same save.

Guided tour, built in

Define onboarding steps in cms.config.ts; <CmsTour/> spotlights each one. Auto-starts once per editor, re-runs from the ✦ Guide pill in the bar.

Drafts, previews, forks

Multiple drafts in flight, live preview of any branch, fork from any version, approval label locks a draft read-only.

Stateless by design

All writes go through the GitHub API with a fine-scoped PAT. The container is disposable; unsaved edits live in the editor's browser.

{}One typed config

cms.config.ts owns namespace, content file, media folders, branch prefix, pages, shared-content rules. No magic strings.

Theme it to your brand

Every color in the editor chrome is a --cmsbar-* CSS variable. Re-brand the whole experience from one stylesheet.

🔒Boring, auditable auth

bcrypt + HMAC session cookie, rate-limited login, shared-element warnings, per-page SEO drawer, optional pre-launch teaser gate.

Install

One command. Your code now.

shadcn-style: the CLI copies the source into your project and gets out of the way. There is no runtime dependency on CMSBar — fork it, theme it, delete it. Starting fresh? npx cmsbar new my-site --framework <fw> scaffolds a host and assembles the core. Below: adding it to an existing Next.js app.

~/my-next-app
$ npx cmsbar init --namespace bakery
 55 files copied (components/cmsbar, lib/cmsbar, app/api/cms, …)
 cms.config.ts written — namespace "bakery"
 env keys appended to .env.example

Next: wrap your layout, import the theme, add a PAT.
Then make anything editable:

  <T path="home.hero.headline" as="h1" />

What you need

  • A supported host — the five React hosts (Next, React Router 7, TanStack Start, Vite SPA, Astro) + Tailwind v4, or SvelteKit / Nuxt with the native UI (plain CSS, no Tailwind)
  • A GitHub repo + fine-scoped PAT (Contents + PRs)
  • ~5 minutes

What you don't

  • A database
  • A CMS subscription
  • Teaching your client Git (they never see it)

Cloud Studio

Twenty client sites.
One approval queue.

The hosted control plane for agencies running CMSBar across many client repos. In development — a demo seeded with sample projects already runs. The OSS core never depends on it.

What it runs for you (v0.2)

  • Approval dashboard across every connected repo — all open cms/* drafts, who's editing, one-click Approve / Unapprove / Merge
  • Per-project chips: live install status + publishing mode (parsed from cms.config.ts)
  • Token custody — the PAT is validated, then stored AES-256-GCM encrypted; it never reaches the browser (the site still keeps its own token for now)
  • Admin sign-in via magic links

How you onboard

  • The /setup wizard opens a cmsbar/install PR (cms.config.ts, .env.example, INSTALL-CMSBAR.md) with a byte-for-byte preview
  • Demo mode — two seeded projects (no GitHub calls, no real PAT) to walk the dashboard + wizard end-to-end
  • Disconnect any time; your sites keep working

Pricing

The CMS is free.
The convenience is the product.

Self-host everything, forever, free. The hosted control plane — for agencies who'd rather not run token custody, client logins, and previews themselves — is in development.

Open source

$0 MIT, forever

  • The full editor + bar
  • GitHub adapter, PR workflow
  • Self-hosted auth & approvals
  • Community support
npx cmsbar init

White-label

$$ for agencies

  • Your brand on the bar, not ours
  • Wholesale per-seat pricing
  • Client never sees “CMSBar”
  • Priority support
Talk to us

FAQ

Fair questions.

Where does the content actually live?

In content/site-content.json in your repo, imported at build time. Drafts are branches; merging redeploys. Your content's full history is git log.

What happens if I uninstall CMSBar?

Your site keeps rendering the same JSON — the primitives render bundled values when no editor is logged in. Delete the components and inline the strings whenever you like. There's nothing to migrate off.

Does my client need a GitHub account?

No. Editors log in with a username/password on your site; the server holds one fine-scoped PAT. (Hosted client identity with magic links is what Cloud Studio adds.)

What about images and video?

Uploads stage in the browser (IndexedDB), then commit as blobs in the same PR as the text changes. Folder allowlists are configured in cms.config.ts.

Which frameworks?

Five React-family hosts ship today, each with a live example that builds and runs: Next.js (App Router), React Router 7, TanStack Start, a Vite + React SPA (with a small Hono companion server), and Astro (React islands). Beyond React, SvelteKit (Svelte 5) and Nuxt (Vue 3) now ship as native UIs on the same protocol — full feature parity, browser-verified — so all seven scaffold with npx cmsbar new --framework <fw>. See the frameworks matrix and docs/PROTOCOL.md (the non-React contract). A pure SPA still needs a tiny server for the write token — that's the companion server, not a browser-held token.

Is this production-ready?

CMSBar was extracted from a production site where it ships real content edits as PRs daily. The standalone package is young — pin a commit, read the code (it's yours), and file issues.

Stop hosting a CMS.
Start merging content.

copied to clipboard