{"slug": "show-hn-plain-a-git-native-cms-in-vanilla-javascript-no-database", "title": "Show HN: Plain – a Git-native CMS in vanilla JavaScript, no database", "summary": "Plain, a Git-native CMS built with vanilla JavaScript and no database, launched on Hacker News. The system stores content as plain files in a Git repository, uses static site generation for deployment, and includes an optional AI assistant for content editing. It aims to simplify web publishing by eliminating servers and databases while leveraging Git for version control and collaboration.", "body_md": "**A Git-native CMS for the AI age.** The repository is the database, static files are the API, and AI is the admin.\n\nYour 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.\n\n**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`\n\n**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`\n\n**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`\n\n— set your`title`\n\n,`description`\n\n, and`url`\n\n— and commit.- Push (or edit on github.com and commit). About 30 seconds later, your site is live.\n\nEvery 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.\n\nYour 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.\n\nSign in once with a GitHub access token (it stays on that device):\n\n- On GitHub:\n**Settings → Developer settings → Fine-grained tokens → Generate new token**. - Repository access:\n**Only select repositories**→ pick your site's repo. - Permissions → Repository permissions:\n**Contents: Read and write**,** Actions: Read-only**. - Generate, copy, and paste it into the admin's sign-in screen.\n\nThe 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.\n\nPrefer 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.\n\n**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.\n\nEvery build also publishes a read-only JSON API: `/api/site.json`\n\n, `/api/posts/index.json`\n\n, `/api/posts/<slug>.json`\n\n— plain static files any script, app, or AI agent can consume. No keys, no rate limits, cached by the CDN.\n\nA post is a Markdown file in `content/posts/`\n\n. The filename is the URL: `hello-world.md`\n\n→ `/blog/hello-world/`\n\n.\n\n```\n---\ntitle: Hello world\ndate: 2026-07-05\ndescription: One sentence for search engines and link previews.\ntags:\n  - launch\ndraft: false\n---\n\nBody in **Markdown**. Images by path: ![A lake](/media/lake.jpg)\n```\n\nSet `draft: true`\n\nand the post is saved but not published. Pages work the same in `content/pages/`\n\n(`about.md`\n\n→ `/about/`\n\n; `index.md`\n\nis the homepage). Menus live in `data/navigation.json`\n\n; renamed URLs get an entry in `data/redirects.json`\n\n.\n\nFirst 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.\n\nFifteen 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.\n\nA plugin is a folder — install one by copying it into `plugins/`\n\nand adding its name to `\"plugins\"`\n\nin `site.config.json`\n\n. Ships with **search** (enabled: a `/search/`\n\npage over a prebuilt index, no services involved) and **contact-form** (disabled reference: write `[[contact-form]]`\n\nin 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:\n\n**reading-time**(enabled) — written by an AI agent from the docs alone, in one prompt, as proof of that claim — plus\n\n**api-form**(forms declared in config, POSTing to your own backend via the\n\n`\"services\"`\n\nmap) and **goatcounter**(opt-in page-view counts). Good first plugins: giscus comments, image gallery, table of contents.\n\nAlready have a site? The importers under `tools/migrate/`\n\nconvert it — content, media, and a complete old→new redirect map so your URLs (and your SEO) survive the move. Run one on your machine:\n\n```\nnode tools/migrate/jekyll.js /path/to/your-jekyll-site\n```\n\nIt writes `content/`\n\n, `media/`\n\n, and `data/redirects.json`\n\ninto `./plain-import/`\n\n(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).\n\nThe 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).\n\nplain follows [semver](https://semver.org). Engine files (`build.js`\n\n, `lib/`\n\n, `admin/`\n\n, `themes/default/`\n\n) 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.\n\n```\nnpm install\nnode build.js            # build into dist/\nnode build.js --watch    # serve on http://localhost:4000, rebuild on change\nnode --test tests/       # run the test suite\nnode tools/engine-manifest.js   # regenerate engine.json before a release\nsite.config.json   all configuration: site info, collections, plugins\nconfig.defaults.json  engine-owned defaults, merged under your config\ncontent/           your words (Markdown, one file per page/post)\ndata/              navigation, redirects (JSON)\nmedia/             images and files\nthemes/            fifteen starters ship in the box; add your own\nplugins/           a plugin is a folder; install = copy + enable in config\nadmin/             the browser editor (static, vanilla ES modules)\ntools/migrate/     importers (Jekyll today)\nworkers/oauth/     optional \"Sign in with GitHub\" worker (v1 uses a token)\nbuild.js + lib/    the whole engine — under 2,500 lines, one dependency, MIT\n```\n\nThe 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\n\n[. To contribute, read](/plain-cms/plain/blob/main/CLAUDE.md)\n\n`CLAUDE.md`\n\n[.](/plain-cms/plain/blob/main/CONTRIBUTING.md)\n\n`CONTRIBUTING.md`\n\n[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.", "url": "https://wpnews.pro/news/show-hn-plain-a-git-native-cms-in-vanilla-javascript-no-database", "canonical_source": "https://github.com/plain-cms/plain", "published_at": "2026-07-09 11:22:17+00:00", "updated_at": "2026-07-09 11:42:19.365050+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools"], "entities": ["Plain", "GitHub", "Anthropic", "Claude Code", "Cloudflare"], "alternates": {"html": "https://wpnews.pro/news/show-hn-plain-a-git-native-cms-in-vanilla-javascript-no-database", "markdown": "https://wpnews.pro/news/show-hn-plain-a-git-native-cms-in-vanilla-javascript-no-database.md", "text": "https://wpnews.pro/news/show-hn-plain-a-git-native-cms-in-vanilla-javascript-no-database.txt", "jsonld": "https://wpnews.pro/news/show-hn-plain-a-git-native-cms-in-vanilla-javascript-no-database.jsonld"}}