---
url: /getting-started/environment-variables.md
---
# Environment Variables

One page for every variable that shapes how an Evolve instance behaves per environment: what it does, what the default is, and who writes it. The rule behind all of them is simple. The environment type sets the defaults, single variables in the `.env` override them.

Three writers touch the `.env` of an instance:

| Writer | Writes |
|---|---|
| Deployment (`evolve:env:setup`, `deploy`) | the posture keys, from the declaration in `config/evolve.php` |
| Settings panel, developer role | posture, debug, banner, mail safety, preview protection, module switches |
| Settings panel, admin role | mail transport, integrations, secrets; public business settings go to the settings store instead, see below |
| By hand, on the server | anything, when no panel is reachable |

Public business values (tracking IDs and consent, verification codes, site name, branding, mail addresses, company identity) live in the database [settings store](/panel/settings-store). Since 2.18 the panel writes them there and no longer to the `.env`. A stored value wins over the `.env` variable of the same setting; the `.env` remains the fallback for existing sites. A new installation leaves these variables out of its `.env` and sets the values in the panel. The starter's `.env.example`  follows that rule: secrets, infrastructure, posture and module switches only, grouped and commented.

## Posture

Written by the deployment, editable by a developer in the panel (the type select writes both keys together). The environment consistency health check fails when they contradict each other.

| Variable | live | staging | development | Effect |
|---|---|---|---|---|
| `EVOLVE_ENV_TYPE` | `live` | `staging` | unset | Evolve's posture. Decides mail redirection, preview protection, noindex. When unset, inferred from `APP_ENV` (`production` is live, `staging` is staging, anything else is development). `testing` is read as `staging`. |
| `APP_ENV` | `production` | `staging` | `local` | Laravel's switch. Debug output, page cache eligibility, local only helpers. Test sites run `staging` because Laravel reads `testing` as "unit tests are running". |
| `APP_DEBUG` | `false` | `true` | `true` | Stack traces in the browser. `evolve:env:verify` fails when it is on for live. |
| `EVOLVE_ENV_BANNER` | `true` | `true` | `true` | The hint on every frontend page naming the environment, a small pill at the bottom centre that a visitor can shrink to a dot in the corner; only live never shows one. Set to `false` to hide it on a type that would. |

## Mail

| Variable | Default | Effect |
|---|---|---|
| `EVOLVE_MAIL_SAFETY` | unset | Overrides the type's mail default in both directions. `false` sends real mail from a staging site; `true` redirects on a live or development site. Unset means the type decides. A developer sets it in the Developers tab. |
| `EVOLVE_MAIL_REDIRECT` | `dev@racerfish.com` | Where redirected mail goes. Original recipients travel in the `X-Original-To` header. Empty on a protected environment means the message is cancelled and a warning is logged. |
| `MAIL_MAILER`, `MAIL_HOST`, `MAIL_PORT`, `MAIL_SCHEME`, `MAIL_USERNAME`, `MAIL_PASSWORD` | installer: `smtp` to `localhost:1025` | How mail is sent. Editable in the Mail tab of the panel, per instance. |
| `MAIL_FROM_NAME`, `MAIL_FROM_ADDRESS` | | Sender. Never a recipient since 2.18. Fallback for the sender fields of the Mail tab (settings store). |
| `EVOLVE_MAIL_NOTIFICATION_ADDRESS` | unset | Internal recipient of leads from forms without an admin address. Without it (and without a stored value) those notifications are skipped. Fallback for the Mail tab (2.18). |
| `EVOLVE_MAIL_REPLY_TO_ADDRESS` | unset | Reply-To of mails to visitors when the form sets none. Unset means the sender. Fallback for the Mail tab (2.18). |

Resulting behaviour per type, with defaults:

| | live | staging | development |
|---|---|---|---|
| Outgoing mail | real recipients | redirected | as configured, normally Mailpit |
| To catch mail instead | `EVOLVE_MAIL_SAFETY=true` | | `EVOLVE_MAIL_SAFETY=true` |
| To test real sending | | `EVOLVE_MAIL_SAFETY=false` | |
| To see content without sending to customers | | `EVOLVE_MAIL_REDIRECT=you@agency.ch` | |

More on the [Mail](/getting-started/mail) page.

## Preview protection and search engines

| Variable | Default | Effect |
|---|---|---|
| `EVOLVE_PROTECT_PASSWORD` | unset | On staging, HTTP basic auth on every page as soon as a password is set. Never active on live or development. The health check endpoint stays open. |
| `EVOLVE_PROTECT_USER` | `preview` | The basic auth user name. |

Independent of the password, staging always sends `X-Robots-Tag: noindex, nofollow` and render a `noindex` robots meta tag. Live and development do neither.

## Page cache

