Environment variables

Environment variables, defaults, and required values in the default template.

Saavo stores local development variables in .env and production variables in .env.production. Both files use example.vars as their template. If .env does not exist when you run npm run saavo:init, the script creates it from the template and generates a missing SAAS_SECRET automatically.

Do not commit environment files

.env and .env.production can contain secrets and are included in .gitignore by default. Do not commit either file to Git or expose sensitive values from them in logs, screenshots, or frontend code.

VITE_ variables are public in the browser

Variables prefixed with VITE_ are written to frontend files at build time and can be viewed by any visitor in the browser. Use them only for public information, never for API keys, access tokens, passwords, or private keys.

Environment files

.env

Used for local development, testing, and local previews. npm run saavo:init preserves an existing file and only adds a missing SAAS_SECRET. It does not overwrite values you have already entered.

.env.production

The production environment file used by deployment scripts. npm run deploy:init creates or updates this file, writes the current Worker's workers.dev URL, and prompts you for the required configuration. Do not assume that all existing values will remain unchanged. Empty strings are not written to the remote Worker, and remote secrets absent from the file are not deleted.

For a standalone local production build, the project also supports a local build workflow that reads .env if .env.production does not exist. This does not mean the project is configured for production deployment.

During production deployment, public variables are passed through Wrangler's --var option. All other nonempty variables are written as Worker secrets through a temporary secrets file, which is deleted immediately after use.

Storage methodVariables
Plain variablesVITE_SITE_URL, VITE_SAAVO_COLLECT_API_HOST, VITE_SAAVO_COLLECT_API_ENDPOINT, CLOUDFLARE_TURNSTILE_SITE_KEY, STRIPE_CONNECTION_ID, PADDLE_CONNECTION_ID, PADDLE_ENVIRONMENT, PADDLE_CLIENT_TOKEN, GITHUB_CLIENT_ID, GOOGLE_CLIENT_ID, R2_ACCOUNT_ID
SecretAll other nonempty variables

Keep Cloudflare deployment credentials out of .env.production

CLOUDFLARE_API_TOKEN, CLOUDFLARE_API_KEY, CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_EMAIL, CF_API_TOKEN, CF_ACCOUNT_ID, and all variables prefixed with WRANGLER_ are for deployment tools only. npm run deploy:init and npm run deploy:update reject .env.production files that contain these variables.

Site and analytics

VITE_SITE_URL

Prop

Type

example.vars does not define this variable. Local development generates http://127.0.0.1:<port>. Local builds without .env.production use http://127.0.0.1:4173. On the first deployment, the script writes the current Worker's workers.dev URL.

VITE_SAAVO_COLLECT_API_HOST

Prop

Type

VITE_SAAVO_COLLECT_API_ENDPOINT

Prop

Type

These two variables are used only when building public/js/analytics.js. Rebuild the site after changing them.

Application secret

SAAS_SECRET

Prop

Type

The default value in example.vars is empty. npm run saavo:init generates it automatically when missing. You can also run npm run secret:generate to generate a new value.

SAAS_SECRET must remain unchanged over time

A project's .env and .env.production must use exactly the same SAAS_SECRET. Changing it after the project has created encrypted data can make existing data impossible to decrypt, including two-factor authentication secrets, affiliate payout details, and OAuth client secrets.

Cloudflare Turnstile

In config/deploy.ts, auth.useTurnstile defaults to { threshold: 2, interval: '1h' }, which triggers verification based on a threshold. When this configuration is enabled, both the site key and secret key are required. Set it to false to leave these two variables empty. You cannot use true in place of the configuration object.

CLOUDFLARE_TURNSTILE_SITE_KEY

Prop

Type

CLOUDFLARE_TURNSTILE_SECRET_KEY

Prop

Type

example.vars uses Cloudflare's official test credentials, which are suitable for local development. Before deploying to production, create a Turnstile widget for your production domain and replace them with real credentials.

Stripe

config/payment.ts selects Stripe by default. Checkout requires STRIPE_CONNECTION_ID and STRIPE_SECRET_KEY. Webhooks also require STRIPE_WEBHOOK_SECRET. Checkout cannot be created without the first two values, and Stripe webhooks cannot be received without the last one.

STRIPE_CONNECTION_ID

Prop

Type

Avoid changing this value once an environment is live. Otherwise, stored Stripe objects may be treated as belonging to another connection. Use different connection IDs for test and production environments.

STRIPE_SECRET_KEY

Prop

Type

STRIPE_WEBHOOK_SECRET

Prop

Type

Paddle

The following variables are included in the environment template and type declarations, but the project does not yet implement Paddle Checkout, the customer portal, or webhooks. Setting these variables alone does not enable Paddle payments.

