Site Settings — Defaults Reference
Every setting below is stored as a row in the AppSetting key-value table and
is editable at runtime via Admin → Site Settings (or, for the first few,
the /setup wizard on a fresh install). None of them are required — this
page documents exactly what happens when a given key has never been saved.
Settings changes take effect within 60 seconds everywhere (the backend's
in-process settings cache TTL) without a server restart.
Organisation
| Setting |
Default when unset |
Notes |
org_name |
"Cross Code" |
Falls back to ORG_NAME env var first, then this hardcoded default (the platform vendor's name, used as a neutral placeholder — org_name is a required field in the setup wizard, so this fallback is practically unreachable once a site has been set up). Shown in the navbar, email headers/subjects, and WhatsApp messages. |
org_tagline |
"Connecting community through events" |
Falls back to ORG_TAGLINE env var first. Shown under the org name in the email header. |
org_email |
(blank) |
No fallback in most places. Exception: the Terms of Use / Privacy Policy pages default to privacy@example.com (an obvious placeholder) if unset — see the Legal section below. |
org_phone / org_address |
(blank) |
No fallback — simply omitted wherever shown if unset. |
app_base_url |
APP_BASE_URL or FRONTEND_URL env var, else http://localhost:3001 |
Used to build links in emails (password reset, ticket delivery, etc.). Should always be set in production. |
Branding
| Setting |
Default when unset |
Notes |
primary_color |
Legacy accent_color value if present, else #4F46E5 (indigo) on the website / #1e3a5f (navy) in emails |
See Two different fallbacks below — this is the one setting with a genuine inconsistency worth knowing about. Drives buttons, links, nav highlights, and the email header/CTA. |
secondary_color |
210 40% 96.1% background / near-black text (light gray "wash", shadcn's stock theme) |
The Branding tab's color picker pre-fills #8B5CF6 as a suggested starting point, but that's only a UI default — if you never hit Save, the actual site uses the light-gray fallback, not violet. |
accent_color |
210 40% 96.1% background / near-black text (same light gray as Secondary) |
Same story as Secondary — the picker pre-fills #EC4899 (pink) as a suggestion only. |
logo_url |
Bundled logo (frontend/src/app/church_logo.jpg) |
Shown in the navbar. This is still the original Hope Family Church artwork checked into the repo — swap the file (or upload a real logo via the Branding tab) before deploying for a different organisation. |
favicon_url |
frontend/public/favicon.ico (the site's original generic favicon) |
Shown in the browser tab. |
Two different fallbacks for primary_color
If primary_color has never been saved (no admin has ever used the new
3-color Branding tab) and there's no legacy accent_color value either,
the website and outgoing emails currently disagree on what "no color" means:
- Website (
globals.css / tailwind.config.js static defaults): #4F46E5, a bright indigo.
- Emails (
backend/src/utils/email.js getOrg()): #1e3a5f, a dark navy — unless overridden by the EMAIL_HEADER_COLOR env var.
In practice this only matters on a brand-new install before the setup wizard
or Branding tab has been saved even once — the wizard always writes
primary_color, so this gap closes itself the moment setup completes.
Notifications
| Setting |
Default when unset |
Notes |
reg_notification_emails |
REGISTRATIONS_EMAIL env var, else (blank) |
If truly blank, no internal notification email is sent for new registrations/payments/donations — it's silently skipped, not an error. |
Email delivery (SMTP)
| Setting |
Default when unset |
Notes |
smtp_host |
SMTP_HOST/EMAIL_HOST env var, else (blank) |
If blank, the app does not send real email — it uses nodemailer's jsonTransport, which just logs the would-be email to the console. This is the single biggest "nothing is configured" gotcha — ticket/payment/password-reset emails silently don't go anywhere until SMTP is set up. |
smtp_port |
587 |
|
smtp_secure |
false |
|
smtp_user / smtp_pass |
(blank) |
Stored AES-256-GCM encrypted (key derived from JWT_SECRET). |
smtp_from |
(blank) |
If blank, outgoing mail's From: address falls back to the literal no-reply@crosscode.local. |
Payments
Managed in the Payments tab (through /api/payments/providers/settings and /api/yoco, not the generic settings endpoints).
| Setting |
Default when unset |
Notes |
payfast_merchant_id / payfast_merchant_key |
PAYFAST_MERCHANT_ID / PAYFAST_MERCHANT_KEY env vars, else (blank) |
From the PayFast (or Sandbox) dashboard. Both are needed before PayFast can take payments. The key isn't secret (it's in the payment form), so it's shown in full. Managed through /api/payfast. |
payfast_passphrase |
PAYFAST_PASSPHRASE env var, else (blank) |
Stored encrypted. Must match the dashboard's salt passphrase exactly, or PayFast refuses payments ("signature does not match"). |
payfast_mode |
PAYFAST_MODE env var, else sandbox |
sandbox or live: where payers are sent and where notifications are confirmed. |
payment_providers |
Yoco enabled |
JSON { order: [...], enabled: { yoco: true } }: which online payment providers are on, and their order (the first one enabled and set up is the default). Providers not listed yet come last, switched off (Yoco on, as it was the only provider). If none is on and set up, online payments are off. |
yoco_secret_key |
YOCO_SECRET_KEY env var, else (blank) |
Stored AES-256-GCM encrypted. If blank, online checkouts and sponsorships fail with "online payments are not configured". |
yoco_webhook_secret |
YOCO_WEBHOOK_SECRET env var, else (blank) |
Stored encrypted. If blank, Yoco webhooks are rejected, so payments aren't confirmed automatically. |
yoco_webhook_id |
(blank) |
The Yoco webhook whose secret is stored, set when it's generated from the tab. Used to mark it "Active" and to clear the secret when it's deleted. |
Legal (Terms of Use / Privacy Policy)
| Setting |
Default when unset |
Notes |
legal_operator_name |
"[operator details not yet configured]" |
Deliberately a generic, obviously-incomplete placeholder rather than any real person's or organisation's name. Unlike org_name, these legal fields are optional and can realistically stay unset on a live site — defaulting them to Cross Code's (or anyone else's) real identity would misrepresent who's legally responsible for that deployment. |
legal_io_name |
"[not yet configured]" |
Same reasoning as legal_operator_name. |
legal_io_email |
"io@example.com" |
Rendered as a mailto: link, so the placeholder stays a syntactically valid address on the IANA-reserved example.com domain rather than a bracketed non-email string that would break the link. |
legal_website_url |
"example.com" |
Same reasoning — used to build an https:// link. |
legal_effective_date |
"[not yet set]" |
Plain text only, no link — a bracketed placeholder is fine here. Only used by the built-in privacy notice: once a version is published in Admin → Privacy, its publish date is the effective date. |
notify_user_on_impersonation |
"false" |
When "true", a user is emailed (with the reason) whenever an admin views their account with Impersonate. Set under Site Settings → Legal → Support access. |
consent_medical_retention_days |
30 |
Youth events: medical and emergency details from parental consent forms are deleted this many days after the event ends (daily 09:00 job). The consent record itself — who agreed, when, to which text — is kept. Set under Site Settings → Legal. |
org_email (as used on legal pages specifically) |
"privacy@example.com" |
Different fallback than org_email's general default (blank) — also rendered as a mailto: link, so it needs to stay a valid-looking address. |
WhatsApp
| Setting |
Default when unset |
Notes |
WAWP_ACCESS_TOKEN / WAWP_INSTANCE_ID |
WAWP_ACCESS_TOKEN/WAWP_INSTANCE_ID env vars, else (blank) |
No hardcoded default — WhatsApp sending is simply unavailable until both are configured (Admin → Site Settings → WhatsApp). |
WAWP_WEBHOOK_SECRET |
Generated (random) the first time the WhatsApp tab is opened |
Part of the webhook URL shown in Site Settings → WhatsApp (/api/whatsapp/webhook?secret=…). WAWP doesn't sign webhooks, so incoming messages (STOP replies) are only acted on when the secret matches. Not editable in the UI; delete the row to get a new one (then update the URL in WAWP). The URL uses BACKEND_URL. |
Privacy (POPIA)
| Setting |
Default when unset |
Notes |
legal_io_email (Information Officer, Site Settings → Legal) |
— |
Also where new privacy requests and the daily "requests due" reminder are emailed, together with all admins. The io@example.com placeholder is skipped. |
PRIVACY_JOBS_ENABLED (env) |
true |
The daily 08:00 privacy job that reminds admins of requests due within a week or overdue. |
privacy_notice_draft |
— |
The unpublished draft from Admin → Privacy → Privacy notice (JSON). Published versions are in the PrivacyNotice table. With no published version the built-in notice (backend/src/utils/privacyNotice.js) is shown. The notice's org name, Information Officer, website, emails and medical retention days come from the settings above when it's shown. |
People's News & announcements opt-in is per account (User.newsEmail, User.newsWhatsApp), not a site setting. Unsubscribe links are signed with JWT_SECRET and don't expire — changing JWT_SECRET breaks links in emails already sent.
Backups
| Setting |
Default when unset |
Notes |
backup_retain_count |
14 |
How many nightly backups to keep on disk (oldest deleted beyond this count). Editable at Admin → Site Settings → Backups. See BACKUP_ENABLED (env var) to turn the nightly job off entirely. |
Setup state
| Setting |
Default when unset |
Notes |
setup_complete |
false (treated as "not true") |
Controls whether /setup redirects to /login or runs the first-time wizard. Set to "true" automatically when the wizard finishes. |