| Variable | Default | Effect |
|---|---|---|
| `APP_PAGE_CACHE` | `false` | Master switch, editable in the panel. Cached HTML is served by the web server before PHP runs. |

The cache only writes when all of these hold: `APP_PAGE_CACHE=true`, `APP_ENV` is `production` or `staging` (`evolve.page-cache.environments`), and no preview password is set. The last rule matters: a protected site cannot serve cached pages, because the web server would hand them out without the basic auth check. `evolve:env:verify` warns when the cache is on for a testing environment. Since 2.12 it also never writes a page rendered for a logged-in user or one that sets a cookie of its own, see [Page Cache](/site/page-cache#what-is-never-written).

## Tracking

Tracking tags only fire on live. Staging and development render the consent notice but load no tag manager, no Hotjar, and send no server side conversions, so test traffic never reaches the customer's properties. Since v2.11.

| Variable | Default | Effect |
|---|---|---|
| `TRACKING_EMIT` | unset | Overrides the type in both directions. `true` fires the tags on a staging site for a test run, `false` silences a live site. Unset means the type decides. A developer sets it in the settings panel. |
| `GTM_ID`, GA4 measurement id, pixel and tag ids | | Public ids. Since 2.18 the Tracking tab saves them to the settings store; the variables are the fallback. They may be identical on staging and live; the type decides whether they fire. |
| `TRACKING_CONSENT_VIA_CMP` | `false` | A consent platform in the tag manager grants consent, so opt-in without the Evolve banner is valid (2.18). Fallback for the Tracking tab. |
| `TRACKING_CMP` | unset | Consent platform Evolve loads and bridges into the Evolve consent cookie, `cookiebot` (2.19). Counts as a consent platform for opt-in. Fallback for the Tracking tab. |
| `COOKIEBOT_ID` | unset | Cookiebot domain group ID for `TRACKING_CMP=cookiebot` (2.19). Fallback for the Tracking tab. |

Once a tracking ID is stored in the [settings store](/panel/settings-store), the stored value wins over the variable. The health check's `tracking.sources` says which one a site uses.

The tracking health check fails on live when GTM or GA4 is missing or malformed, or when `TRACKING_EMIT=false` silences a live site, and warns when `TRACKING_EMIT=true` is left on a staging site. `HEALTH_CHECK_TRACKING=false` turns it off for a site without tracking on purpose.

## Security headers

Sent by the Evolve middleware on every web response since 2.12. Details on [Security Headers](/site/security-headers).

| Variable | Default | Effect |
|---|---|---|
| `EVOLVE_SECURITY_HEADERS` | `true` | `false` switches all Evolve security headers off. |
| `EVOLVE_FRAME_OPTIONS` | `SAMEORIGIN` | Value of `X-Frame-Options`. Empty for a site embedded in an iframe on another domain. |
| `EVOLVE_HSTS` | `max-age=31536000` | Value of `Strict-Transport-Security`, sent on live over https only. Empty disables it. |
| `EVOLVE_CSP` | unset | A Content-Security-Policy. Unset sends none. |
| `EVOLVE_CSP_REPORT_ONLY` | `false` | `true` sends the policy as `Content-Security-Policy-Report-Only`. Start here. |

Pages served from the static page cache bypass PHP; their headers come from the web server config.

## Form spam protection

See [Form Spam Protection](/panel/form-spam-protection).

| Variable | Default | Effect |
|---|---|---|
| `EVOLVE_HONEYPOT_THRESHOLD` | `3` | Seconds a form has to be open before a submission is accepted. Applies with and without reCAPTCHA. |
| `GOOGLE_RECAPTCHA_SITEKEY`, `GOOGLE_RECAPTCHA_SECRET` | unset | v3 or v2 Invisible keys. Set both or neither. Settings panel, Google tab. |
| `GOOGLE_RECAPTCHA_SCORE` | `0.5` | Minimum v3 score a submission needs. Settings panel, Google tab. |

## Queue

| Variable | Default | Effect |
|---|---|---|
| `QUEUE_CONNECTION` | Laravel's, `sync` in most existing projects | `sync` runs jobs inside the request. Any other connection (typically `database`) needs a worker; on Onlime a cron line, see [Deployment](/getting-started/deployment#cron-jobs). Since 2.12 it also decides whether image variants render in the request or in the worker ([Image Rendering](/frontend/image-rendering#queued-conversions)). |

## Modules

| Variable | Default | Effect |
|---|---|---|
| `EVOLVE_MODULE_<NAME>` | unset | Overrides the module's entry in `config/evolve.php` (`EVOLVE_MODULE_BLOG=false` switches the blog off), since 2.21 also for a module the file does not list. Written by the module switches in the Developers tab and, for every module, by `evolve:install`. |

## Deployment

Read on the developer machine, from the project `.env`, never on the server.

| Variable | Default | Effect |
|---|---|---|
| `DEPLOYMENT_DEFAULT` | `live` | Environment used when `php artisan deploy` gets no argument. |
| `DEPLOYMENT_USER`, `DEPLOYMENT_HOST`, `DEPLOYMENT_PATH`, `DEPLOYMENT_PHP_BIN` | | SSH user, host, base path and PHP binary on the server. |
| `DEPLOYMENT_TARGET` | `www` | Legacy single target, honoured when no `environments` are declared. |
| `DEPLOYMENT_SSH_MULTIPLEX`, `DEPLOYMENT_SSH_PERSIST` | `false`, `10m` | Reuse one SSH connection across the deploy steps. |

## Health

| Variable | Default | Effect |
|---|---|---|
| `HEALTH_CHECK_ENVIRONMENT` | `true` | Enables the environment consistency check. |
| `HEALTH_CHECK_MODULE_SWITCHES` | `true` | Enables the module switches check (2.21): modules without an explicit switch, a Brevo key with the email module off. |
| `HEALTH_CHECK_EMAIL_RECIPIENT`, `HEALTH_WEBMASTER_ADDRESS` | | Recipient of the daily mailer test message, and the alert address for failure notifications (also the fallback for the test message). Since 2.18 the alert address is edited on the Mail tab (settings store) and `HEALTH_WEBMASTER_ADDRESS` is its fallback. Never `MAIL_FROM_ADDRESS`. On staging both are subject to the mail redirect. |
| `EVOLVE_HEALTH_CHECK_TOKEN` | | Bearer token for the `api/health-check` system information endpoint that Fleet reads. Oh Dear uses `OH_DEAR_HEALTH_CHECK_SECRET` instead. |
| `HEALTH_CHECK_QUEUE` | unset | Unset (or empty) is auto-detect: the queue check runs when the queue driver is not `sync`. `true` or `false` pins it. The Health tab of the settings panel offers Auto, On and Off; Auto removes the key again (since 2.12). |

The full list of health toggles is on [Health Checks](/site/health-checks).

## Error tracking

Variables of `sentry/sentry-laravel`, read when the package is installed. Evolve tags events itself, see [Error Tracking](/site/error-tracking).

| Variable | Default | Effect |
|---|---|---|
| `SENTRY_LARAVEL_DSN` | unset | Where errors are sent. `evolve:env:verify` warns on live when neither this nor `SENTRY_DSN` is set (since 2.12). |
| `SENTRY_DSN` | unset | Legacy name, used when `SENTRY_LARAVEL_DSN` is not set. |
| `SENTRY_RELEASE` | unset | A release of your own. Unset, Evolve reports `evolve@<version>+<release directory>`. |

## Shopify

Only relevant with the Shopify module. The full list is on [Shopify](/modules/shopify#configuration).

| Variable | Default | Effect |
|---|---|---|
| `SHOPIFY_AUTO_IMPORT` | unset | Interval of the scheduled product and collection import (`hourly`, `daily`, ...). |
| `SHOPIFY_PURGE_DELETED_AFTER_DAYS` | unset | Since 2.12. Days a product must stay confirmed as deleted in Shopify before the daily purge removes it. Unset: no scheduled purge, `shopify:purge-deleted-products` runs by hand only (30 days). |

## Modules and integrations

Every module and integration documents its own keys on its page. This table is the index.

| Keys | Page |
|---|---|
| `EVOLVE_MODULE_<KEY>` | [Modules](/modules/) |
| `BLOG_*` | [Blog](/modules/blog) |
| `COMPANY_*`, `JOBS_OVERVIEW_PAGE`, `TEAM_OVERVIEW_PAGE`, `MEMBER_OVERVIEW_PAGE` | [Company](/modules/company) |
| `EVENT_*` | [Events](/modules/events) |
| `FAQ_OVERVIEW_PAGE` | [FAQ](/modules/faq) |
| `GOOGLE_MAPS_API_KEY`, `GOOGLE_GEOCODING_API_KEY` | [Locator](/modules/locator) |
| `PROJECT_*` | [Projects](/modules/projects) |
| `SCOUT24_*`, `HEALTH_CHECK_SCOUT24` | [Scout24](/modules/scout24) |
| `SHOPIFY_*`, `HEALTH_CHECK_SHOPIFY` | [Shopify](/modules/shopify) |
| `EMAIL_BRAND_COLOR`, `EMAIL_CONTAINER_WIDTH`, `EMAIL_ASSET_BASE` | [Mail templates](/panel/mail-templates#look-of-the-mails) (core since 2.22) |
| other `EMAIL_*`, `BREVO_API_KEY` | [Email Campaigns](/modules/email-campaigns), [Brevo](/integrations/brevo) |
| `MCP_*` | [MCP Server](/modules/mcp-server) |
| `CHATBOT_*` | [AI Chatbot](/modules/ai-chatbot) |
| `EVOLVE_MODULE_REGION`, `EVOLVE_REGION_REMOVE_DEFAULT_PREFIX` | [Regions](/modules/regions) |
| `OPENAI_*`, `LLM_PROVIDER` | [Editor AI](/panel/editor-ai), [OpenAI](/integrations/openai) |
| `GTM_ID`, `ANALYTICS_*`, `FACEBOOK_*`, `PINTEREST_TAG_ID`, `LINKEDIN_*`, `HOTJAR_SITE_ID`, `TRACKING_*`, `APP_COOKIE_CONSENT` | [Tracking and Consent](/site/tracking-and-consent) |
| `GOOGLE_SITE_VERIFICATION`, `BING_SITE_VERIFICATION`, `PINTEREST_SITE_VERIFICATION` | [SEO and Head](/site/seo-and-head) |
| `GOOGLE_RECAPTCHA_*`, `EVOLVE_HONEYPOT_THRESHOLD` | [Form Spam Protection](/panel/form-spam-protection) |
| `VIMEO_*`, `VIDEO_DRIVER`, `VIDEO_RESOLVER_URL` | [Vimeo](/integrations/vimeo) |
| `IMAGE_DRIVER`, `IMAGE_ALLOW_IDS`, `IMAGE_RECOGNITION` | [Media library](/panel/media-library), [Image Rendering](/frontend/image-rendering) |
| `HEALTH_*`, `OH_DEAR_HEALTH_CHECK_SECRET`, `SCHEDULE_HEARTBEAT_URL`, `EVOLVE_HEALTH_CHECK_TOKEN` | [Health Checks](/site/health-checks) |
| `SENTRY_*` | [Error Tracking](/site/error-tracking) |

## Other keys

Keys the package reads that belong to no feature page.

| Variable | Default | Effect |
|---|---|---|
| `APP_EVOLVE_LOCALE` | `de` | Default language of the panel. Users switch their own in the profile. |
| `APP_LOGIN_IMAGE_ID`, `APP_BACKEND_THEME_COLOR` | unset, `#E78401` | Panel branding: the login screen image (a media ID) and the brand colour on the logo and loaders. Since 2.18 Settings > General saves them to the settings store group `branding`; the variables are the fallback. |
| `APP_INFO_BAR` | `true` | The info bar on the frontend for logged-in panel users (edit links, cache button). |
| `APP_AUTO_TRANSLATE` | `false` | The auto-translate button in block fields, through Google Cloud Translation. Needs `GOOGLE_APPLICATION_CREDENTIALS`. |
| `ANALYTICS_PROPERTY_ID`, `GOOGLE_APPLICATION_CREDENTIALS` | unset | Page views on the dashboard and in the page list, read from the GA4 Data API with a service account JSON file. The path is absolute or relative to the project root; the same file serves Cloud Vision and Cloud Translation. The package ships no service account : without the file the dashboard shows a notice instead of loading, and image recognition and auto-translate fail with a clear message. Create the account in Google Cloud with the Analytics Data API enabled and add its email as a viewer of the GA4 property. |
| `ACTIVITY_LOGGER_ENABLED`, `ACTIVITY_LOGGER_DB_CONNECTION` | `true`, unset | The activity log (spatie/laravel-activitylog). Off, the Activity screen and the health check's `content` block go empty. |
| `IPSTACK_API_ACCESS_KEY`, `IPSTACK_TEST_IP` | unset | IP geolocation through ipstack, used by `IpGeoLocator` to place a visitor in a country. |
| `SLACK_WEBHOOK` | unset | Deploy notifications (release deployed, release rolled back) to a Slack channel. |
| `REALFAVICONGENERATOR_API_KEY` | a shared key | The favicon generator in the panel. Override when the shared key is rate-limited. |
| `GOOGLE_PAGESPEED_KEY` | unset | Reserved; nothing reads it in 2.16. |
| `STRIPE_API_SECRET`, `STRIPE_API_PUBLIC` | unset | Not read by Evolve. Present in `services.php` for projects that use Laravel Cashier. |
| `GOOGLE_TRACKING_IMPLEMENTATION`, `ANALYTICS_ANONYMIZE_IP` | `gtag`, `false` | Deprecated with the GTM path. Kept for published configs; only the legacy globals read them. |

## Checking an environment

```bash
php artisan evolve:env:verify staging   # posture keys against the declaration
php artisan evolve:env:diff             # key names across local and remote .env files
```

The Health tab of the panel shows the same posture verdict at runtime, so a wrong server `.env` is visible without a terminal.
