Reference

Configuration reference

Edit page
Customer Portal environment variables, defaults, precedence, secrets, and production checks.

Customer Portal reads deployment settings from the process environment. Copy .env.example for local development, but provide production values through the hosting platform or secret manager.

Environment variables

VariableRequiredDefaultMeaning
DATABASE_URLYesNonePostgreSQL connection string used by the shared Drizzle client and migrations.
PUBLIC_URLProductionhttp://localhost:3051 in .env.examplePublic origin used to construct application links and as the fallback Better Auth origin.
BETTER_AUTH_URLProductionFalls back to PUBLIC_URLCanonical Better Auth server and client origin. It must address the same deployed portal.
BETTER_AUTH_SECRETYesNone in productionHigh-entropy secret used by Better Auth for encryption, signing, and hashing. Use at least 32 characters and keep the same value across application instances.
PORTAL_ENCRYPTION_KEYSaved credentialsEmptyBase64-encoded 32-byte root key used to derive purpose-specific keys for credentials saved through portal settings.
ADMIN_EMAILSNoEmptyComma-separated, case-insensitive email allowlist for system administrators. Whitespace is trimmed.
PORTAL_DEMONofalseEnables the protected interactive demo mode. Use only for dedicated demo deployments, never as a production access-control mechanism.
PORTAL_PROVIDER_NAMENoEmptyProvider organization name bootstrapped by the portal after migrations. Configure together with PORTAL_PROVIDER_SLUG.
PORTAL_PROVIDER_SLUGNoEmptyProvider organization slug bootstrapped by the portal after migrations. Configure together with PORTAL_PROVIDER_NAME.
RESEND_API_KEYEmail fallbackEmptyResend credential used until a system administrator saves portal-wide email settings.
RESEND_FROM_EMAILEmail fallbackEmptySender used with the environment Resend credential. Saved Admin → Email settings take precedence.
PORTAL_EMAIL_BRAND_NAMEEmail brandingNuxt Customer PortalBrand name used by the default email frame in apps without portal-managed branding.
PORTAL_EMAIL_BRAND_TAGLINEEmail brandingCustomer workspaceTagline used by the default email frame in apps without portal-managed branding.
PORTAL_EMAIL_BRAND_LOGOEmail brandingEmptyHTTPS or image data URL used as the default email logo in apps without portal-managed branding.
PORTAL_EMAIL_BRAND_LOGO_LIGHTEmail brandingPORTAL_EMAIL_BRAND_LOGOLight-mode logo available to templates as brand_logo_light.
PORTAL_EMAIL_BRAND_LOGO_DARKEmail brandingPORTAL_EMAIL_BRAND_LOGODark-mode logo available to templates as brand_logo_dark.
PORTAL_EMAIL_BRAND_ICON_LIGHTEmail brandingEmptyLight-mode icon available to templates as brand_icon_light.
PORTAL_EMAIL_BRAND_ICON_DARKEmail brandingEmptyDark-mode icon available to templates as brand_icon_dark.
PORTAL_EMAIL_PRIMARY_COLOREmail branding#0ea5e9Six-digit hexadecimal accent color used by the default email frame in apps without portal-managed branding.
PORTAL_GITHUB_ENABLEDNofalseSet to true to show and configure GitHub sign-in when both GitHub credentials are also present.
GITHUB_CLIENT_IDGitHub sign-inEmptyGitHub OAuth client identifier.
GITHUB_CLIENT_SECRETGitHub sign-inEmptyGitHub OAuth client secret.
PORTAL_GOOGLE_ENABLEDNofalseSet to true to show and configure Google sign-in when both Google credentials are also present.
GOOGLE_CLIENT_IDGoogle sign-inEmptyGoogle OAuth client identifier.
GOOGLE_CLIENT_SECRETGoogle sign-inEmptyGoogle OAuth client secret.
PORTAL_REGISTRATION_MODENoopenAccount policy: open, invitation-only, or disabled. Invalid values fall back to open.
PORTAL_TERMS_URLNo/Link shown from authentication forms for the deployment's Terms of Service.

The provider flags use strict string parsing: only the literal value true enables a flag. Set a provider flag to false when the deployment does not configure that provider.

Generate BETTER_AUTH_SECRET with openssl rand -base64 32; never copy a documentation placeholder into a deployment. See the Better Auth secret reference for the upstream authentication contract.

Generate PORTAL_ENCRYPTION_KEY separately with openssl rand -base64 32. Never reuse the authentication secret as the portal encryption key.

