Skip to content

Need it customized?

Custom features, theme changes, server setup, migrations and upgrades - built by the Botble team. Quick fixes from $99, fixed quote within 1 business day.

Get a free quote →

Environment reference ​

Every configurable setting is driven by environment variables in .env. This page lists all of them, grouped by function. Defaults are read from .env.example and the source config files.

Required variables ​

These must be set before the platform starts.

VariableDefaultWhat it does
APP_KEY(empty)Encryption key for the application. Left empty on purpose — the browser installer generates it on the first request to /install and writes it to .env (which must be writable). On the CLI path, generate it yourself with php artisan key:generate
APP_URLhttp://localhostThe public URL of your control-plane domain (e.g., https://saas.yourdomain.com)
APP_ENVproductionSet to production on live; local or development for testing
CENTRAL_DOMAINSyour-domain.comControl-plane host(s), comma-separated. Never a store's host
DB_HOST127.0.0.1MySQL server hostname or IP
DB_PORT3306MySQL port
DB_DATABASElaravelCentral database name (e.g., saas_central)
DB_USERNAMErootMySQL user (must have GRANT CREATE, DROP ON *.*)
DB_PASSWORDyour_db_passwordMySQL password
CACHE_STOREfileCache store for the control plane and all stores. Shipped as file so the installer runs before a database exists; switch to database or redis after install. file, database, redis, or memcached — see below
QUEUE_CONNECTIONsyncShipped as sync so the wizard completes with no worker running. Switch to database or redis (with a worker) before opening signups — tenancy:preflight flags a sync queue

APP_KEY and DB_PASSWORD

Never share these. Store them in a secret manager, not in version control or logs.

