The React Ghost admin interface.
The application uses admin-x-framework for API hooks and routing. Shade
provides its application wrapper and design system. After Vite builds Admin,
pnpm assemble:assets runs the standalone assembler in tools/scripts/ to copy
dist/ into ghost/core/built/admin/. It ships Koenig’s embed renderer
separately in ghost/core/built/embed-renderer/.
Route handles own sidebar visibility. The hideAdminSidebar handle hides it
for focused screens such as the editor.
src/index.css is the single Tailwind CSS entry point for Admin. It imports
Shade's styles and uses @source directives to scan Admin, Shade, ActivityPub,
Admin Framework, and the embedded Koenig selector. Only this app loads the
@tailwindcss/vite plugin for the embedded Admin CSS lane.
Embedded Admin apps must not import @tryghost/shade/styles.css themselves.
Doing so generates duplicate utilities and creates cascade conflicts.
Admin also owns the shared .koenig-react-editor width and centering rule.
Koenig loads its own editor stylesheet through fetchKoenigLexical.
Shade's Tailwind imports are unlayered, so source order resolves overlapping utilities. Do not move Shade's imports into a CSS layer without accounting for the cascade.
The shared ThemeProvider owns the appearance preference and the Admin theme controller. The controller applies the root dark class, follows system appearance and suppresses transitions during a switch.
Ghost Admin and Ghost Core can deploy at different times. New Admin UI that depends on a new setting, endpoint, or configuration value must detect backend support and hide or safely disable the feature when it is absent. A Labs flag alone is not a compatibility check because the flag may exist before the supporting backend version is live.
Add an acceptance test for the older-backend case. The social accounts settings and membership tiers tests contain current examples of hiding controls until their supporting settings are present.
Screens, the editor and the search modal load their code when first needed. A signed-in shell preloads both Settings chunks when the browser is idle, keeping the first Settings visit from waiting on its code when preloading has finished. A browser never fetches a module again after loading it failed, so when that code fails to load the admin reloads at the same route. It reloads at most once a minute and not while the browser is offline; within that minute a failed screen is left on screen, where Admin's own screens offer Reload, as they do when they crash for any other reason. The search modal fails inside its own dialog, leaving the rest of the admin in place. Sentry gets each automatic reload as a warning, grouped apart from the failures left on screen, which are reported as errors.
# Start development server (from monorepo root)
pnpm devAdmin, Admin Framework and Shade hot-reload through Admin's dev server. It
pre-bundles dependencies in a separate vite optimize run first, because
Rolldown holds a cold pre-bundle's memory until its process exits.
Development commands do not change Labs settings. To preview a flagged feature
in one browser tab, open http://localhost:2368/ghost/#/?labs=<flag>. These
session overrides
survive navigation and reloads in that tab; use ?labs= to clear them.
Build new Admin features in this React app. Use admin-x-framework for API
access and Shade for UI. Product copy belongs in the ghost namespace; follow
the internationalization guide.
pnpm dev runs pnpm nx run @tryghost/admin:build:dev before starting Admin's
dev server. It builds Koenig Lexical and the Unsplash selector, the only
workspace packages the dev server loads from built output.
Vite resolves Admin Framework and Shade through their source exports in
development, production and tests. It tracks each package’s source and path aliases
directly, and transforms Shade’s SVG icons with SVGR. The shared CSS lane stays
in this app. Compiled library builds and watchers remain available for other
consumers. Typechecks and editors still use the library declarations, which
pnpm nx run @tryghost/admin:test:types builds.
The post editor is the largest area with documentation of its own — start at
src/editor/README.md before changing anything under
src/editor/.
- Unit tests (
pnpm test:unit): Vitest + jsdom, colocated*.test.ts(x)files. - Acceptance tests (
pnpm test:acceptance): the real app in real Chromium against a fake admin API served through MSW — see test-utils/acceptance/README.md. - Typechecks (
pnpm test:types): TypeScript checks app code, test code and Vite configuration without bundling Admin. - Browser e2e against a real Ghost instance lives in the top-level
e2e/workspace.
From the monorepo root, use pnpm nx run @tryghost/admin:test:unit or
pnpm nx run @tryghost/admin:test:acceptance, or
pnpm nx run @tryghost/admin:test:types to build the required React
libraries first. These targets do not boot Ghost. The dependsOn list of
test:types explicitly names the React dependencies with a build target;
update it when adding one. Add it to build:dev, test:unit and
test:acceptance only if Vite loads its built output rather than its source
export.
Nx caches unit, acceptance and typecheck results. Their inputs include every
transitive dependency's default inputs. Test inputs also include Core's aliased card assets, the lockfile and runtime
settings such as Node version, platform, timezone and CI mode. A digest of
local .env and .env.* files covers Vite's mode-specific configuration without
printing their values. Shard and other
CLI arguments get separate cache keys. Use --skip-nx-cache to force a fresh run.
pnpm test and pnpm check at the root run Admin's test:unit and test:types
tasks and their prerequisites. Admin's aggregate test target schedules the same
tasks and is not cached itself, so an outer cache cannot skip their input checks.
CI changes confined to Admin's *.test.ts(x), *.screen.ts, test-utils/ or
vitest.acceptance.config.ts skip the build, packaging and browser E2E lane.
Affected unit, app acceptance, lint and typecheck checks still run. Changes to
runtime code or shared configuration keep the full lane.
# Build production bundle
pnpm nx run @tryghost/admin:buildThe assembler reads the individual build outputs, so it can also be rerun with
pnpm nx run @tryghost/admin:assemble:assets after those builds. It does not run
compilers or upload sourcemaps. See
Admin asset assembly for the input
requirements.
This outputs to apps/admin/dist/ and updates the assets in ghost/core/built/admin/.
The build also writes hidden sourcemaps: .map files that no bundle references.
With IS_SHIPPING set, as CI does for main and release tags, it injects Sentry
debug IDs into the bundles. CI then uploads the maps with sentry-cli in a
separate step, so a slow or unavailable Sentry can't block the build.