PADDLE_CONNECTION_ID

Prop

Type

PADDLE_ENVIRONMENT

Prop

Type

PADDLE_API_KEY

Prop

Type

PADDLE_WEBHOOK_SECRET

Prop

Type

PADDLE_CLIENT_TOKEN

Prop

Type

Email

RESEND_API_KEY

Prop

Type

The template uses Resend by default, so this variable is required for production deployment. If it is missing locally, npm run doctor issues a warning. If it is missing in production, email sending is unavailable and remote checks fail.

If you change emailProvider.type to cloudflare, you do not need RESEND_API_KEY, but you must configure an EMAIL resource binding in wrangler.jsonc. See Cloudflare resource bindings.

OAuth sign-in

GitHub and Google sign-in each require both a client ID and a client secret. Leaving both values empty disables that sign-in method. If you set only one, the sign-in method remains unavailable and npm run doctor issues a warning.

GITHUB_CLIENT_ID

Prop

Type

GITHUB_CLIENT_SECRET

Prop

Type

GOOGLE_CLIENT_ID

Prop

Type

GOOGLE_CLIENT_SECRET

Prop

Type

R2 API credentials

example.vars labels these as R2 API credentials for signing template version downloads. The template retains the variables and type declarations, and npm run doctor checks that all three are set together. The existing application code does not yet read them. These API credentials are unrelated to the resource binding that lets the Worker access MAIN_R2 directly.

R2_ACCOUNT_ID

Prop

Type

R2_ACCESS_KEY_ID

Prop

Type

R2_SECRET_ACCESS_KEY

Prop

Type

Leave all three empty to keep them unconfigured. If you set only some of them, npm run doctor issues a warning.

External notification variables

External notification variables have no fixed names. Each *Binding field in config/notifications.ts specifies an environment variable name. The application reads the webhook, token, or signing secret from that variable. Choose names that suit your project. You do not need to use the example names in example.vars.

Store only variable names in the configuration file

config/notifications.ts must contain only binding names, never webhook URLs, bot tokens, chat IDs, signing secrets, or custom request headers directly.

Default template configuration

The template does not enable any external notification destinations by default. example.vars lists only example variables corresponding to the source comments in notifications.ts, all with empty-string defaults.

Notification serviceExample variable nameValue
SlackSLACK_OPERATIONS_WEBHOOK_URLIncoming Webhook URL
DiscordDISCORD_SALES_WEBHOOK_URLIncoming Webhook URL
DiscordDISCORD_SALES_THREAD_IDThread ID
TelegramTELEGRAM_ALERTS_BOT_TOKENBot Token
TelegramTELEGRAM_ALERTS_CHAT_IDChat ID
TelegramTELEGRAM_ALERTS_THREAD_IDMessage Thread ID
Microsoft TeamsTEAMS_OPERATIONS_WEBHOOK_URLWorkflow Webhook URL
Feishu or LarkFEISHU_RELEASE_WEBHOOK_URLCustom bot webhook URL
Feishu or LarkFEISHU_RELEASE_SIGNING_SECRETBot signing secret
DingTalkDINGTALK_RELEASE_WEBHOOK_URLCustom bot webhook URL
DingTalkDINGTALK_RELEASE_SIGNING_SECRETBot signing secret
WeComWECOM_RELEASE_WEBHOOK_URLGroup bot webhook URL
Generic webhookINTERNAL_AUDIT_WEBHOOK_URLHTTPS request URL
Generic webhookINTERNAL_AUDIT_WEBHOOK_HEADERSJSON object containing HTTP request headers

The value of INTERNAL_AUDIT_WEBHOOK_HEADERS is a JSON object string, for example:

INTERNAL_AUDIT_WEBHOOK_HEADERS='{"Authorization":"Bearer token"}'

Plain variables in wrangler.jsonc

The default names below come from the template source. When the CLI creates a project, it first replaces the saavo-template prefix with the project name.

These variables are defined directly in the vars object in wrangler.jsonc, not in example.vars. npm run deploy:init updates these resource names based on the Worker name. Do not duplicate them in .env.

ASYNC_POLICY_TASK_QUEUE_NAME

Prop

Type

ASYNC_LOGGER_QUEUE_NAME

Prop

Type

ANALYTICS_QUEUE_NAME

Prop

Type

R2_BUCKET_NAME

Prop

Type

Type declarations

Wrangler generates worker-configuration.d.ts to declare types for environment variables and Cloudflare resource bindings. After changing wrangler.jsonc, adding or removing fixed variables, or adjusting resource bindings, run:

npm run cf-typegen

This is a generated file. Do not edit it directly. External notifications use custom binding names, which are not declared individually as fixed fields in CloudflareBindings.