Docs
Next

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 --yes

setup 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:

  1. Positional argument — systhema setup email touches only the email feature.
  2. Implied by flags — a feature-specific flag passed without a positional (e.g. systhema setup --email smtp --yes) is enough; setup infers the feature(s) to run from which flags were set.
  3. Interactive picker — bare systhema setup prints 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

FlagDescriptionDefault
--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-switchRequired with --yes to switch the database adapterfalse
--confirm-disableRequired with --yes to disable a currently-enabled modulefalse
--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-runPlan only, no changesfalse
--email <adapter>Email adapter: resend | sendgrid | smtp | none
--email-from <addr>Default from address (env: SYSTHEMA_EMAIL_FROM)
--forceAllow dirty tree + overwrite hand-edited modules + allow config line replacesfalse
--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-checkSkip git repo/clean checks (CI mode)true
--no-installSkip dependency install after changestrue
--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, --yesSkip promptsfalse

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 status 1 — unless --force is also passed. Declining the interactive overwrite confirmation has the same effect — the module is left untouched and the command exits with status 1.

.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_SECRET and SYSTHEMA_API_SECRET are never regenerated.
  • A project with no .env at all gets one created fresh, seeded from whatever setup can 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:

ModuleFileExportWhat it controls
formssrc/payload/forms.tsformsThe form builder, the Forms + Form Submissions collections, and the form block in editors.
redirectssrc/payload/redirects.tsredirectThe 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 on

Two things are worth knowing:

  • The Forms toggle never touches dependencies — @payloadcms/plugin-form-builder is a regular dependency of @systhemaui/payload. The Redirects toggle only ever adds one: @payloadcms/plugin-redirects is a required peer of @systhemaui/payload, so enabling on a project that somehow lacks it adds it to devDependencies, and disabling never removes it (an "unused" install is the correct steady state — removing it would produce an unmet-peer warning).
  • --ai none is the same kind of toggle — it disables the AI assistant module, commenting out the SYSTHEMA_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-disable

Adopting 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 language

What it does, in order:

  1. Edits systhema.config.ts — adds the defineSysthemaLocales({ supported, default }) block, the @systhemaui/core import, and the locales, 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 — a systhema.config.js, a multi-line locales: member, a missing SysthemaConfig anchor — 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, missing or fallback is 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 only supported and default; edit a richer one by hand.

  2. Runs <pm> exec systhema-core sync --migrate-locales after the dependency install, shown in the plan preview as a → line (and skipped under --dry-run). That's the project's own @systhemaui/core doing the structural work: wrapping next.config, adding locales to payload.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).