Redis is optional — the platform works with file, database, redis, or memcached. CACHE_STORE=file is the shipped install-time default (it's the only store that works before the database exists). Once installed, use CACHE_STORE=database for small installs without Redis, or set CACHE_STORE=redis once you have real traffic. If you use Redis, configure it carefully:

VariableDefaultWhat it does
REDIS_CLIENTpredispredis (pure PHP) or phpredis (faster, needs PHP extension)
REDIS_HOST127.0.0.1Redis server hostname or IP
REDIS_PORT6379Redis port
REDIS_PASSWORDnullRedis auth password (if required by your Redis instance)
REDIS_CACHE_DB1Cache db index — must differ from queue db (default is db 0)

Keep cache and queue on separate db indexes (Redis only)

If both point to db 0, Clear cache (via FLUSHDB) deletes all queued jobs for all stores. Use db 0 for queue and db 1 for cache.

Multi-tenancy ​

VariableDefaultWhat it does
TENANCY_DB_PREFIXtenant_Prefix for store database names (e.g., tenant_abc123, tenant_def456)
TENANCY_DEFAULT_THEMEamerceTheme preset on signup if none is chosen
TENANCY_MAX_CUSTOM_DOMAINS5Max custom domains per store (plan can lower this)
SESSION_DOMAIN(empty)MUST stay empty — a cookie scoped to parent domain leaks sessions between stores
SESSION_DRIVERfileWhere sessions are stored. Shipped as file so the installer works before the database exists; database is recommended after install
SESSION_LIFETIME120Minutes until a session expires (2 hours)
ADMIN_DIRadminURL path for the operator console and store admin (e.g., /admin or /saas-admin)

SESSION_DOMAIN must be empty

A cookie scoped to .yourdomain.com is sent to every store subdomain (store1.yourdomain.com, store2.yourdomain.com). One store's session cookie is then valid on another, allowing cross-store impersonation. Always leave this empty.

Provisioning ​

VariableDefaultWhat it does
TENANCY_PROVISION_QUEUEdefaultQueue name for provisioning jobs (usually default)
TENANCY_PROVISION_TIMEOUT900Provisioning job timeout in seconds (15 minutes)
TENANCY_PROVISION_SYNCfalseIf true, provision stores synchronously (no queue) — for testing only

Note: provisioning.tries is hard-coded to 1 in the source; there is no env var for it.

Signup and landing page ​

VariableDefaultWhat it does
TENANCY_LANDING_ENABLEDtrueShow the marketing/landing page at the root of your central domain
TENANCY_LANDING_MIN_STORES10Min live stores before the stats section appears (avoids advertising "3 stores")
TENANCY_SIGNUP_VERIFY_EMAILtrueRequire email verification before a new store is created
TENANCY_SIGNUP_VERIFY_TTL_HOURS24Hours until a verification email link expires
TENANCY_SIGNUP_CAPTCHAtrueRequire CAPTCHA on the signup form
TENANCY_SIGNUP_PER_IP_PER_HOUR3Max signups per IP per hour (rate-limit)
TENANCY_SIGNUP_GLOBAL_PER_HOUR60Max signups globally per hour (platform-wide rate-limit)
TENANCY_SIGNUP_BLOCKED_EMAIL_DOMAINS(empty)Comma-separated domains to block at signup (e.g., tempmail.com,guerrillamail.com)
TENANCY_SHOW_POWERED_BYfalseShow a "Powered by Botble" footer link

Billing and subscriptions ​

VariableDefaultWhat it does
TENANCY_PLATFORM_CURRENCY(empty)3-letter currency code for billing (e.g., USD, EUR, VND)
TENANCY_BILLING_GRACE_DAYS14Days of grace after a payment fails before the store is suspended
TENANCY_RETENTION_DAYS30Days to retain a cancelled store before deleting it and its database
TENANCY_RETENTION_WARNING_DAYS7Days before retention expires to send a final warning
TENANCY_TRIAL_REMINDER_DAYS7,3,1Days before trial ends to send reminder emails (comma-separated)
TENANCY_ALLOW_PROMOTION_CODEStrueAllow store owners to apply coupon/promotion codes
TENANCY_COUPONS_ENABLEDtrueEnable the coupons feature in the operator console

Custom domains ​

VariableDefaultWhat it does
TENANCY_CNAME_HOST(empty)Default hostname customers point their CNAME at (defaults to CENTRAL_DOMAINS if empty)

Example: if you set TENANCY_CNAME_HOST=cname.yourdomain.com, store owners point their domain's CNAME to cname.yourdomain.com instead of your server's IP.

Control-plane API ​

VariableDefaultWhat it does
TENANCY_API_ENABLEDtrueEnable the REST API at /api/platform/v1 (central domain only)
TENANCY_API_RATE_LIMIT120Requests per minute per API key
TENANCY_API_RATE_LIMIT_PER_IP600Requests per minute per IP across all keys (0 = disabled)

Webhooks ​

VariableDefaultWhat it does
TENANCY_WEBHOOK_TIMEOUT10Seconds to wait for a webhook endpoint to respond
TENANCY_WEBHOOK_CONNECT_TIMEOUT5Seconds to wait opening the connection (on top of TENANCY_WEBHOOK_TIMEOUT)
TENANCY_WEBHOOK_QUEUE(empty)Queue name for first webhook delivery attempt (empty = default queue)
TENANCY_WEBHOOK_DISABLE_AFTER25Consecutive failures after which an endpoint is auto-disabled
TENANCY_WEBHOOKS_RETENTION_DAYS30Days to keep delivered webhooks in the database (0 = forever)
TENANCY_WEBHOOKS_ALLOW_PRIVATE_HOSTSfalseAllow webhook endpoints on private/reserved addresses (dev only)

Webhook delivery needs cron

php artisan tenancy:webhooks-deliver must run every minute via cron or a cron job, or webhook retries never fire. See Queue worker and cron.

Billing — Stripe (optional) ​

Leave these empty to run without Stripe. Stores sign up, trial, and convert without payment if you set nothing.

VariableDefaultWhat it does
STRIPE_KEY(empty, commented)Stripe public key (starts with pk_)
STRIPE_SECRET(empty, commented)Stripe secret key (starts with sk_)
STRIPE_WEBHOOK_SECRET(empty, commented)Stripe webhook signing secret (for /stripe/webhook)
CASHIER_CURRENCYusd3-letter currency for Stripe (e.g., usd, eur, gbp)

See Billing with Stripe for setup instructions.

Operator seed ​

The operator admin account seeded at install. These variables are not in .env.example but are read by the seeder if present.

VariableDefaultWhat it does
OPERATOR_ADMIN_EMAILoperator@ + APP_URL's hostEmail address of the seeded operator
OPERATOR_ADMIN_USERNAMEoperatorLogin username for the seeded operator
OPERATOR_ADMIN_PASSWORDgenerated per runInitial password. Left unset, a 16-character password is generated and printed once

No published default password

Neither default is a fixed value shipped to every install. The email derives from your own APP_URL host, and an unset password is generated per run and printed once in the seeder's output. Set these variables only if you want to choose them yourself.

The browser installer does not use this seeder at all — it takes a real name, email and password from you during setup. These variables are the CLI path.

Variables you must NOT set ​

VariableWhy not
SESSION_DOMAINA cookie scoped to the parent domain leaks sessions between stores — leave it empty
LOG_CHANNEL=stackCan cause file handle exhaustion when logs pile up across many stores; use single or daily instead

Example .env for production ​

.env.example ships CACHE_STORE=file, SESSION_DRIVER=file and QUEUE_CONNECTION=sync so the installer runs before a database or queue worker exists. The block below is the recommended post-install production configuration — switch to it once the wizard has finished and (for the queue) a worker is running.

env
APP_NAME="My Platform"
APP_KEY=base64:…
APP_ENV=production
APP_DEBUG=false
APP_URL=https://saas.yourdomain.com

CACHE_STORE=redis
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_CACHE_DB=1
REDIS_PORT=6379

DB_CONNECTION=central
DB_HOST=127.0.0.1
DB_DATABASE=saas_central
DB_USERNAME=app_user
DB_PASSWORD=<secure>

CENTRAL_DOMAINS=yourdomain.com
TENANCY_DB_PREFIX=tenant_
SESSION_DOMAIN=
ADMIN_DIR=admin

STRIPE_KEY=pk_live_…
STRIPE_SECRET=sk_live_…
STRIPE_WEBHOOK_SECRET=whsec_…
CASHIER_CURRENCY=usd

TENANCY_API_ENABLED=true
TENANCY_WEBHOOK_TIMEOUT=10

OPERATOR_ADMIN_EMAIL=you@yourdomain.com
OPERATOR_ADMIN_USERNAME=yourusername
OPERATOR_ADMIN_PASSWORD=<random>