Datum is an SEO content pipeline built on Payload CMS. It finds content-gap keywords, drafts articles from reusable templates, and checks each draft before publication.
Datum makes its editorial rules visible and editable. Editors set template rules in Payload. Maintainers keep shared rules in docs/style-guide.md. The generator and QA checks read both.
Before an editor can approve a draft, Datum checks its structure and readability without an LLM. One model call fact-checks claims with web search. A second reviews the writing against the template and style guide. Datum records tokens and cost for responses that parse successfully. Responses that fail JSON parsing are not yet recorded.
Everything runs in your Payload and Postgres setup under the MIT license. If a rule is wrong, edit it. If a draft fails, the QA results say which check rejected it.
| Path | Role |
|---|---|
cms/ |
Payload CMS 3 app using Next.js 16 and Postgres. Stores templates and articles and provides the admin UI. |
pipeline/ |
Node/TypeScript CLI that advances articles through research → generate → QA |
docs/ |
Style guide and other content rules that the pipeline reads, plus docs/operations.md for queues, webhooks, caching, and limits |
cms and pipeline share one Postgres database and the types generated in cms/src/payload-types.ts. The pipeline imports Payload in the same process. It does not call the CMS over HTTP.
External SEO data is isolated behind the AhrefsClient contract. To add another provider, implement that interface and inject it through StageContext; topic discovery and SERP research will consume the normalized results. The editable diagram source lives beside the SVG.
Research ends at brief_review: Datum writes a brief from the template, the research gaps and your brand voice, and waits. Approving the brief is what starts writing, checks and scoring — the first human decision comes before anything is paid for, not after. The pipeline never picks up brief_review, needs_revision, needs_review or blocked on its own; those are yours. The editable diagram source lives beside the SVG.
Editorial rules live in three places in the admin, all under the Governance nav group:
- Brand voice (
/admin/ops/governance/brand-voice) — a workspace-wide voice every generated title, description, FAQ, and body follows, layered on top ofdocs/style-guide.md. Set it up with a nine-step onboarding stepper or by uploading an existing brand guide (.md/.txt/.pdf/.docx) for one-call extraction into a draft you review before activating. An active voice is required for content runs. Seed a demo voice withnpm run seed -- --with-brand-voice. - Models (
/admin/globals/llm-settings) — which model runs generate, fact-check, qualitative review, and brand-voice extraction. Pick a model here, or leave it blank to fall back to the matchingPIPELINE_MODEL_*/BRAND_VOICE_EXTRACT_MODELenv var, or toclaude-opus-5if neither is set. Each model needs its provider's API key (ANTHROPIC_API_KEYforclaude-*,OPENAI_API_KEYforgpt-*/o3/o4-mini) wherever that call runs; see the env var split below. Acodex/*model (for examplecodex/gpt-5.6-terra) needs no key. It runs through your own Codex CLI login on that host instead; see Using your ChatGPT plan (Codex). - Source review (
/admin/ops/governance/source-review) — the domains the pipeline cited or saw ranking that nobody has rated yet. An unrated domain can't back a claim nobody else is making, so an article resting on one gets blocked; rate the ones you trust here and the next run counts them. Seedocs/information-gain.md.
- Node.js 22+
- npm with workspaces. You do not need pnpm for daily use.
- Docker for local Postgres through Docker Compose
# 1. Env files
cp .env.example .env
cp cms/.env.example cms/.env
# Generate a Payload secret:
# openssl rand -hex 32
# Set PAYLOAD_SECRET in both .env files, or at least in cms/.env.
# 2. Database
docker compose up -d
# 3. Install & seed
npm install
npm run seed
# 4. Admin UI
npm run dev
# → http://localhost:3000The seed creates this account for local development. Change its password before using Datum in a shared or deployed environment.
- Email:
admin@datum.local - Password: value of
SEED_ADMIN_PASSWORD, ordatum-dev-passwordif unset
Mock mode needs no API keys. Set MOCK_MODE=true, as shown in .env.example, and Datum uses fixtures instead of calling Ahrefs or Anthropic.
npm run pipeline:fetch -- --template Listicle --count 3
npm run pipeline:run
npm run pipeline:report -- --period weekOnboarding is one decision. /admin asks how Datum should sound: set up a brand voice, or start with the default and replace it later. Templates are seeded. Missing live-provider keys show as a banner for whoever deploys — they never block an editor.
Then New content (/admin/ops/new): pick the kind of piece (a template card), say what it is about — suggested topics from Ahrefs, or a keyword you already know — and create it. Research starts on its own; the piece opens; when research is done a brief appears with the angle, audience and sections. Edit it, approve it, and Datum writes the draft, runs the checks and scores it. Content (/admin/ops/content) is the one list: every piece on a five-step stepper (Research → Brief → Writing → Review → Publish), who it is waiting on, and the one thing to do next. Every run is stored in pipeline-runs and executed as a native Payload content-run task; a bar at the bottom of every admin page shows what the model is doing while a run is in flight. Live mode asks for a paid-provider confirmation before the expensive half starts.
Local development processes the content queue automatically every two seconds. Production intentionally disables in-process autorun; run this from a durable worker or scheduler instead:
npm run jobs:run --workspace cmsThat command processes one queued content run. Schedule it repeatedly for continuous production processing.
For live API calls, set MOCK_MODE=false and provide the key for each chosen model's provider (ANTHROPIC_API_KEY or OPENAI_API_KEY). A codex login on the host is the third way to satisfy this, for codex/* models. Ahrefs also needs AHREFS_API_KEY, TARGET_DOMAIN, and COMPETITOR_DOMAINS. See .env.example.
codex/* models run each call through the Codex CLI on your ChatGPT plan instead of a per-token API. npm install brings the binary. To use it:
- Run
codex loginon every host that makes model calls. That is the host runningnpm run jobs:run --workspace cmsand the host running the Next server, which does brand-voice extraction. Datum never reads or stores the token; preflight runscodex login status. - Pick a
codex/model in Models (/admin/globals/llm-settings). Six are available, the subset the ChatGPT plan serves:codex/gpt-5.6-sol,codex/gpt-5.6-terra,codex/gpt-5.6-luna,codex/gpt-5.5,codex/gpt-5.4,codex/gpt-5.4-mini. Thecodex/prefix alone decides routing. - Set
MOCK_MODE=falseexplicitly. A Codex login does not switch a workspace to live mode.
What to expect:
- Every call carries roughly 15k input tokens of overhead. A trivial prompt billed 15,767 input tokens, because Codex loads its own agent preamble and tool definitions on each call. Budget that per stage against your plan's quota. It is the main reason API keys stay the default and Codex is opt-in.
- Cost-log rows for these calls are marked
provider = codexand hold estimates at API rates. Your plan bills in its own included usage and credits, so the dollar figure is indicative, not what you were charged. - Each call runs in an isolated
CODEX_HOMEthat Datum manages. It holds a minimal config plus a symlink to your existingauth.json. Without it your own Codex config would apply to every call; on the machine this was measured on, that meant 11 MCP servers booting and anotifyhook launching a desktop app per call. Passing-c 'mcp_servers={}'does not prevent this, because-cmerges into the config table rather than replacing it.DATUM_CODEX_HOMEoverrides the managed home. - If
CODEX_HOMEis already set by other tooling (Orca sets it per account), whatever it points at is the login Datum uses. - Optional tuning:
CODEX_PATHoverrides the binary,CODEX_REASONING_EFFORTdefaults tomedium,CODEX_TIMEOUT_MSdefaults to600000.
This is for a local or otherwise trusted host. OpenAI has not stated whether embedding Codex with a ChatGPT login inside a hosted multi-tenant product is permitted, so that is out of scope.
Copy .env.example and cms/.env.example — both. The pipeline CLI loads cms/.env first and the root .env second, but the admin UI (Next.js) only ever loads cms/.env, so any variable the admin needs at request time — API keys, MOCK_MODE, BRAND_VOICE_EXTRACT_MODEL — has to be set in cms/.env too, not just the root file. Environment variables set in the shell override both files.
| Variable | Purpose |
|---|---|
DATABASE_URL |
Postgres connection string |
PAYLOAD_SECRET |
Payload token signing secret |
SITE_URL |
Public CMS origin used for canonical article URLs (defaults to http://localhost:3000) |
ANTHROPIC_API_KEY |
Claude models (claude-*). Not required in mock mode. |
OPENAI_API_KEY |
OpenAI models (gpt-*, o3, o4-mini). Not required in mock mode. |
AHREFS_API_KEY |
Keyword and SERP research. Not required in mock mode. |
TARGET_DOMAIN |
Domain that will publish the articles |
COMPETITOR_DOMAINS |
Comma-separated competitors for content-gap fetch |
MOCK_MODE |
true to use fixtures instead of paid APIs |
SEED_ADMIN_PASSWORD |
Password for the seeded admin user |
PIPELINE_MODEL_GENERATE / _FACT_CHECK / _QUALITATIVE_REVIEW |
Fallback model per pipeline stage when the Models admin field is blank |
BRAND_VOICE_EXTRACT_MODEL |
Fallback model for brand-guide upload extraction, same rule |
CODEX_PATH |
Overrides the Codex CLI binary used by codex/* models. Not required in mock mode. |
CODEX_REASONING_EFFORT |
Reasoning effort passed to the Codex CLI for codex/* models (default medium). Not required in mock mode. |
CODEX_TIMEOUT_MS |
Per-call timeout for codex/* models, in milliseconds (default 600000). Not required in mock mode. |
DATUM_CODEX_HOME |
Overrides the managed CODEX_HOME Datum builds for each codex/* call. Not required in mock mode. |
PAYLOAD_AUTO_LOGIN (cms/.env only) |
true to skip the admin login form in local dev; never honoured when NODE_ENV=production |
PAYLOAD_AUTO_LOGIN_EMAIL (cms/.env only) |
Which seeded user to auto-login as (default admin@datum.local) |
| Script | Description |
|---|---|
npm run dev |
CMS Next.js dev server |
npm run seed |
Create or update templates and the admin user. Add -- --with-brand-voice to also seed and activate a demo brand voice. |
npm run pipeline:fetch -- --template NAME_OR_ID |
Create templated articles from content-gap keywords |
npm run pipeline:run |
Advance every eligible article one stage set: research (stops at the brief), then generate → QA → scoring for approved briefs |
npm run pipeline:report |
Print the QA and spending report |
npm run typecheck / lint / test |
Workspace checks described in CONTRIBUTING.md |
- CONTRIBUTING.md covers setup, tests, and pull request expectations.
- SECURITY.md explains how to report a vulnerability.
- CODE_OF_CONDUCT.md
- CLAUDE.md documents the architecture for contributors and coding agents.
- docs/style-guide.md contains the editorial rules and banned phrases checked by QA.
- docs/open-source-checklist.md lists the maintainer steps for making the repository public.
Agent helpers live under .claude/ and vendor/claude-plugins/. Datum runs without them.
MIT © 2026