# Environment variables

Source: https://docs.qiick.io/externa/environment-variables

Externa reads configuration from `.env` (copied from `.env.example` during `composer setup`). Most keys map into `config/*.php` via `env()`. This page groups the variables you will actually set for externa-core and documents defaults.

> **Note**
> After changing env values that are cached in production, run `php artisan config:clear` or rebuild config cache. Locally, `composer test` already clears config before Pest runs.

## App

Standard Laravel application identity and runtime flags from `.env.example`:

| Variable                   | Default (example)                          | Purpose                                                                                                                                                                                                                                                                                                                     |
| -------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `APP_NAME`                 | `Laravel`                                  | Application name. Used as mail from-name interpolation and as the fallback display name when Project settings → name is empty. Transactional mail branding (logo/color/header) comes from Appearance / Project settings — see [Project settings](https://docs.qiick.io/externa/project-settings#transactional-email-branding).                   |
| `APP_ENV`                  | `local`                                    | Environment name (`local`, `production`, …).                                                                                                                                                                                                                                                                                |
| `APP_KEY`                  | _(empty until generated)_                  | Encryption key; `composer setup` runs `key:generate`.                                                                                                                                                                                                                                                                       |
| `APP_DEBUG`                | `false` (1.4.0 `.env.example`; was `true`) | Detailed errors when true — **keep `false` in production**; turn on only while actively debugging.                                                                                                                                                                                                                          |
| `APP_URL`                  | `http://localhost`                         | Canonical URL (scheme + host + port). Must match how you browse. App (password, TOTP, CMS) works on HTTP; **passkeys** need a secure context (HTTPS, or `http://localhost` / `*.localhost` — not `http://*.test`). Drives Fortify RP ID / `allowed_origins`. See [Passkeys — Requirements](https://docs.qiick.io/externa/passkeys#requirements). |
| `APP_LOCALE`               | `en`                                       | Default locale.                                                                                                                                                                                                                                                                                                             |
| `APP_FALLBACK_LOCALE`      | `en`                                       | Fallback locale.                                                                                                                                                                                                                                                                                                            |
| `APP_FAKER_LOCALE`         | `en_US`                                    | Faker locale for factories/seed demos.                                                                                                                                                                                                                                                                                      |
| `APP_MAINTENANCE_DRIVER`   | `file`                                     | Maintenance mode driver.                                                                                                                                                                                                                                                                                                    |
| `APP_MAINTENANCE_STORE`    | _(commented)_                              | Optional store when using database maintenance driver.                                                                                                                                                                                                                                                                      |
| `BCRYPT_ROUNDS`            | `12`                                       | Password hashing rounds.                                                                                                                                                                                                                                                                                                    |
| `LOG_CHANNEL`              | `stack`                                    | Default log channel.                                                                                                                                                                                                                                                                                                        |
| `LOG_STACK`                | `single`                                   | Channels in the stack.                                                                                                                                                                                                                                                                                                      |
| `LOG_DEPRECATIONS_CHANNEL` | `null`                                     | Deprecation log target.                                                                                                                                                                                                                                                                                                     |
| `LOG_LEVEL`                | `debug`                                    | Minimum log level.                                                                                                                                                                                                                                                                                                          |
| `VITE_APP_NAME`            | `"${APP_NAME}"`                            | Exposed to the Vite/React build as the app name.                                                                                                                                                                                                                                                                            |

```env
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=false
APP_URL=http://localhost

APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US

APP_MAINTENANCE_DRIVER=file
BCRYPT_ROUNDS=12

LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

VITE_APP_NAME="${APP_NAME}"
```

## Database, session, cache, and queue

Externa’s `.env.example` defaults to **SQLite**, **database** sessions, and **Redis** for queue + cache (full stack). For light local without Redis, set `QUEUE_CONNECTION=database` and `CACHE_STORE=database`. Supported engines and versions: [Supported databases](https://docs.qiick.io/externa/supported-databases).

| Variable               | Default                       | Purpose                                                                                                         |
| ---------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `DB_CONNECTION`        | `sqlite`                      | Driver: `sqlite` \| `pgsql` \| `mysql` \| `mariadb`. See [Supported databases](https://docs.qiick.io/externa/supported-databases).   |
| `DB_HOST`              | `127.0.0.1` (commented)       | Host for MySQL/Postgres.                                                                                        |
| `DB_PORT`              | `3306` (commented)            | Port for non-SQLite drivers.                                                                                    |
| `DB_DATABASE`          | `laravel` (commented)         | Database name / path depending on driver.                                                                       |
| `DB_USERNAME`          | `root` (commented)            | DB user.                                                                                                        |
| `DB_PASSWORD`          | _(empty, commented)_          | DB password.                                                                                                    |
| `SESSION_DRIVER`       | `database`                    | Session storage.                                                                                                |
| `SESSION_LIFETIME`     | `120`                         | Minutes.                                                                                                        |
| `SESSION_ENCRYPT`      | `false`                       | Encrypt session payload.                                                                                        |
| `SESSION_PATH`         | `/`                           | Cookie path.                                                                                                    |
| `SESSION_DOMAIN`       | `null`                        | Cookie domain.                                                                                                  |
| `QUEUE_CONNECTION`     | `redis` (fallback `database`) | Full stack uses Redis + [Horizon](https://docs.qiick.io/externa/horizon). Minimal: `database` + `queue:listen`.                      |
| `CACHE_STORE`          | `redis`                       | Prefer Redis for app / public API cache; `database` is fine for light local.                                    |
| `CACHE_PREFIX`         | _(commented)_                 | Optional key prefix.                                                                                            |
| `BROADCAST_CONNECTION` | `reverb` (fallback `log`)     | Full: [Reverb](https://docs.qiick.io/externa/reverb-and-echo). Minimal: `log` (60s notification poll **only when realtime is off**). |
| `FILESYSTEM_DISK`      | `local`                       | Framework default disk (private/local).                                                                         |
| `FILES_DISK`           | `assets`                      | File manager disk (`assets` or `s3`). See [Files configuration](https://docs.qiick.io/externa/files-configuration).                  |

```env
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=

SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null

# Full stack defaults in .env.example — switch to database/log for minimal:
BROADCAST_CONNECTION=reverb
FILESYSTEM_DISK=local
QUEUE_CONNECTION=redis

CACHE_STORE=redis
# CACHE_STORE=database  # ok for light local without Redis cache
```

### Redis (Herd / full stack)

Required for Horizon, recommended for app cache / public API performance, plus Reverb-friendly ops and Pulse redis ingest — see [Redis prerequisite](https://docs.qiick.io/externa/redis-prerequisite).

| Variable         | Default                            |
| ---------------- | ---------------------------------- |
| `REDIS_CLIENT`   | `phpredis`                         |
| `REDIS_HOST`     | `127.0.0.1`                        |
| `REDIS_PASSWORD` | `null`                             |
| `REDIS_PORT`     | `6379`                             |
| `REDIS_CACHE_DB` | `1` (app cache; keep off queue DB) |
| `REDIS_PULSE_DB` | `2` (optional; connection `pulse`) |
| `MEMCACHED_HOST` | `127.0.0.1`                        |

### Reverb (Herd Pro defaults)

Match **Herd → Services → Reverb** (`:8080`). Host for Echo must be `127.0.0.1` or `localhost` (not `0.0.0.0`). Restart Vite after changing `VITE_REVERB_*`.

| Variable              | Default               | Notes               |
| --------------------- | --------------------- | ------------------- |
| `REVERB_APP_ID`       | `1001`                | Herd shared app id  |
| `REVERB_APP_KEY`      | `laravel-herd`        | Shared with Vite    |
| `REVERB_APP_SECRET`   | `secret`              | Server secret       |
| `REVERB_HOST`         | `127.0.0.1`           | WS host for clients |
| `REVERB_PORT`         | `8080`                | Herd Reverb port    |
| `REVERB_SCHEME`       | `http`                | `http` / `https`    |
| `VITE_REVERB_APP_KEY` | `"${REVERB_APP_KEY}"` | Echo key            |
| `VITE_REVERB_HOST`    | `"${REVERB_HOST}"`    | Echo host           |
| `VITE_REVERB_PORT`    | `"${REVERB_PORT}"`    | Echo port           |
| `VITE_REVERB_SCHEME`  | `"${REVERB_SCHEME}"`  | Echo TLS flag       |

Standalone (no Herd Reverb): set your own `REVERB_APP_*` and run `php artisan reverb:start`.

```env
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=1001
REVERB_APP_KEY=laravel-herd
REVERB_APP_SECRET=secret
REVERB_HOST=127.0.0.1
REVERB_PORT=8080
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
```

### Pulse

| Variable                 | Default  | Notes                             |
| ------------------------ | -------- | --------------------------------- |
| `PULSE_ENABLED`          | `true`   | Master switch                     |
| `PULSE_INGEST_DRIVER`    | `redis`  | Or `storage` without Redis worker |
| `PULSE_REDIS_CONNECTION` | `pulse`  | Dedicated Redis connection        |
| `PULSE_STORAGE_KEEP`     | `2 days` | Trim window                       |
| `PULSE_INGEST_KEEP`      | `2 days` | Ingest trim                       |

> With **Herd Reverb** already on `:8080`, full local ops need `horizon` + `pulse:work` (do not also `reverb:start`). Standalone: `composer run dev:full` starts all three. Minimal: `composer run dev` + `queue:listen`. See [Minimal vs full stack](https://docs.qiick.io/externa/minimal-vs-full-stack).

## Mail

Local default is the `log` mailer (messages go to the log, not SMTP).

| Variable            | Default             | Purpose                                 |
| ------------------- | ------------------- | --------------------------------------- |
| `MAIL_MAILER`       | `log`               | Mail transport.                         |
| `MAIL_SCHEME`       | `null`              | Scheme when using SMTP-like transports. |
| `MAIL_HOST`         | `127.0.0.1`         | SMTP host.                              |
| `MAIL_PORT`         | `2525`              | SMTP port.                              |
| `MAIL_USERNAME`     | `null`              | SMTP user.                              |
| `MAIL_PASSWORD`     | `null`              | SMTP password.                          |
| `MAIL_FROM_ADDRESS` | `hello@example.com` | From address.                           |
| `MAIL_FROM_NAME`    | `"${APP_NAME}"`     | From display name.                      |

Logo/color/header branding for password reset and similar mail is **not** controlled by `MAIL_*` — see [Project settings → Transactional email branding](https://docs.qiick.io/externa/project-settings#transactional-email-branding). Ensure logo files are publicly reachable (`FILE_PUBLIC_URL_BASE` / assets disk / CDN). Production provider `.env` examples: [Mail providers](https://docs.qiick.io/externa/mail-providers).

```env
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
```

## AWS / S3 / MinIO (optional)

For file-manager object storage set `FILES_DISK=s3` (keep `FILESYSTEM_DISK=local` unless you intentionally move everything):

| Variable                      | Default     | Notes                          |
| ----------------------------- | ----------- | ------------------------------ |
| `AWS_ACCESS_KEY_ID`           | _(empty)_   |                                |
| `AWS_SECRET_ACCESS_KEY`       | _(empty)_   |                                |
| `AWS_DEFAULT_REGION`          | `us-east-1` |                                |
| `AWS_BUCKET`                  | _(empty)_   |                                |
| `AWS_URL`                     | _(empty)_   | Public/base URL for the bucket |
| `AWS_ENDPOINT`                | _(empty)_   | MinIO/R2 endpoint              |
| `AWS_USE_PATH_STYLE_ENDPOINT` | `false`     | `true` for MinIO               |

```env
FILES_DISK=s3
AWS_ENDPOINT=http://minio:9000
AWS_USE_PATH_STYLE_ENDPOINT=true
```

Compose MinIO profile: [Installation — Docker](https://docs.qiick.io/externa/installation#docker-compose-local-full-stack).

## Google Cloud Storage (optional, 1.4.0)

For `FILES_DISK=gcs` (`config/filesystems.php` → `disks.gcs`). Details: [Files & storage — Google Cloud Storage](https://docs.qiick.io/externa/files-configuration#google-cloud-storage-core-1-4-0).

| Variable                            | Default   | Notes                                                                                                 |
| ----------------------------------- | --------- | ----------------------------------------------------------------------------------------------------- |
| `GOOGLE_CLOUD_PROJECT_ID`           | _(empty)_ | GCP project id                                                                                        |
| `GOOGLE_CLOUD_KEY_FILE`             | _(empty)_ | Service-account JSON path; empty = Application Default Credentials / `GOOGLE_APPLICATION_CREDENTIALS` |
| `GOOGLE_CLOUD_STORAGE_BUCKET`       | _(empty)_ | Bucket name                                                                                           |
| `GOOGLE_CLOUD_STORAGE_PATH_PREFIX`  | _(empty)_ | Optional key prefix                                                                                   |
| `GOOGLE_CLOUD_STORAGE_API_URI`      | _(empty)_ | Optional public URI base                                                                              |
| `GOOGLE_CLOUD_STORAGE_API_ENDPOINT` | _(empty)_ | Optional API endpoint (emulators)                                                                     |
| `GOOGLE_CLOUD_STORAGE_VISIBILITY`   | `private` | Default object visibility                                                                             |

```env
FILES_DISK=gcs
GOOGLE_CLOUD_PROJECT_ID=
GOOGLE_CLOUD_KEY_FILE=
GOOGLE_CLOUD_STORAGE_BUCKET=
# GOOGLE_CLOUD_STORAGE_PATH_PREFIX=
# GOOGLE_CLOUD_STORAGE_API_URI=
# GOOGLE_CLOUD_STORAGE_API_ENDPOINT=
# GOOGLE_CLOUD_STORAGE_VISIBILITY=private
```

## Azure Blob Storage (optional, 1.4.0)

For `FILES_DISK=azure` (`config/filesystems.php` → `disks.azure`). Details: [Files & storage — Azure Blob Storage](https://docs.qiick.io/externa/files-configuration#azure-blob-storage-core-1-4-0).

| Variable                          | Default   | Notes                                                                                  |
| --------------------------------- | --------- | -------------------------------------------------------------------------------------- |
| `AZURE_STORAGE_CONNECTION_STRING` | _(empty)_ | Shared-key connection string                                                           |
| `AZURE_STORAGE_CONTAINER`         | `externa` | Container name                                                                         |
| `AZURE_STORAGE_PREFIX`            | _(empty)_ | Optional blob prefix                                                                   |
| `AZURE_STORAGE_URL`               | _(empty)_ | Optional public base URL; its origin is added to the admin CSP `img-src` / `media-src` |

```env
FILES_DISK=azure
AZURE_STORAGE_CONNECTION_STRING=
AZURE_STORAGE_CONTAINER=externa
# AZURE_STORAGE_PREFIX=
# AZURE_STORAGE_URL=
```

## Docker Compose knobs

Used by `compose.yaml` / samples (`.env.docker.example`, `.env.docker.prod.example`). Compose `environment:` entries override host Herd `.env` values **inside containers** so `DB_HOST=127.0.0.1` cannot poison the stack.

| Variable                | Default (local sample)                 | Purpose                                                                           |
| ----------------------- | -------------------------------------- | --------------------------------------------------------------------------------- |
| `COMPOSE_APP_URL`       | `http://localhost:8000`                | Overrides container `APP_URL` (session/CSRF must match published URL)             |
| `COMPOSE_DB_CONNECTION` | `pgsql`                                | Driver for app services                                                           |
| `COMPOSE_DB_HOST`       | `pgsql`                                | Service DNS (`mysql` / `mariadb` with overlays)                                   |
| `COMPOSE_DB_PORT`       | `5432`                                 | Port on the Compose network                                                       |
| `COMPOSE_DB_DATABASE`   | `externa`                              | Shared with DB container                                                          |
| `COMPOSE_DB_USERNAME`   | `externa`                              | Shared with DB container                                                          |
| `COMPOSE_DB_PASSWORD`   | `secret`                               | Shared with DB container                                                          |
| `APP_PORT`              | `8000` local / `8080` prod sample      | Host publish for nginx                                                            |
| `REVERB_HOST_PORT`      | `8081`                                 | Host publish for Reverb (avoids Herd `:8080`)                                     |
| `VITE_PORT`             | `5173`                                 | Host publish for Vite (local only)                                                |
| `RUN_MIGRATIONS`        | `true`                                 | Entrypoint runs `migrate --force` when `true` (default off in entrypoint)         |
| `RUN_SEED`              | `false`                                | Entrypoint runs `db:seed --force` when `true` (admin via `INITIAL_SUPER_ADMIN_*`) |
| `CACHE_CONFIG`          | `false` local / `true` prod            | Entrypoint config/route/view/event cache                                          |
| `TRUSTED_PROXIES`       | _(unset)_                              | Set behind TLS-terminating proxy (`*` or CIDRs) — prod                            |
| `EXTERNA_IMAGE`         | `ghcr.io/qiick-io/externa-core:latest` | Override image for `compose.quick.yaml`                                           |

Full walkthrough: [Deploy with Docker](https://docs.qiick.io/externa/deploy-with-docker) · [Installation — Docker](https://docs.qiick.io/externa/installation#docker-compose-local-full-stack) · [Deployment — Docker](https://docs.qiick.io/externa/deployment#docker-production).

## Files

Mapped in `config/files.php`:

| Variable                         | Default              | Purpose                                                                                                                                        |
| -------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `FILES_DISK`                     | `assets`             | File manager disk name: `assets`, `s3`, `gcs` (1.4.0), or `azure` (1.4.0).                                                                     |
| `FILES_DUPLICATE_SYNC_MAX_BYTES` | `52428800` (50 MB)   | Single non-folder files at or below this size duplicate **synchronously**. Folders, bulk selections, and larger files use `DuplicateFilesJob`. |
| `FILES_ZIP_TTL_MINUTES`          | `60`                 | Prepared zip lifetime under `storage/app/zips` before cleanup (`files:cleanup-zips` / related command).                                        |
| `FILES_ZIP_MAX_BYTES`            | `104857600` (100 MB) | Maximum zip payload size enforced by application config.                                                                                       |
| `FILE_PUBLIC_URL_BASE`           | _(optional)_         | Override base URL for public/asset disks (`config/filesystems.php`). Also documented under [Files & storage](https://docs.qiick.io/externa/files-configuration).    |

```env
FILES_DISK=assets
FILES_DUPLICATE_SYNC_MAX_BYTES=52428800
FILES_ZIP_TTL_MINUTES=60
FILES_ZIP_MAX_BYTES=104857600
# FILE_PUBLIC_URL_BASE=
```

> **Note**
> Public URLs for the public disk still require `php artisan storage:link` regardless of these variables.

## Outbound webhooks / Slack (1.3.0+)

| Variable                       | Default | Notes                                                                                                                                                                                                                       |
| ------------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WEBHOOKS_ALLOW_PRIVATE_HOSTS` | `false` | When `false`, reject loopback / RFC1918 / link-local / reserved hosts for webhook + Slack notifier URLs (save + delivery). Set `true` only for trusted LAN SSRF opt-in — [Outbound webhooks](https://docs.qiick.io/externa/webhooks#ssrf-guard). |

```env
WEBHOOKS_ALLOW_PRIVATE_HOSTS=false
```

## AI

Mapped primarily in `config/ai.php`. Externa defaults to a **local** OpenAI-compatible provider (LM Studio, vLLM, etc.).

| Variable                                                                                                | Default                                                                                        | Purpose                                                                                                                                                                                    |
| ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `AI_DEFAULT_PROVIDER`                                                                                   | `local`                                                                                        | Provider key under `config('ai.providers')`.                                                                                                                                               |
| `LOCAL_AI_URL`                                                                                          | `http://127.0.0.1:1234/v1`                                                                     | Base URL for the local OpenAI-compatible API.                                                                                                                                              |
| `LOCAL_AI_API_KEY`                                                                                      | _(empty)_                                                                                      | Optional bearer key if your gateway requires one.                                                                                                                                          |
| `LOCAL_AI_MODEL`                                                                                        | `openai/gpt-oss-20b` in `.env.example` (`local-model` if unset in config)                      | Default text model for the local provider. Prefer tool-calling-capable models.                                                                                                             |
| `LOCAL_AI_TRANSLATE_MODEL`                                                                              | _(empty — falls back to `LOCAL_AI_MODEL`)_                                                     | Optional faster non-thinking model for AI field translation (`POST /ai/translate-fields`), so the blocking request does not hit an nginx 504. Example: `phi-4-mini-instruct`.              |
| `AI_ASSISTANT_PROVIDER`                                                                                 | `local`                                                                                        | Provider the in-app Assistant (`AppAssistant`) runs on, a key under `config('ai.providers')` (1.4.0). Separate from `AI_DEFAULT_PROVIDER`, which other AI features use.                    |
| `OPENAI_REASONING_EFFORT`                                                                               | `medium`                                                                                       | Reasoning effort the Assistant asks OpenAI (and Azure) for: `low`, `medium` or `high` (1.4.0). Any other value, or empty, sends no reasoning option.                                       |
| `ANTHROPIC_REASONING_EFFORT`                                                                            | `medium`                                                                                       | Anthropic extended thinking: `low` / `medium` / `high` = 2048 / 8192 / 24576 budget tokens (1.4.0). Any other value, or empty, turns thinking off.                                         |
| `GEMINI_REASONING_EFFORT`                                                                               | `medium`                                                                                       | Gemini thinking level: `low`, `medium` or `high`, with thought summaries (1.4.0). Any other value, or empty, sends no thinking option.                                                     |
| `LOCAL_AI_REASONING_EFFORT`                                                                             | _(unset = none)_                                                                               | Reasoning effort sent to the local OpenAI-compatible model (1.4.0). Unset keeps the model's own behaviour; set `low` / `medium` / `high` only when the model supports it.                  |
| `OPENAI_CONTEXT_WINDOW`, `ANTHROPIC_CONTEXT_WINDOW`, `GEMINI_CONTEXT_WINDOW`, `LOCAL_AI_CONTEXT_WINDOW` | `400000`, `200000`, `1048576`, `131072`                                                        | Context size in tokens shown next to an answer's token usage in the Assistant (1.4.0). Display only: it does not limit the request.                                                        |
| `AI_REMOTE_IMPORT_HOSTS`                                                                                | _(empty)_                                                                                      | Comma-separated host allowlist for remote JSON import tools. Empty means no remote hosts allowed.                                                                                          |
| `AI_WEBHOOK_TOKEN`                                                                                      | _(empty)_                                                                                      | HMAC-SHA256 key for collection import webhook (`X-AI-Webhook-Signature` over the **raw body** only). **Required whenever the webhook is used** — empty fails closed in every env (1.3.0+). |
| `AI_WEBHOOK_TOKEN_PREVIOUS`                                                                             | _(empty)_                                                                                      | Previous HMAC key accepted only while `AI_WEBHOOK_TOKEN_PREVIOUS_EXPIRES_AT` is in the future (set by `ai:rotate-webhook-token`).                                                          |
| `AI_WEBHOOK_TOKEN_PREVIOUS_EXPIRES_AT`                                                                  | _(empty)_                                                                                      | ISO 8601 expiry for the previous token grace window.                                                                                                                                       |
| `AI_DAILY_PROMPT_LIMIT`                                                                                 | `0`                                                                                            | Max prompts per day; **`0` disables the limit**.                                                                                                                                           |
| `AI_EMBEDDINGS_ENABLED`                                                                                 | `false`                                                                                        | When `true`, `SearchSimilarCollectionItems` uses stored vectors (cosine); queues embed jobs on item save; falls back to semantic-lite without vectors / on provider failure.               |
| `AI_MCP_ENABLED`                                                                                        | `false`                                                                                        | Turns on the MCP server at `/mcp` (1.4.0; **404** while `false`) — [MCP server](https://docs.qiick.io/externa/mcp-server).                                                                                      |
| `AI_MCP_WRITE_ENABLED`                                                                                  | `false`                                                                                        | Registers MCP write tools (1.4.0). Still needs the `mcp:write` key scope, the AI write gate, and an approval.                                                                              |
| `AI_MCP_RATE_LIMIT_PER_MINUTE`                                                                          | `60`                                                                                           | `mcp` rate limiter per API key / user / IP (1.4.0); a key's own `rate_limit_per_minute` wins.                                                                                              |
| `AI_TOOLS_ENABLED`                                                                                      | `true`                                                                                         | `false` disables every AI tool in chat and MCP (1.4.0).                                                                                                                                    |
| `AI_WRITE_TOOLS`                                                                                        | `false`                                                                                        | Kill switch for write/destructive AI tools (1.4.0). Off = chat keeps read actions only — [AI tool approvals & audit](https://docs.qiick.io/externa/ai-tool-approvals).                                          |
| `AI_TOOLS_WRITE_ENABLED`                                                                                | `false`                                                                                        | Older name for `AI_WRITE_TOOLS`, read only when `AI_WRITE_TOOLS` is unset (1.4.0).                                                                                                         |
| `AI_TOOLS_WRITE_ALLOW`                                                                                  | _(empty)_                                                                                      | CSV of mutating tool class names allowed to run after approval, e.g. `ManageFiles,ImportCollectionCsv`; merged with Settings → AI write tools (1.4.0).                                     |
| `AI_TOOLS_APPROVAL_TTL`                                                                                 | `15`                                                                                           | Minutes before an MCP pending approval expires (1.4.0).                                                                                                                                    |
| `AI_TOOLS_AUDIT_RETENTION_DAYS`                                                                         | `90`                                                                                           | Retention for `ai_tool` audit rows and resolved approvals, pruned daily by `ai:prune-tool-audit` (1.4.0).                                                                                  |
| `AI_TOOLS_REDACT_KEYS`                                                                                  | built-in list (`authorization`, `password`, `token`, `api_key`, `secret`, `email`, `phone`, …) | CSV of argument keys redacted in the audit log and approval inbox (1.4.0; not in `.env.example`).                                                                                          |
| `AI_TOOLS_ALLOW`                                                                                        | _(empty)_                                                                                      | When set, only these tool names are available (1.4.0).                                                                                                                                     |
| `AI_TOOLS_DENY`                                                                                         | _(empty)_                                                                                      | Tool names that are never available (1.4.0).                                                                                                                                               |

```env
AI_DEFAULT_PROVIDER=local
LOCAL_AI_URL=http://127.0.0.1:1234/v1
LOCAL_AI_API_KEY=
LOCAL_AI_MODEL=openai/gpt-oss-20b
# LOCAL_AI_TRANSLATE_MODEL=phi-4-mini-instruct
# Assistant provider, reasoning and context window (1.4.0)
# AI_ASSISTANT_PROVIDER=local
# LOCAL_AI_REASONING_EFFORT=
# OPENAI_REASONING_EFFORT=medium
# ANTHROPIC_REASONING_EFFORT=medium
# GEMINI_REASONING_EFFORT=medium
# LOCAL_AI_CONTEXT_WINDOW=131072
AI_REMOTE_IMPORT_HOSTS=
AI_WEBHOOK_TOKEN=
# AI_WEBHOOK_TOKEN_PREVIOUS=
# AI_WEBHOOK_TOKEN_PREVIOUS_EXPIRES_AT=
AI_DAILY_PROMPT_LIMIT=0
AI_EMBEDDINGS_ENABLED=false
AI_MCP_ENABLED=false
AI_MCP_WRITE_ENABLED=false
AI_MCP_RATE_LIMIT_PER_MINUTE=60
AI_TOOLS_ENABLED=true
AI_WRITE_TOOLS=false
AI_TOOLS_WRITE_ENABLED=false
AI_TOOLS_WRITE_ALLOW=
AI_TOOLS_APPROVAL_TTL=15
AI_TOOLS_AUDIT_RETENTION_DAYS=90
AI_TOOLS_ALLOW=
AI_TOOLS_DENY=
```

Rotation runbook: [AI API — Rotate the webhook token](https://docs.qiick.io/externa/ai-api#rotate-the-webhook-token).

### Meilisearch (optional site search)

See [Meilisearch sync](https://docs.qiick.io/externa/meilisearch).

| Variable                         | Default     | Notes                                                                |
| -------------------------------- | ----------- | -------------------------------------------------------------------- |
| `MEILISEARCH_ENABLED`            | `false`     | Master switch                                                        |
| `MEILISEARCH_HOST`               | _(empty)_   | e.g. `http://127.0.0.1:7700` or `http://meilisearch:7700` in Compose |
| `MEILISEARCH_API_KEY`            | _(empty)_   | Master/admin key for indexing                                        |
| `MEILISEARCH_INDEX`              | `externa`   | Index uid; created with `primaryKey=id` if missing                   |
| `MEILISEARCH_INDEXABLE_STATUSES` | `published` | Comma-separated; empty = ignore `status` field                       |

```env
MEILISEARCH_ENABLED=false
MEILISEARCH_HOST=
MEILISEARCH_API_KEY=
MEILISEARCH_INDEX=externa
MEILISEARCH_INDEXABLE_STATUSES=published
```

### Git export (optional content-as-code)

See [Git export sync](https://docs.qiick.io/externa/git-export). Prefer [webhooks](https://docs.qiick.io/externa/webhooks) for static rebuilds.

| Variable                      | Default   | Notes                                             |
| ----------------------------- | --------- | ------------------------------------------------- |
| `GIT_EXPORT_ENABLED`          | `false`   | Master switch                                     |
| `GIT_EXPORT_REMOTE_URL`       | _(empty)_ | SSH remote, e.g. `git@github.com:org/content.git` |
| `GIT_EXPORT_BRANCH`           | `main`    | Push target branch                                |
| `GIT_EXPORT_SSH_KEY_PATH`     | _(empty)_ | Deploy private key on disk                        |
| `GIT_EXPORT_FORMAT`           | `json`    | `json` or `markdown`                              |
| `GIT_EXPORT_DRY_RUN`          | `false`   | Commit locally, skip push                         |
| `GIT_EXPORT_COLLECTION_SLUGS` | `*`       | Comma-separated slugs or `*`                      |

```env
GIT_EXPORT_ENABLED=false
GIT_EXPORT_REMOTE_URL=
GIT_EXPORT_BRANCH=main
GIT_EXPORT_SSH_KEY_PATH=
GIT_EXPORT_FORMAT=json
GIT_EXPORT_DRY_RUN=false
GIT_EXPORT_COLLECTION_SLUGS=*
```

### Other AI providers (optional)

`config/ai.php` also documents keys for cloud providers when you switch `AI_DEFAULT_PROVIDER` (or call a named provider explicitly). These are **not** all listed in `.env.example`, but are read when present:

| Variable                                                                                                                           | Used by                           |
| ---------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
| `ANTHROPIC_API_KEY`, `ANTHROPIC_URL`                                                                                               | Anthropic                         |
| `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_URL`, `AZURE_OPENAI_API_VERSION`, `AZURE_OPENAI_DEPLOYMENT`, embedding/image deployment vars | Azure OpenAI                      |
| `AWS_BEDROCK_REGION`, `AWS_BEARER_TOKEN_BEDROCK`, `AWS_SESSION_TOKEN`, `AWS_USE_DEFAULT_CREDENTIALS`                               | Bedrock (plus standard AWS keys)  |
| `COHERE_API_KEY`                                                                                                                   | Cohere                            |
| `DEEPSEEK_API_KEY`                                                                                                                 | DeepSeek                          |
| `ELEVENLABS_API_KEY`                                                                                                               | ElevenLabs                        |
| `GEMINI_API_KEY`, `GEMINI_URL`                                                                                                     | Gemini                            |
| `GROQ_API_KEY`                                                                                                                     | Groq                              |
| `JINA_API_KEY`                                                                                                                     | Jina                              |
| `MISTRAL_API_KEY`                                                                                                                  | Mistral                           |
| `OLLAMA_API_KEY`, `OLLAMA_URL`                                                                                                     | Ollama (`http://localhost:11434`) |
| `OPENAI_API_KEY`, `OPENAI_URL`                                                                                                     | OpenAI                            |
| `OPENROUTER_API_KEY`                                                                                                               | OpenRouter                        |
| `VOYAGEAI_API_KEY`                                                                                                                 | Voyage                            |
| `XAI_API_KEY`                                                                                                                      | xAI                               |

The **local** provider uses the `openai` driver with a custom `url` — laravel/ai accepts that pattern for OpenAI-compatible gateways.

> Never commit real `AI_WEBHOOK_TOKEN`, cloud API keys, or production `LOCAL_AI_API_KEY` values. Rotate the HMAC secret with `ai:rotate-webhook-token` if it leaks ([runbook](https://docs.qiick.io/externa/ai-api#rotate-the-webhook-token)); remote import without a tight `AI_REMOTE_IMPORT_HOSTS` allowlist is unsafe.

## Seeding (initial super admin)

Read by `config/super_admin.php` and used only when `CreateSuperAdminSeeder` runs (via `php artisan db:seed`).

| Variable                         | Default                  | Purpose                          |
| -------------------------------- | ------------------------ | -------------------------------- |
| `INITIAL_SUPER_ADMIN_FIRST_NAME` | `Super`                  | First name.                      |
| `INITIAL_SUPER_ADMIN_LAST_NAME`  | `Admin`                  | Last name.                       |
| `INITIAL_SUPER_ADMIN_EMAIL`      | `superadmin@example.com` | Login email.                     |
| `INITIAL_SUPER_ADMIN_PASSWORD`   | `password`               | Login password (**local only**). |

```env
INITIAL_SUPER_ADMIN_FIRST_NAME=Super
INITIAL_SUPER_ADMIN_LAST_NAME=Admin
INITIAL_SUPER_ADMIN_EMAIL=superadmin@example.com
INITIAL_SUPER_ADMIN_PASSWORD=password
```

These keys may be omitted from `.env.example` because config defaults already match local development; set them explicitly before seeding shared environments.

Built-in roles created by seeders (not env vars): `super-admin`, `admin`, `reader`, `public`.

## Public CMS API

| Variable                                    | Default   | Purpose                                                                                                                                                                                                                         |
| ------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `API_KEY_RATE_LIMIT`                        | `60`      | Default requests/minute per API key when the key has no explicit limit (`config/api.php`).                                                                                                                                      |
| `API_COST_LIMIT`                            | `0`       | Optional cost budget per 60 s window (`0` = off). Weights: `API_COST_BASE` / `API_COST_INCLUDE` / `API_COST_MUTATION` (defaults `1` / `2` / `5`).                                                                               |
| `API_MAX_BODY_BYTES`                        | `0`       | Optional max `Content-Length` (`0` = off). Over → **413**.                                                                                                                                                                      |
| `API_COST_FILTER_RELATION`                  | `2`       | Cost added per dotted relational filter key (`filter[author.name]`) when `API_COST_LIMIT` is on (1.4.0).                                                                                                                        |
| `API_MAX_BATCH_SIZE`                        | `100`     | Max entries per `POST\|PATCH\|DELETE /api/v1/collections/{slug}/items/batch` request, min 1 (1.4.0).                                                                                                                            |
| `API_MAX_PER_PAGE`                          | `100`     | Ceiling for REST/GraphQL list `per_page` / `perPage`.                                                                                                                                                                           |
| `PUBLIC_API_CACHE_TTL`                      | `120`     | Public REST/GraphQL response-cache safety-net TTL in seconds (`config/api.php` `response_cache_ttl`, min 1) — [Performance tuning](https://docs.qiick.io/externa/performance-tuning).                                                                |
| `GRAPHQL_MAX_DEPTH`                         | empty     | Lighthouse max query depth; empty/unset = disabled.                                                                                                                                                                             |
| `GRAPHQL_MAX_COMPLEXITY`                    | empty     | Lighthouse max query complexity; empty/unset = disabled.                                                                                                                                                                        |
| `LIGHTHOUSE_SECURITY_DISABLE_INTROSPECTION` | `false`   | When `true`, disable GraphQL introspection.                                                                                                                                                                                     |
| `CORS_ALLOWED_ORIGINS`                      | `*`       | Comma-separated origins for `api/*` when project `public_api_allowed_origins` is empty (`config/cors.php`). Prefer the project allowlist in production; see [Origin allowlist](https://docs.qiick.io/externa/public-cms-api-types#origin-allowlist). |
| `LIGHTHOUSE_QUERY_CACHE_ENABLE`             | `true`    | Cache parsed GraphQL query strings (`config/lighthouse.php`).                                                                                                                                                                   |
| `LIGHTHOUSE_QUERY_CACHE_MODE`               | `opcache` | Prefer `opcache` (default) over `store` when `CACHE_STORE=database` — avoids serializing AST into the DB cache.                                                                                                                 |

```env
API_KEY_RATE_LIMIT=60
PUBLIC_API_CACHE_TTL=120
CORS_ALLOWED_ORIGINS=*
# API_COST_LIMIT=0
# API_MAX_BODY_BYTES=0
# API_MAX_PER_PAGE=100
# API_COST_FILTER_RELATION=2
# API_MAX_BATCH_SIZE=100
# GRAPHQL_MAX_DEPTH=
# GRAPHQL_MAX_COMPLEXITY=
```

Setup and auth model: [Public CMS API](https://docs.qiick.io/externa/public-cms-api). Abuse controls & scopes: [API abuse controls](https://docs.qiick.io/externa/api-abuse-controls). Cache TTL and include prewarm: [Performance tuning](https://docs.qiick.io/externa/performance-tuning).

## Security headers (1.4.0)

From `config/security.php` — applied to admin HTML responses only (not `/api/*` or GraphQL). Details: [Security headers](https://docs.qiick.io/externa/configuration#security-headers).

| Variable                      | Default                                                            | Purpose                                                                               |
| ----------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| `SECURITY_HEADERS_ENABLED`    | `true`                                                             | Master switch for the headers below plus `X-Content-Type-Options: nosniff`            |
| `SECURITY_X_FRAME_OPTIONS`    | `DENY`                                                             | `X-Frame-Options`                                                                     |
| `SECURITY_REFERRER_POLICY`    | `strict-origin-when-cross-origin`                                  | `Referrer-Policy`                                                                     |
| `SECURITY_PERMISSIONS_POLICY` | `camera=(), microphone=(self), geolocation=(), payment=(), usb=()` | `Permissions-Policy`; `microphone=(self)` keeps AI voice input working                |
| `SECURITY_CSP`                | _(empty)_                                                          | Full `Content-Security-Policy` override; empty = built-in Vite/Reverb-friendly policy |

```env
SECURITY_HEADERS_ENABLED=true
# SECURITY_X_FRAME_OPTIONS=DENY
# SECURITY_REFERRER_POLICY=strict-origin-when-cross-origin
# SECURITY_PERMISSIONS_POLICY="camera=(), microphone=(self), geolocation=(), payment=(), usb=()"
# SECURITY_CSP=
```

None of these keys are in `.env.example`; the defaults apply when they are unset.

## Activity log

From `config/activitylog.php` (Spatie Laravel Activitylog):

| Variable                     | Default | Purpose                                                                                                     |
| ---------------------------- | ------- | ----------------------------------------------------------------------------------------------------------- |
| `ACTIVITYLOG_ENABLED`        | `true`  | When false, activities are not persisted.                                                                   |
| `ACTIVITYLOG_BUFFER_ENABLED` | `false` | Buffer activities in memory and flush after the response — useful only for high-volume logging per request. |

```env
ACTIVITYLOG_ENABLED=true
ACTIVITYLOG_BUFFER_ENABLED=false
```

Other activity log options (retention days, model class, soft-deleted subjects) are config-file defaults rather than `.env` keys — see `config/activitylog.php` (`clean_after_days` defaults to `365`).

## Optional integrations (1.2.0+)

All of these are **off** by default. When disabled, or when the host/remote is empty, the queue jobs do nothing.

### OIDC / SSO

OIDC is configured in **Project settings** (DB), not in `.env`. The only env keys PHP reads are the Docker QA helpers and SSRF opt-in below (`config/services.php`):

| Variable                   | Default | Notes                                                                                                              |
| -------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------ |
| `OIDC_DISCOVERY_BASE`      | —       | Container-internal URL used to fetch discovery when the browser issuer is `localhost` (e.g. Keycloak in Compose)   |
| `OIDC_SERVER_ORIGIN`       | —       | Rewrites the server-side token/userinfo endpoints to this origin                                                   |
| `OIDC_ALLOW_PRIVATE_HOSTS` | `false` | 1.4.0: allow discovery / endpoint URLs on private or CGNAT hosts (lab / Docker IdP). Off = SSRF guard rejects them |

Redirect URI to register at the IdP: `{APP_URL}/auth/oidc/callback`. See [SSO / OIDC](https://docs.qiick.io/externa/sso-oidc).

### Meilisearch

| Variable                         | Default     | Notes                                                                       |
| -------------------------------- | ----------- | --------------------------------------------------------------------------- |
| `MEILISEARCH_ENABLED`            | `false`     | Turns on the sync jobs                                                      |
| `MEILISEARCH_HOST`               | —           | e.g. `http://meilisearch:7700` (Compose `--profile meilisearch`)            |
| `MEILISEARCH_API_KEY`            | —           | Master or admin key (server-side only)                                      |
| `MEILISEARCH_INDEX`              | `externa`   | Index uid; created with `primaryKey=id` if missing                          |
| `MEILISEARCH_INDEXABLE_STATUSES` | `published` | Comma list. Applies only when the item has a `status` field; empty = ignore |

See [Meilisearch sync](https://docs.qiick.io/externa/meilisearch).

### Git export

| Variable                      | Default                    | Notes                                                 |
| ----------------------------- | -------------------------- | ----------------------------------------------------- |
| `GIT_EXPORT_ENABLED`          | `false`                    | Export on publish                                     |
| `GIT_EXPORT_REMOTE_URL`       | —                          | SSH remote, e.g. `git@github.com:org/content.git`     |
| `GIT_EXPORT_BRANCH`           | `main`                     |                                                       |
| `GIT_EXPORT_SSH_KEY_PATH`     | —                          | Deploy-key path on disk. Never commit the private key |
| `GIT_EXPORT_FORMAT`           | `json`                     | `json` or `markdown`                                  |
| `GIT_EXPORT_DRY_RUN`          | `false`                    | Commit locally, skip push                             |
| `GIT_EXPORT_COLLECTION_SLUGS` | `*`                        | Comma list of slugs, or `*`                           |
| `GIT_EXPORT_WORK_DIR`         | `storage/app/git-export`   | Local clone                                           |
| `GIT_EXPORT_COMMIT_NAME`      | `Externa Git Export`       | Commit author name                                    |
| `GIT_EXPORT_COMMIT_EMAIL`     | `git-export@externa.local` | Commit author email                                   |

See [Git export sync](https://docs.qiick.io/externa/git-export).

## Quick reference: externa-specific block

Copy-paste block of the Externa-focused keys as they appear conceptually for a local AI-ready install:

```env
# Files (assets | s3 | gcs | azure)
FILES_DISK=assets
FILES_DUPLICATE_SYNC_MAX_BYTES=52428800
FILES_ZIP_TTL_MINUTES=60
FILES_ZIP_MAX_BYTES=104857600

# Outbound webhooks / Slack SSRF (1.3.0+)
WEBHOOKS_ALLOW_PRIVATE_HOSTS=false
# FILE_PUBLIC_URL_BASE=

# AI (LM Studio / OpenAI-compatible local gateway)
AI_DEFAULT_PROVIDER=local
LOCAL_AI_URL=http://127.0.0.1:1234/v1
LOCAL_AI_API_KEY=
LOCAL_AI_MODEL=openai/gpt-oss-20b
# LOCAL_AI_TRANSLATE_MODEL=phi-4-mini-instruct
AI_REMOTE_IMPORT_HOSTS=
AI_WEBHOOK_TOKEN=
AI_DAILY_PROMPT_LIMIT=0
AI_EMBEDDINGS_ENABLED=false

# MCP server + AI tool write gate (1.4.0)
AI_MCP_ENABLED=false
AI_MCP_WRITE_ENABLED=false
AI_MCP_RATE_LIMIT_PER_MINUTE=60
AI_TOOLS_ENABLED=true
AI_WRITE_TOOLS=false
AI_TOOLS_WRITE_ALLOW=
AI_TOOLS_APPROVAL_TTL=15
AI_TOOLS_AUDIT_RETENTION_DAYS=90
AI_TOOLS_ALLOW=
AI_TOOLS_DENY=

# Security headers (1.4.0, defaults shown)
SECURITY_HEADERS_ENABLED=true

# Seeding (optional overrides — config defaults apply if omitted)
INITIAL_SUPER_ADMIN_EMAIL=superadmin@example.com
INITIAL_SUPER_ADMIN_PASSWORD=password

# Activity log
ACTIVITYLOG_ENABLED=true
ACTIVITYLOG_BUFFER_ENABLED=false

# Public CMS API
API_KEY_RATE_LIMIT=60
CORS_ALLOWED_ORIGINS=*
# API_COST_FILTER_RELATION=2
# API_MAX_BATCH_SIZE=100
```

## Related docs

- [Quick Start](https://docs.qiick.io/externa/quick-start) — Herd-oriented `.env` and minimal commands
- [Installation](https://docs.qiick.io/externa/installation) — when `.env` is created and seeded; Docker Compose local
- [Deployment](https://docs.qiick.io/externa/deployment) — Docker production, health probes, `TRUSTED_PROXIES`
- [Public CMS API](https://docs.qiick.io/externa/public-cms-api) — `/api/v1`, keys, collection access
- [Files & storage](https://docs.qiick.io/externa/files-configuration) — runtime file behavior
- [AI configuration](https://docs.qiick.io/externa/ai-configuration) — providers and tool safety
- [MCP server](https://docs.qiick.io/externa/mcp-server) and [AI tool approvals & audit](https://docs.qiick.io/externa/ai-tool-approvals) — 1.4.0 AI keys
- [Application config](https://docs.qiick.io/externa/configuration) — non-env config surfaces
