@laikacms/decap-cms
v4.2.0
Published
Single-package fork of Decap CMS v4.beta. Same components as the original monorepo, exposed through one package with subpath exports. Root export is the decap-cms-app bootstrap. See restructure.md.
Maintainers
Readme
@laikacms/decap-cms
A single-package fork of Decap CMS, the open-source, Git-based CMS for
static site generators. It presents a clean UI for editing content stored in a Git repository: you
describe your content model in a YAML config, drop the CMS into the /admin part of your site, and
editors work against your repo through their browser.
This fork exists to keep that idea moving. All credit for the concept, the architecture, and a decade of groundwork goes to the Decap CMS team; see Credits below.
What is different from upstream
- One package instead of a monorepo. The former
decap-cms-*packages live in a single@laikacms/decap-cmspackage. Each former package is exposed as a subpath export (seepackage.json#exports); the root export is the classic app bootstrap. Where possible, dependencies are declared as optional peer dependencies so you only install what your build actually uses, keeping install size down. - The Laika UI. Alongside the classic Decap app shell there is a new shell with a dashboard, command palette, and mobile support.
- A modernized stack. Base UI primitives for interactive behavior, Emotion for styling, Vitest and Playwright for testing, plain objects instead of Immutable.js, and an ongoing dependency-reduction effort.
- Richtext on Portable Text. The
markdownwidget is replaced by arichtextwidget backed by the Portable Text editor. See breaking-changes-v4-beta.md for the full list of breaking changes. - AI chat (deprecated). A document-scoped
ai-chatwidget streams assistant replies and can apply proposed edits back onto the current entry's draft fields; see src/widgets/aichat/README.md for widget setup. This widget is deprecated in favor of the laikacms MCP server (/mcp) and is being phased out — see the widget's own README for details on the replacement and migration path. The server side is powered bydecapAi()from the@laikacms/decap-cms/aisubpath export, which bundles the Vercel AI SDK (model provider factories,tool/jsonSchemare-exports) so consumers share oneairuntime instead of installing it themselves; see src/ai/index.ts for usage.
Installation
npm install @laikacms/decap-cmsThe root export bootstraps the classic app. Individual parts (backends, widgets, the core engine, UI primitives) are importable through subpath exports so you can assemble your own build.
For configuration, content modeling, and backend setup, the upstream Decap CMS documentation applies to this fork unless noted in breaking-changes-v4-beta.md.
If you use the laika backend, read src/backends/laika/README.md
first — it diverges from the upstream backend docs in three ways that aren't obvious from the
standard config reference:
- It requires the
laika-appentry point, not the root import above. The root export (@laikacms/decap-cms, what plainnpm install @laikacms/decap-cmsgives you) never callsCMS.registerBackend('laika', …), so settingbackend: { name: laika }against it fails silently at runtime (no registered backend, no editor). Import@laikacms/decap-cms/laika-appinstead (or@laikacms/decap-cms/laika-app/bareto register only the pieces you use) — see src/backends/laika/README.md#usage for the exact import. - Only
format: jsoncollections are supported. This setting is only needed for backwards compatibility with Decap's format handling; Laika parses any format itself. Decap's default (markdown-frontmatter) is not yet supported; omittingformat:now fails fast client-side with an actionable error before any request reaches the server. - Entry locking is not yet implemented. The advisory "Being edited by X" locking that Decap core
supports (
getEntryLock/acquireEntryLock/releaseEntryLock/refreshEntryLock) has no effect on this backend yet.
CDN builds (no bundler)
Both full app shells ship as prebuilt, self-contained browser bundles under dist/cdn/, so unpkg
and jsdelivr serve them straight off npm. Nothing else is needed: React, the backends, the widgets
and the styles are all inlined, and the bundle registers everything and calls init() on load.
<!-- classic Decap shell -->
<script src="https://cdn.jsdelivr.net/npm/@laikacms/decap-cms@4/dist/cdn/decap-cms.js"></script>
<!-- or the Laika shell (this is the one with the `laika` backend registered) -->
<script src="https://cdn.jsdelivr.net/npm/@laikacms/decap-cms@4/dist/cdn/laika-cms.js"></script>The classic bundle exposes a DecapCms global (and the usual window.CMS / window.h); the Laika
bundle exposes LaikaCms. Set window.CMS_MANUAL_INIT = true before the script tag if you want to
register your own widgets or preview templates before the app boots, then call window.initCMS().
An ES module build sits next to each one for <script type="module">:
<script type="module">
import { init, CMS } from 'https://cdn.jsdelivr.net/npm/@laikacms/decap-cms@4/dist/cdn/decap-cms.esm.js';
</script>| URL path | Entry point | IIFE global |
| --------------------------- | ------------------------------- | ----------- |
| dist/cdn/decap-cms.js | @laikacms/decap-cms/app | DecapCms |
| dist/cdn/decap-cms.esm.js | @laikacms/decap-cms/app | - |
| dist/cdn/laika-cms.js | @laikacms/decap-cms/laika-app | LaikaCms |
| dist/cdn/laika-cms.esm.js | @laikacms/decap-cms/laika-app | - |
Pin a version (@4.1.0) rather than a range for production. These bundles are ~5.5 MB raw and ~1.8
MB gzipped, because a script tag can't tree-shake: every backend, widget, locale and the whole
richtext editor is included. If that matters, install the package and build against the bare
entries (/app/bare, /laika-app/bare) instead, registering only what you use. The bare entries
deliberately have no CDN build, since a prebuilt file can't be shaken down.
Build them locally with pnpm build:cdn; prepack runs it so every published version has them. Add
CDN_SOURCEMAP=1 for a debuggable build (the sourcemaps are ~20 MB each, so they are not
published).
JSON Schema (editor autocompletion)
The package ships a JSON Schema for config.yml at
@laikacms/decap-cms/schema/config.schema.json, so editors with
yaml-language-server support (VS Code's
YAML extension, most
JetBrains IDEs, etc.) can offer autocompletion and inline validation while you write config.yml.
Point at it with the $schema comment convention at the top of the file:
# yaml-language-server: $schema=./node_modules/@laikacms/decap-cms/schema/config.schema.json
backend:
name: git-gateway
collections:
- name: posts
label: Posts
folder: _posts
fields:
- { label: Title, name: title, widget: string }A CDN URL works too, if you'd rather not depend on the path to node_modules:
# yaml-language-server: $schema=https://unpkg.com/@laikacms/decap-cms/schema/config.schema.jsonThis schema is hand-maintained to mirror the structural shape of the runtime validator
(src/core/lib/validateConfig.ts's getConfigSchema()) — the runtime validator is still the source
of truth and the only thing that actually blocks the app from booting with a bad config. The schema
intentionally leaves field objects open (additionalProperties unset) since valid keys on a field
depend on its widget, which is only known once widgets are registered at runtime; it isn't (yet)
published to SchemaStore, so the explicit $schema comment above is
required rather than automatic filename matching.
Visual Editing (Stega)
The editor's live preview pane can steganographically encode field values (via @vercel/stega) so a
frontend can detect which on-page text maps back to which CMS field, the same technique used by
tools like Vercel's Visual Editing. This only changes what is rendered in the preview iframe; the
entry data saved to your repository is never touched.
Visual editing is opt-in at the collection level, with a field-level opt-out available only for
string/text widgets:
editor.visualEditing(collection-levelboolean, defaultfalse) enables steganographic encoding of the preview entry for that collection. When left unset orfalse, the preview pane renders the entry unmodified and no encoding happens.visualEditing(field-levelboolean, effectivelytrueonce the collection has opted in). Set it tofalseon an individualstringortextfield to exclude just that field's value from encoding. This opt-out is only checked forstring/textfields, since those are the only widgets ever encoded in the first place.
collections:
- name: posts
label: Posts
editor:
visualEditing: true
fields:
- { label: Title, name: title, widget: string }
- { label: Body, name: body, widget: richtext }
- { label: Internal Note, name: note, widget: string, visualEditing: false }Only string and text widgets are encoded. richtext fields (including their legacy markdown
alias) are deliberately excluded and never encoded, regardless of the collection- or field-level
settings above: their raw value is markdown source that still has to pass through the markdown ->
Portable Text -> preview-HTML pipeline, and appending a stega block per paragraph would survive that
pipeline as literal zero-width characters sitting inside the rendered preview's prose text nodes —
poisoning copy-paste out of the preview and diverging from what a reader actually sees. All other
widget types are likewise left untouched by the encoder.
Development
pnpm install # Node >= 24, pnpm 9
pnpm test:ci # lint + typecheck + unit tests
pnpm build:dev-test && pnpm serve:dev-test # demo app on http://localhost:5174, Laika UI on /laika.htmlSee CONTRIBUTING.md for the full workflow.
Credits
This project is a fork of Decap CMS, created as Netlify CMS by Netlify and renamed in February 2023. Decap CMS is maintained with care by PM TechHub and friends; if you want to support the original project, visit decapcms.org.
Everything here builds on their work, and the Decap maintainers are welcome to adopt any part of this fork upstream.
Change log
This project adheres to Semantic Versioning. Every release is documented on the GitHub Releases page.
License
Released under the MIT License, retaining the original Netlify copyright.
