# Show HN: Plain – a Git-native CMS in vanilla JavaScript, no database

> Source: <https://github.com/plain-cms/plain>
> Published: 2026-07-09 11:22:17+00:00

**A Git-native CMS for the AI age.** The repository is the database, static files are the API, and AI is the admin.

Your whole website is a folder of plain files: Markdown for content, JSON for settings. Git gives you versioning, collaboration, and hosting hooks for free. The build turns it into a fast static site — HTML pages plus a read-only JSON API — that deploys anywhere for $0/month.

**No database, no server, nothing to patch.** All state lives in this repo.**Vanilla by design.** No frameworks, no bundlers. One dependency:. The entire engine is a few small, readable files.`marked`

**Works without JavaScript.** JS is progressive enhancement only.**AI-operable.** Deterministic layout, machine-readable content model, and aso agents (or Claude Code) can edit content, add collections, and write plugins safely.`CLAUDE.md`

**Get a copy:** click**Use this template**(or fork) to create your own repo.** Enable hosting:**in your repo, go to** Settings → Pages**and set** Source**to** GitHub Actions**.** Make it yours:**edit`site.config.json`

— set your`title`

,`description`

, and`url`

— and commit.- Push (or edit on github.com and commit). About 30 seconds later, your site is live.

Every later change is the same loop: edit → commit → live in ~30s. Nothing is ever lost; any version of any page can be restored from Git history.

