systhema setup
Add, reconfigure or turn off database, email, maps, captcha, forms, redirects, AI, Cloudflare and locales.
On this page
Add or reconfigure Payload features — database, email, Google maps, form captcha, forms, redirects, the AI assistant, Cloudflare, or locales — on an already-scaffolded project, without re-running create (which requires an empty target directory and can't touch an existing project). This is how you answer "I skipped email during create, how do I add it now?", "I want to switch from SQLite to Postgres", or "turn the Forms module off."
systhema setup # state overview + interactive checkbox picker
systhema setup email # configure one feature interactively
systhema setup --email smtp --smtp-host "$SMTP_HOST" --yes # feature implied by flags, no positional needed
systhema setup ai --dry-run # plan only, no changes
systhema setup forms --forms off # toggle a module off (confirms first)
systhema setup locales --locales en,hu --default-locale en --yessetup reuses create's per-feature flags, env vars, and non-interactive credential resolution verbatim — --db, --db-url, --email, --email-from, --resend-key, --sendgrid-key, --smtp-*, --captcha, --recaptcha-*, --turnstile-*, --maps-key, --forms, --redirects, --ai, --ai-key, --ai-model, --ai-base-url, --cloudflare, --cloudflare-api-token, --cloudflare-api-key, --cloudflare-email, --cloudflare-zone-id, --locales, --default-locale. See the create flag table for what each one does.
Choosing featuresLink to this section
The features to touch are picked in priority order:
- Positional argument —
systhema setup emailtouches only the email feature. - Implied by flags — a feature-specific flag passed without a positional (e.g.
systhema setup --email smtp --yes) is enough;setupinfers the feature(s) to run from which flags were set. - Interactive picker — bare
systhema setupprints every feature's current state, then opens a checkbox prompt to pick which ones to (re)configure.
A non-interactive run (--yes) that can't resolve a feature from either a positional or flags exits with an error asking for one.
OptionsLink to this section
| Flag | Description | Default |
|---|---|---|
--ai <provider> | AI single provider: none | openai | anthropic | google | groq | openai-compatible ('none' disables the module) | |
--ai-base-url <url> | AI endpoint base URL (env: SYSTHEMA_AI_BASE_URL) | |
--ai-key <key> | AI provider API key (env: SYSTHEMA_AI_API_KEY) | |
--ai-model <model> | AI model id (env: SYSTHEMA_AI_MODEL) | |
--captcha <choice> | Form captcha: none | recaptcha | turnstile | both | |
--cloudflare <mode> | Cloudflare auth: token | key | none (env: SYSTHEMA_CLOUDFLARE_MODE) | |
--cloudflare-api-key <key> | Cloudflare Global API key (env: CLOUDFLARE_API_KEY) | |
--cloudflare-api-token <token> | Cloudflare API token (env: CLOUDFLARE_API_TOKEN) | |
--cloudflare-email <email> | Cloudflare account email (env: CLOUDFLARE_EMAIL) | |
--cloudflare-zone-id <id> | Cloudflare zone ID (env: CLOUDFLARE_ZONE_ID) | |
--confirm-db-switch | Required with --yes to switch the database adapter | false |
--confirm-disable | Required with --yes to disable a currently-enabled module | false |
--db <adapter> | Database adapter: sqlite | postgres | |
--db-url <uri> | Database connection string (env: DATABASE_URI) | |
--default-locale <code> | Default locale (must be in --locales) | |
--dry-run | Plan only, no changes | false |
--email <adapter> | Email adapter: resend | sendgrid | smtp | none | |
--email-from <addr> | Default from address (env: SYSTHEMA_EMAIL_FROM) | |
--force | Allow dirty tree + overwrite hand-edited modules + allow config line replaces | false |
--forms <on|off> | Forms module: on | off | |
--locales <codes|none> | Locales: comma-separated BCP 47 codes (e.g. en,hu), or 'none' to disable | |
--maps-key <key> | Google Maps API key (env: NEXT_PUBLIC_GOOGLE_MAPS_API_KEY) | |
--no-git-check | Skip git repo/clean checks (CI mode) | true |
--no-install | Skip dependency install after changes | true |
--recaptcha-secret-key <key> | reCAPTCHA secret key (env: RECAPTCHA_SECRET_KEY) | |
--recaptcha-site-key <key> | reCAPTCHA site key (env: NEXT_PUBLIC_RECAPTCHA_SITE_KEY) | |
--redirects <on|off> | Redirects module: on | off | |
--resend-key <key> | Resend API key (env: RESEND_API_KEY) | |
--sendgrid-key <key> | SendGrid API key (env: SENDGRID_API_KEY) | |
--smtp-host <host> | SMTP host (env: SMTP_HOST) | |
--smtp-pass <pass> | SMTP password (env: SMTP_PASS) | |
--smtp-port <port> | SMTP port (env: SMTP_PORT) | |
--smtp-user <user> | SMTP user (env: SMTP_USER) | |
--turnstile-secret-key <key> | Turnstile secret key (env: TURNSTILE_SECRET_KEY) | |
--turnstile-site-key <key> | Turnstile site key (env: NEXT_PUBLIC_TURNSTILE_SITE_KEY) | |
-y, --yes | Skip prompts | false |
Non-interactive re-runs never disable by omission. Under --yes, a feature you don't give a flag for keeps whatever it is already set to — systhema setup ai --yes on a project with AI configured is a no-op, not a switch-off. Turning something off is always explicit (--ai none, --forms off, --locales none), and still needs --confirm-disable.
Safety: setup only runs against Payload projects (the next/html templates have no server-side integrations to configure) and, like upgrade, preflights on a clean git tree unless --force / --no-git-check.
Divergence handlingLink to this section
Each feature's generated module (src/payload/database.ts, src/payload/email.ts, src/payload/forms.ts, src/payload/redirects.ts, src/payload/ai.ts, src/payload/cloudflare.ts) is compared, whitespace-normalized, against every render the current CLI's generators can produce (for a toggle that means both the on and the off render, so flipping a module is a pristine overwrite):
- Pristine (matches some generated render, untouched since scaffolding or a previous
setup) — overwritten silently. - Hand-edited (doesn't match any known render) — prints a diff and asks for confirmation before overwriting. A module scaffolded by an older CLI version won't match a current render either, so it's treated the same way — you'll see a diff and a confirm, never a silent overwrite. Under
--yes, a diverged module is skipped — and the command exits with status1— unless--forceis also passed. Declining the interactive overwrite confirmation has the same effect — the module is left untouched and the command exits with status1.
.env handlingLink to this section
setup edits .env in place. Detection reads .env, .env.development, .env.production, and .env.local (later files override earlier ones) so a value set anywhere is recognized, but every write lands in .env:
- Existing values are never blanked by an empty answer — leave a prompt empty to keep whatever's already set.
- Switching away from a provider (e.g. Resend → SMTP, or a keyed AI provider → none) comments out the old provider's keys instead of deleting them, preserving the values. Switching back later without supplying a new value restores them uncommented.
PAYLOAD_SECRETandSYSTHEMA_API_SECRETare never regenerated.- A project with no
.envat all gets one created fresh, seeded from whateversetupcan detect (installed adapters, existing env values) plus this run's choices.
Database switchLink to this section
Switching the database adapter (e.g. SQLite → Postgres) swaps the dependency and rewrites src/payload/database.ts, but your existing data is not migrated. setup prints a loud warning and asks for confirmation before proceeding; under --yes you must also pass --confirm-db-switch, or the switch is skipped and the command exits with status 1. Declining the interactive confirmation has the same effect — the switch is skipped and the command exits with status 1.
Module togglesLink to this section
Forms and Redirects are plain on/off modules rather than credential-carrying integrations. They own no .env variables — a module registers collections, and schema-shaping state belongs in checked-in source, not in per-environment variables — so the toggle state lives entirely in a generated file:
| Module | File | Export | What it controls |
|---|---|---|---|
forms | src/payload/forms.ts | forms | The form builder, the Forms + Form Submissions collections, and the form block in editors. |
redirects | src/payload/redirects.ts | redirect | The Redirects collection (including wildcard rules). |
payload.config.ts wires each one by shorthand (forms, / redirect,), the same way the database, email, AI, analytics and Cloudflare modules are wired, so systhema setup forms --forms off is a one-file rewrite.
systhema setup forms --forms off # confirms, then writes the disabled render
systhema setup redirects --redirects onTwo things are worth knowing:
- The Forms toggle never touches dependencies —
@payloadcms/plugin-form-builderis a regular dependency of@systhemaui/payload. The Redirects toggle only ever adds one:@payloadcms/plugin-redirectsis a required peer of@systhemaui/payload, so enabling on a project that somehow lacks it adds it todevDependencies, and disabling never removes it (an "unused" install is the correct steady state — removing it would produce an unmet-peer warning). --ai noneis the same kind of toggle — it disables the AI assistant module, commenting out theSYSTHEMA_AI_*variables rather than deleting them. It carries no disable confirmation: no user-authored collection disappears (the hidden AI usage log simply stops being registered, and its rows persist).
The disable guardLink to this section
Turning a module off never touches your data — the plugin just stops registering the collections, and the rows stay in the database. The risk is indirect (a later dev-mode schema push may offer to drop the now-orphaned tables — decline unless you mean it) and re-enabling restores access to everything.
Because it's still a visible, content-affecting change, setup prints the data-impact warnings and asks for confirmation, defaulting to No. Under --yes you must pass --confirm-disable; otherwise the disable is skipped and the command exits with status 1 — the exact mirror of --confirm-db-switch. If the module file was also hand-edited, you'll get both prompts: the diverged-file diff confirm and the disable confirm.
systhema setup forms --forms off --yes --confirm-disableAdopting missing modulesLink to this section
If a project predates a given feature (no src/payload/database.ts / email.ts / forms.ts / redirects.ts / ai.ts / cloudflare.ts yet), setup creates the missing module and tries to splice its wiring into payload.config.ts — adding the import plus the wiring itself. For db/email this means replacing a single, comma-terminated wiring line; forms, redirect, ai and cloudflare are inserted as a new property into the plugin-options object, or — when the key is already there as a single-line value (redirect: true, in pre-toggle projects) — replaced with the shorthand through the usual replace confirmation. Anything ambiguous — an unusual shape, an existing conflicting member, a multi-line expression — is left untouched, and setup prints the exact lines to add by hand instead.
The pre-toggle template's inline forms: { … } object is exactly such a multi-line expression, so on an older project setup forms writes the module but prints the wire step as a manual edit. The smooth path is systhema upgrade, whose extract-forms-redirects-modules codemod extracts both members into modules for you (it skips a customized forms object rather than guessing).
Google MapsLink to this section
systhema setup maps sets NEXT_PUBLIC_GOOGLE_MAPS_API_KEY. There's no "disable" choice for maps — to turn it off, comment out NEXT_PUBLIC_GOOGLE_MAPS_API_KEY in .env yourself.
LocalesLink to this section
systhema setup locales is the multilingual entry point. Unlike every other feature it writes neither src/payload/* nor .env: systhema.config.ts is the canonical locale source (payload.config.ts already projects it via locales: systhemaConfig.locales), so the connector edits that one file and then runs the project's own locale migration.
systhema setup locales # interactive
systhema setup locales --locales en,hu --default-locale en --yes # enable / reconfigure*
systhema setup locales --locales none --yes --confirm-disable # back to one languageWhat it does, in order:
-
Edits
systhema.config.ts— adds thedefineSysthemaLocales({ supported, default })block, the@systhemaui/coreimport, and thelocales,member on the config object (or removes all three when disabling). The plan preview shows it as~ systhema.config.ts. Anything it doesn't recognize — asysthema.config.js, a multi-linelocales:member, a missingSysthemaConfiganchor — is left untouched and printed as a manual step.It also declines to rewrite a contract that carries settings it doesn't collect. A definition holding
routing.domains,rtl,subfolder,missingorfallbackis left exactly as it is, and the block to paste is printed instead — replacing it wholesale would silently delete a production domain map, flip an RTL locale back to LTR, or reset the missing-translation policy. In-place reconfiguration therefore applies to a contract holding onlysupportedanddefault; edit a richer one by hand. -
Runs
<pm> exec systhema-core sync --migrate-localesafter the dependency install, shown in the plan preview as a→line (and skipped under--dry-run). That's the project's own@systhemaui/coredoing the structural work: wrappingnext.config, addinglocalestopayload.config.ts, and converting the site shell / route boundary.If it fails, the command prints the migration's own output and exits non-zero. The config edit stays on disk — it is exactly the input a re-run reads — but the project is half-migrated until the migration completes, so the run must not look successful to a script. (The most common cause is a customized
app/(site)/template.tsx, which the migration refuses to convert.)
Enabling locales also makes publication status per-locale: _status becomes a localized field on Pages, Posts and Components, so a locale can be published while another stays a draft (Per-locale publishing, opt out with localization: { localizeStatus: false }). A project with existing content therefore has to include the _status move in the reviewed migration plan it writes for the rest of the schema (Localization migration). systhema migrate migrate-localize-status performs that half.
The wizard collects only the supported codes and the default locale. RTL, per-locale domains, subfolders, missing behavior and frontend messages stay hand-edited in systhema.config.ts — see Frontend locales. *Because of that split, reconfiguration edits a contract holding just those two members; one carrying hand-authored settings is left alone with printed instructions (step 1).