Runtime variables

These variables belong to Node or the generated Nitro server rather than a Customer Portal feature:

VariableProduction behavior
NODE_ENVSet to production by the Docker image and normal production runtimes. Customer Portal uses it to avoid retaining the development database singleton.
HOSTBind address for the generated server. The Docker image sets 0.0.0.0; use a loopback address only when a local proxy is the sole caller.
PORTListening port for the generated server. Nitro uses port 3000 unless the runtime overrides it.

Tooling-only variables

Repository verification and release tooling also read environment variables that are not portal deployment settings:

VariablePurpose
CIEnables continuous-integration behavior in package and starter verification.
DEMO_TEST_URLOverrides the deployed demo origin exercised by demo browser tests.
GITHUB_TOKENAuthenticates repository metadata and source-link verification in CI.
PORTAL_STARTER_TEST_DATABASE_URLSupplies the disposable PostgreSQL database used by generated-starter verification.
RELEASE_TAGConfirms that package versions match the GitHub release tag before publication.

URL precedence

The authentication server resolves its origin in this order:

  1. BETTER_AUTH_URL;
  2. PUBLIC_URL;
  3. the local development fallback.

Set both URL variables to the same HTTPS origin in production. A mismatch can produce incorrect email links, rejected OAuth callbacks, or session cookies attached to the wrong host. Do not add a trailing path.

.env
PUBLIC_URL=https://portal.example.com
BETTER_AUTH_URL=https://portal.example.com

Registration and administrators

PORTAL_REGISTRATION_MODE controls who may create an account:

ValueBehavior
openAnyone can use the signup flow.
invitation-onlySignup is available only in an organization invitation flow.
disabledPublic signup is unavailable; existing users may still sign in.

ADMIN_EMAILS grants the system-wide administrator role when a listed account registers or signs in. This is separate from the owner, admin, and member roles inside an organization. Limit this list to accounts that genuinely administer the whole installation.

Email ownership

The Resend environment variables belong to the platform and send authentication mail. Organization-owned transactional mail is configured in the portal UI and stored per organization. The Invoices module uses that organization credential for invoices and reminders, so changing the platform Resend key does not replace an organization's sender.

Secret handling

Keep these server-only values out of source control, client runtime configuration, logs, screenshots, and issue reports:

  • DATABASE_URL;
  • BETTER_AUTH_SECRET;
  • PORTAL_ENCRYPTION_KEY;
  • RESEND_API_KEY;
  • GITHUB_CLIENT_SECRET;
  • GOOGLE_CLIENT_SECRET;
  • saved portal email credentials.

Client IDs and public origins are identifiers rather than secrets, but should still be specific to the intended deployment.

Do not rotate BETTER_AUTH_SECRET by simply replacing it on running instances: existing encrypted or signed auth state may depend on the prior value. Plan secret rotation using Better Auth's versioned-secret mechanism and test active sessions and OAuth flows before production rollout.

Keep PORTAL_ENCRYPTION_KEY stable, backed up with the database, and identical on every application instance. Portal Core derives separate keys for email, product storage, Stripe, planning OAuth, and other purposes; one module's ciphertext cannot be decrypted as another module's secret. Replacing this root key without re-encrypting saved credentials makes those credentials unreadable.

Legacy encryption overrides

Upgraded deployments can continue using these former module-specific keys as optional overrides:

  • PORTAL_EMAIL_ENCRYPTION_KEY;
  • PRODUCTS_STORAGE_ENCRYPTION_KEY;
  • PRODUCTS_STRIPE_ENCRYPTION_KEY;
  • PLANNING_ENCRYPTION_KEY.

An override takes precedence over PORTAL_ENCRYPTION_KEY for its module. Retain the old variable while credentials encrypted with it remain stored. To consolidate an upgraded deployment, re-save or reconnect the affected integration with only PORTAL_ENCRYPTION_KEY configured; verify it, then remove the old override. Stripe continues to accept the storage override as its legacy fallback when no Stripe-specific override is set.

Production verification

After changing configuration:

  1. restart the application so Nitro reads the new environment;
  2. open the portal at PUBLIC_URL and complete a sign-in;
  3. verify signup matches the configured registration mode;
  4. request an authentication email and inspect its origin and sender;
  5. test each enabled OAuth provider from the public hostname;
  6. confirm a normal member does not receive system-administrator access;
  7. run the deployment checks in Deployment.