Environment Variables#

All environment variables are configured in the root .env file.

bashcp .env.example .env

Backend Variables (Server Only)#

Basic#

VariableDescriptionExample
NODE_ENVRuntime environmentdevelopment / production

Database#

VariableDescriptionExample
POSTGRES_CONNECTION_STRINGPostgreSQL connection stringpostgresql://admin:admin@localhost:5432/stackflare

JWT#

VariableDescriptionExample
JWT_SECRETJWT signing secretyour-secret-key
JWT_EXPIRES_INToken expiration365d

API Authentication#

VariableDescriptionExample
API_KEYSecret for machine-to-machine calls to protected endpoints, sent via the x-api-key header (e.g. external cron calling /v1/cron/free-packages)your_random_api_key

Email#

Switch the sending service via MAIL_PROVIDER (defaults to ses).

VariableDescriptionExample
MAIL_SEND_ENABLEDEnable email sending. When false, emails are not sent and the verification code is printed to the server console log (grab it from the logs during development)true / false
MAIL_PROVIDERProvider: ses (AWS SES) / cloudflare (Cloudflare Email)cloudflare
MAIL_FROM_NAMESender nameYour App Name
MAIL_FROM_ADDRESSSender emailno-reply@example.com
  • MAIL_PROVIDER=cloudflare: requires CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_API_TOKEN below.
  • MAIL_PROVIDER=ses: requires the AWS credentials below (AWS_REGION / AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY).

Cloudflare (shared by Email / R2)#

VariableDescription
CLOUDFLARE_ACCOUNT_IDCloudflare account ID (needed for both Cloudflare email sending and R2 storage)
CLOUDFLARE_API_TOKENCloudflare API token (used for sending when MAIL_PROVIDER=cloudflare)

AWS (SES email / S3 storage)#

VariableDescriptionExample
AWS_REGIONAWS regionus-east-1
AWS_ACCESS_KEY_IDAWS access key (can be left empty on EC2/ECS with an IAM Role)
AWS_SECRET_ACCESS_KEYAWS secret key
S3_BUCKET_NAMES3 bucket name (when using S3 storage)your_s3_bucket

Cloudflare R2 Storage#

VariableDescription
R2_ACCESS_KEY_IDR2 access key
R2_SECRET_ACCESS_KEYR2 secret key
R2_BUCKET_NAMEBucket name

Object storage supports R2 and S3 at the same time: setting R2_ACCESS_KEY_ID enables R2, setting S3_BUCKET_NAME enables S3. When no provider is specified, R2 is used by default (e.g. invoice/receipt PDFs are auto-uploaded to R2); code can also pass a provider to pick R2 or S3 per upload.

R2 Bucket CORS Configuration (Required)#

Browser-to-R2 direct uploads (presigned PUT and multipart) require CORS to be configured on the R2 bucket — otherwise the browser blocks the request, or multipart fails because ETag headers can't be read.

Go to Cloudflare Dashboard → R2 → your bucket → Settings → CORS Policy and add:

json[
  {
    "AllowedOrigins": ["http://localhost:3000", "https://yourdomain.com"],
    "AllowedMethods": ["GET", "PUT", "HEAD"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": ["*"]
  }
]

Notes:

  • AllowedOrigins: list every origin that will upload (dev + production)
  • AllowedMethods must list methods explicitly (S3/R2 CORS does not accept * wildcard). PUT for upload; GET/HEAD for fetch / <img crossOrigin> preflight
  • ExposeHeaders: ["*"] is simplest, otherwise must include "ETag" or multipart complete will fail because the client can't read part ETags

Stripe#

VariableDescription
STRIPE_SECRET_KEYStripe API key
STRIPE_WEBHOOK_SECRET_KEYWebhook signing secret

Cloudflare Turnstile#

VariableDescription
TURNSTILE_VERIFY_URLVerification endpoint
TURNSTILE_SECRET_KEYServer-side secret

Frontend Variables (PUBLIC_ prefix)#

Exposed to the browser. Do not put sensitive data here.

VariableDescriptionExample
PUBLIC_APP_URLApp URLhttp://localhost:3000
PUBLIC_TITLEApp nameMy SaaS
PUBLIC_GOOGLE_CLIENT_IDGoogle OAuth Client ID446xxx.apps.googleusercontent.com
PUBLIC_TURNSTILETurnstile site key0x4AAAxxx
PUBLIC_DEFAULT_LANGDefault languageen
PUBLIC_CLARITY_PROJECT_IDMicrosoft Clarity project ID, leave empty to disableabc123
PUBLIC_GOOGLE_ANALYTICS_IDGoogle Analytics ID, leave empty to disableG-XXXXXXXXXX