Editor system

The editor system (@nordcom/commerce-cms/editor) is the unified way to build admin pages for CMS collections. Each editor page is driven by a manifest — field descriptors plus route shape, route-level access predicates, list columns, live preview URL, and revalidation paths.

Why

Before this system the admin app shipped two parallel patterns: per-collection routes with hand-written form/action files, and a separate bridge plugin for the database-backed entities. Adding a feature (tightening field allowlists, fixing the autosave loop, improving error handling) meant touching both. The unified editor system collapses them into one primitive set, and the Convex migration finished the consolidation — every collection edits through the same manifest-driven stack against the Convex cms/documents functions.

The mental model

Field descriptors              ←   single source of truth for fields, drafts,
(src/descriptors, used by         versions, locales — the shapes `pnpm cms:gen`
each manifest)                     derives every artifact from.

CollectionEditorManifest       ←   route shape, route-level access, list
                                   columns, live-preview URL, revalidation.
                                   Declares the collection by slug.

EditorRuntime                  ←   admin-app dependency bundle: auth helper,
                                   form-state builder, shell-props helper,
                                   shell components. Built once per app.

<EditorEditPage>               ←   server component; consumes manifest +
<EditorListPage>                   runtime + route params; renders the
<EditorNewPage>                    descriptor-driven form / list inside
<EditorVersionsPage>               the admin's chrome.

pnpm cms:gen                   ←   codegen step that emits `'use server'`
                                   action wrappers per manifest into
                                   `apps/admin/src/lib/cms-actions/_generated/`
                                   (plus the content types and Convex table
                                   validators).

Route file shape

A per-collection page.tsx is ~25 lines:

import 'server-only';
import { businessDataEditor } from '@nordcom/commerce-cms/editor/manifests';
import { EditorEditPage } from '@nordcom/commerce-cms/editor/ui';
import * as actions from '@/lib/cms-actions/_generated/businessData';
import { editorRuntime } from '@/lib/editor-runtime';

export default async function Page({ params, searchParams }: {
    params: Promise<{ domain: string }>;
    searchParams: Promise<{ locale?: string }>;
}) {
    const { domain } = await params;
    const sp = await searchParams;
    return (
        <EditorEditPage
            manifest={businessDataEditor}
            runtime={editorRuntime}
            params={{ domain, id: 'singleton' }}
            searchParams={sp}
            generatedActions={{
                saveDraft: actions.businessDataSaveDraft,
                publish: actions.businessDataPublish,
                create: actions.businessDataCreate,
                delete: actions.businessDataDelete,
                bulkDelete: actions.businessDataBulkDelete,
                bulkPublish: actions.businessDataBulkPublish,
                restoreVersion: actions.businessDataRestoreVersion,
            }}
        />
    );
}

When to use it

  • A new CMS collection needs an admin edit / list / new / versions page.
  • An existing collection's bespoke route is being refactored away.

When NOT to use it

  • A route doesn't follow the "edit one collection" shape — e.g. multi-collection dashboards, custom workflows. Build those by hand and let them coexist with editor routes.
  • One-off scripts or admin tools that don't need the editor's form chrome at all.

Continue reading

On this page