Your live site includes an editor at ** /admin/** — a clean writing screen with Save draft / Publish buttons, live preview, image uploads, and per-page History with one-click restore. No Git knowledge needed.

Sign in once with a GitHub access token (it stays on that device):

- On GitHub:
**Settings → Developer settings → Fine-grained tokens → Generate new token**. - Repository access:
**Only select repositories**→ pick your site's repo. - Permissions → Repository permissions:
**Contents: Read and write**,** Actions: Read-only**. - Generate, copy, and paste it into the admin's sign-in screen.

The token never leaves the browser except to api.github.com. Editors who prefer files can keep editing files — the admin and direct edits coexist happily.

Prefer a button to a paste? Deploy the optional ~60-line [OAuth Worker](/plain-cms/plain/blob/main/workers/oauth/README.md) (free on Cloudflare, backed by a free GitHub App) and the sign-in screen gains **"Sign in with GitHub"** — one click, and each writer gets a token scoped to just that repo's contents.

**AI assist (optional, BYOK):** paste an Anthropic API key in Settings and the editor grows ✨ buttons — improve writing, suggest titles, generate the meta description, write image alt text, translate a page into a new draft. Every suggestion shows a before/after and asks before applying. The key stays in your browser and is sent only to Anthropic.

Every build also publishes a read-only JSON API: `/api/site.json`

, `/api/posts/index.json`

, `/api/posts/<slug>.json`

— plain static files any script, app, or AI agent can consume. No keys, no rate limits, cached by the CDN.

A post is a Markdown file in `content/posts/`

. The filename is the URL: `hello-world.md`

→ `/blog/hello-world/`

.

```
---
title: Hello world
date: 2026-07-05
description: One sentence for search engines and link previews.
tags:
  - launch
draft: false
---

Body in **Markdown**. Images by path: ![A lake](/media/lake.jpg)
```

Set `draft: true`

and the post is saved but not published. Pages work the same in `content/pages/`

(`about.md`

→ `/about/`

; `index.md`

is the homepage). Menus live in `data/navigation.json`

; renamed URLs get an entry in `data/redirects.json`

.

First sign in and the admin greets you with a five-step wizard: pick what you're building, name it, see it (a live preview already wearing your name), write your first words, and go live. Under the hood it applies a **starter** — a theme plus the right content types, menu, and example content.

Fifteen starters ship in the box: **Journal** (blog), **Toolbox** (trades & local services), **Studio** (portfolio), **Bistro** (restaurant), **Manual** (documentation), **Terminal** (developer blog), **Letters** (newsletter & essays), **Launch** (startup/SaaS), **Gazette** (local news & magazine), **Folio** (résumé/CV), **Keys** (real estate), **Cause** (nonprofit), **Practice** (clinic), **Form** (fitness studio), and **Encore** (band & artist). The Appearance screen lets you try any of them on *your own pages* — with device widths and a light/dark toggle — before committing, and a customizer exposes each theme's colors and fonts as live controls. Your tweaks survive theme updates.

A plugin is a folder — install one by copying it into `plugins/`

and adding its name to `"plugins"`

in `site.config.json`

. Ships with **search** (enabled: a `/search/`

page over a prebuilt index, no services involved) and **contact-form** (disabled reference: write `[[contact-form]]`

in any page, point it at a Formspree-style endpoint). The full hook API is documented in [ CLAUDE.md](/plain-cms/plain/blob/main/CLAUDE.md) — it's small enough that "write me a plugin that adds reading time" is a one-prompt job for an AI agent. Also included:

**reading-time**(enabled) — written by an AI agent from the docs alone, in one prompt, as proof of that claim — plus

**api-form**(forms declared in config, POSTing to your own backend via the

`"services"`

map) and **goatcounter**(opt-in page-view counts). Good first plugins: giscus comments, image gallery, table of contents.

Already have a site? The importers under `tools/migrate/`

convert it — content, media, and a complete old→new redirect map so your URLs (and your SEO) survive the move. Run one on your machine:

```
node tools/migrate/jekyll.js /path/to/your-jekyll-site
```

It writes `content/`

, `media/`

, and `data/redirects.json`

into `./plain-import/`

(never touching your working tree) plus a migration report of anything that needs a human eye. Copy those folders into your plain repo and build. Jekyll ships today; Hugo, Eleventy, and WordPress are on the roadmap (§15).

The full step-by-step walkthrough — keeping your URLs, wiring forms and analytics, the cutover checklist — is [ tools/migrate/README.md](/plain-cms/plain/blob/main/tools/migrate/README.md).

plain follows [semver](https://semver.org). Engine files (`build.js`

, `lib/`

, `admin/`

, `themes/default/`

) are upstream-owned; your content, config, and custom themes are yours. When a new version ships, the admin shows an **Update available** banner: click it and a pull request appears with the changelog and any files you'd customized flagged for review. **Merge to upgrade, revert to roll back** — never a surprise. An optional weekly workflow opens that PR on its own, so even an unattended site keeps getting security fixes as reviewable PRs. See §14 for the mechanism.

```
npm install
node build.js            # build into dist/
node build.js --watch    # serve on http://localhost:4000, rebuild on change
node --test tests/       # run the test suite
node tools/engine-manifest.js   # regenerate engine.json before a release
site.config.json   all configuration: site info, collections, plugins
config.defaults.json  engine-owned defaults, merged under your config
content/           your words (Markdown, one file per page/post)
data/              navigation, redirects (JSON)
media/             images and files
themes/            fifteen starters ship in the box; add your own
plugins/           a plugin is a folder; install = copy + enable in config
admin/             the browser editor (static, vanilla ES modules)
tools/migrate/     importers (Jekyll today)
workers/oauth/     optional "Sign in with GitHub" worker (v1 uses a token)
build.js + lib/    the whole engine — under 2,500 lines, one dependency, MIT
```

The full product specification lives in [ cms-spec.md](/plain-cms/plain/blob/main/cms-spec.md); instructions for AI agents (and how to add collections, plugins, themes) in

[. To contribute, read](/plain-cms/plain/blob/main/CLAUDE.md)

`CLAUDE.md`

[.](/plain-cms/plain/blob/main/CONTRIBUTING.md)

`CONTRIBUTING.md`

[MIT](/plain-cms/plain/blob/main/LICENSE) — open source from day one. Themes, plugins, and importers are the adoption engine; the core will never be closed.
