Back to Ghost

Koenig

koenig/README.md

6.58.07.1 KB
Original Source

Koenig

Koenig is Ghost's post editor. This directory contains the editor UI and its supporting packages, merged into the Ghost monorepo from the former TryGhost/Koenig repo with full git history.

Everything here resolves via workspace: specs — nothing in dev, CI, or the release archive installs these packages from npm. The npm-published versions exist for external consumers only (see Shipping).

Packages

Editor UI

PackageWhat it isConsumed by
koenig-lexicalThe Lexical-based rich text editor (React). Ships as a UMD bundle with styles and SVGs inlinedghost/admin (bundled into admin assets at build time), apps/admin, apps/admin-x-framework
kg-simplemdeCustomised fork of SimpleMDE, used by koenig-lexical's markdown cardkoenig-lexical
kg-unsplash-selectorReact Unsplash image pickerkoenig-lexical, apps/admin

Lexical node definitions & rendering

These are plain TypeScript Node libraries. kg-default-nodes is the single source of truth for node rendering — both the editor and the server render through it.

PackageWhat it isConsumed by
kg-default-nodesLexical node definitions for all of Ghost's cards, including each node's HTML renderer. Must stay browser-safe (it runs in the editor as well as on the server)ghost/core, koenig-lexical, most other kg-* packages
kg-default-transformsLexical node transforms (denesting, blockquote children, etc.) shared between editor and serverkoenig-lexical, kg-lexical-html-renderer, kg-html-to-lexical
kg-lexical-html-rendererRenders a serialized Lexical state to front-end/email HTML server-side (not editor DOM — output differs per target, e.g. <table> markup for email)ghost/core
kg-html-to-lexicalConverts HTML strings into Lexical editor state (imports, API ?source=html)ghost/core
kg-convertersConverts between serialized Mobiledoc and Lexical formatsghost/core, ghost/admin

Shared helpers

PackageWhat it isConsumed by
kg-clean-basic-htmlSanitises/normalises snippets of "basic HTML" (card captions etc.)ghost/core, ghost/admin, kg-default-nodes
kg-markdown-html-rendererMarkdown → HTML rendering for the markdown cardghost/core, kg-default-nodes, kg-default-cards
kg-utilsSmall shared utilities (slugify)kg-default-cards, kg-lexical-html-renderer, kg-markdown-html-renderer

Legacy (Mobiledoc era)

Still consumed by ghost/core to render posts that have never been converted from Mobiledoc. Avoid new work here.

PackageWhat it isConsumed by
kg-card-factoryCard definition factory for the Mobiledoc rendererghost/core
kg-default-cardsMobiledoc card definitionsghost/core

Development

There is no linking or per-package install step — run pnpm setup once from the monorepo root and everything resolves through the workspace.

Working on the editor (koenig-lexical)

Two modes:

  • Standalonepnpm dev inside koenig/koenig-lexical starts a demo app on http://localhost:5173 with all features enabled. Fastest feedback loop; no Ghost required.
  • Integratedpnpm dev:lexical from the monorepo root starts the full Ghost dev environment plus a rebuild watcher for the editor (and kg-default-nodes / kg-default-transforms) with a preview server on port 4173. Ghost Admin at http://localhost:2368/ghost loads your local editor build; changes appear after the few seconds it takes to rebuild.

See koenig-lexical/README.md for card-specific setup (Klipy API key for the gif card, CORS notes for bookmark/embed cards) and styling conventions.

Working on the kg-* Node libraries

The libraries consumed by ghost/core declare a source export condition pointing at their raw src/*.ts, listed before the compiled build/ entries. ghost/core's dev runner and Vitest configs activate that condition, so a source change in a kg-* package is picked up by a running Ghost dev server and by core's tests with no tsc rebuild. Production and the published npm tarballs ignore source and use build/.

You only need pnpm build (Nx handles the dependency order) for type-checking, the browser lanes, and production artifacts — not for the edit/run/test loop against ghost/core.

Testing

  • kg-* Node libraries share a Vitest base config — vitest.shared.ts (createKoenigVitestConfig). Run pnpm test:unit in the package for unit tests, pnpm test:types for type checks, and pnpm lint for lint. pnpm test runs the package's configured test suites and enforces coverage thresholds; packages with a posttest hook also run lint automatically.
  • koenig-lexical has Vitest unit tests and a large Playwright browser acceptance suite: pnpm test:unit, pnpm test:acceptance (headless by default; :headed, :report, and test:slowmo variants for debugging). See koenig-lexical/AGENTS.md. Ghost's end-to-end suite is the separate top-level e2e/ workspace.
  • kg-unsplash-selector also has a Playwright suite (pnpm test:acceptance).

Shipping

Ghost itself never installs these packages from npm:

  • Dev and CI resolve them from the workspace (workspace:~).
  • Ghost Admin bundles the editor at build time: ghost/admin/lib/asset-delivery copies koenig-lexical's UMD build into the admin assets, served at /ghost/assets/koenig-lexical/.
  • The release archive embeds the kg-* packages as component tarballs — ghost/core/scripts/pack.js discovers them transitively from ghost/core's dependencies and packs each one, so a deployed Ghost installs exactly the versions it was built with. src/ is excluded from each package's files array, so raw source is never shipped.

npm publishing (external consumers)

The Publish Packages workflow runs on stable Ghost release tags (no -rc prereleases), in parallel with Ghost's own publishing workflow. Release commits consume pending changesets and commit the resulting package versions before the tag is created.

The workflow runs scripts/publish-packages.js, which:

  1. Selects every publishable workspace package, including the public Koenig packages.
  2. Builds the selected packages and their workspace dependencies.
  3. Publishes each committed version that is not already on npm, in dependency order, rewriting workspace: ranges to published versions.

For an out-of-band publish, the same workflow has a workflow_dispatch escape hatch that can be narrowed to one package. The package version must already have been bumped, normally by consuming a changeset; the workflow does not calculate a new version itself.