> ## Documentation Index
> Fetch the complete documentation index at: https://docs.avocadostudio.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Environment reference

> Every environment variable Avocado Studio reads, grouped by what it configures, with which process reads it, its default, and what happens when it is unset.

This page is for the self-hoster and for developers looking a variable up. It
lists what the orchestrator, the site SDK, the editor and the command-line tools
actually read, grouped by what each variable configures.

<Note>
  **Which process reads what.** In **library mode** the site and the orchestrator
  are the same process, so the site and orchestrator variables land in one
  `.env.local`. In a **standalone** deployment they are separate environments, and
  each variable goes where the table says. Most variables are read when they are
  first needed rather than once at boot. Provider keys are the exception that
  matters: change a key and restart the process.
</Note>

Boolean flags accept `1`/`true`/`yes`/`on` and `0`/`false`/`no`/`off`.

## Model providers

At least one key is required for chat. Everything else in the editor works
without one: the preview, the property panel and click-to-select.

| Variable | Default | What it does |
| - | - | - |
| `ANTHROPIC_API_KEY` | — | Anthropic planner. The best-tested path. Chat only, with no image generation. |
| `OPENAI_API_KEY` | — | OpenAI planner, image generation, and voice transcription. |
| `GOOGLE_GENAI_API_KEY` | — | Gemini planner, image generation, and the transcription fallback. In library mode it **also needs `npm install @google/genai`**, an optional peer dependency that is not installed for you. |

With several keys set, a request that names no provider goes to OpenAI, then
Anthropic, then Gemini, in that order. The editor names Anthropic on every
request when that key is present. `CHAT_PLANNER_FORCE_SONNET=1` pins
provider-less requests to Anthropic when its key is set.

### Model selection, per tier

Each provider resolves four tiers. Override any of them by name:

| Tier | Anthropic | OpenAI | Google |
| - | - | - | - |
| fast | `ANTHROPIC_MODEL_FAST` (`claude-haiku-4-5-20251001`) | `OPENAI_MODEL_FAST` (`gpt-5.4-nano`) | `GOOGLE_GENAI_MODEL_FAST` (`gemini-3.5-flash-lite`) |
| balanced | `ANTHROPIC_MODEL_BALANCED` (`claude-sonnet-5-5`) | `OPENAI_MODEL_BALANCED` (`gpt-5.6-terra`) | `GOOGLE_GENAI_MODEL_BALANCED` (`gemini-3.6-flash`) |
| reasoning | `ANTHROPIC_MODEL_REASONING` (`claude-sonnet-5-5`) | `OPENAI_MODEL_REASONING` (`gpt-5.6-terra`) | `GOOGLE_GENAI_MODEL_REASONING` (`gemini-2.5-pro`) |
| codex | `ANTHROPIC_MODEL_CODEX` (`claude-opus-5-5`) | `OPENAI_MODEL_CODEX` (`gpt-5.3-codex`) | `GOOGLE_GENAI_MODEL_CODEX` (`gemini-2.5-pro`) |

<Warning>
  **Leave these unset unless you mean to pin a model.** A pinned id stays pinned
  when Avocado's defaults move to a newer generation, which is why `.env.example`
  no longer sets them.
</Warning>

Also read:

| Variable | Default | What it does |
| - | - | - |
| `ANTHROPIC_PROMPT_CACHE` | on | Anthropic prompt caching. Set `0` to turn it off. |
| `ANTHROPIC_PROMPT_CACHE_TTL` | the 5-minute TTL | `5m` or `1h`. Measure your own traffic before choosing `1h`: it costs more on every cache write. |
| `OPENAI_VISION_MODEL` | `gpt-4o` | The OpenAI model that reads attached images |
| `OPENAI_VISION_ALT_MODEL` | `OPENAI_VISION_MODEL`, else `gpt-4o-mini` | OpenAI model that writes alt text |
| `ANTHROPIC_VISION_ALT_MODEL` | `claude-haiku-4-5-20251001` | Anthropic model that writes alt text |
| `OPENAI_MODEL_KEY` | `balanced` | Default tier for requests that name none |

See [AI providers](/ai-providers).

### Images

| Variable | Default | What it does |
| - | - | - |
| `IMAGE_GEN_PROVIDER` | see note | `gemini` or `openai`. Unset, image variations prefer Gemini, and `/image/generate` and the `image.generate` tool prefer OpenAI. Either way, the request falls back to whichever provider has a key. |
| `OPENAI_IMAGE_MODEL` | per path | Pins every OpenAI image path to one model. Unset, the `image.generate` tool uses `gpt-image-2` for final quality, and drafts and variations use `gpt-image-1-mini`. |
| `OPENAI_IMAGE_MODEL_DRAFT` | `gpt-image-1-mini` | OpenAI model for draft-quality images |
| `GOOGLE_GENAI_IMAGE_MODEL` | `gemini-3.1-flash-lite-image` | Gemini image model |
| `VARIATION_DEFAULT_IMAGE_SOURCE` | `unsplash` | Default image branch for variations when the message gives no hint. `unsplash`, `ai`, `gemini` or `openai`. Message keywords always win. |
| `CHAT_IMAGE_SOURCE_DEFAULT` | ask | `unsplash`, `ai` or `ask`. With both Unsplash and AI images configured, the editor asks where an image should come from, unless this is set. A `PUBLIC_DEMO` uses Unsplash. |
| `UNSPLASH_ACCESS_KEY` | — | Enables Unsplash search in the asset manager |
| `GOOGLE_DRIVE_FOLDER_ID`, `GOOGLE_API_KEY`, `GOOGLE_SERVICE_ACCOUNT_KEY_JSON` | — | A Google Drive folder as a media source |
| `ORCHESTRATOR_GENERATED_IMAGE_DIR` | `.data/generated-images` | Where generated and uploaded images are written |

### Voice input

`POST /audio/transcribe` tries OpenAI first. It falls back to Gemini when OpenAI
fails or is over quota.

| Variable | Default |
| - | - |
| `OPENAI_TRANSCRIBE_MODEL` | `gpt-4o-mini-transcribe` |
| `OPENAI_TRANSCRIBE_FALLBACK_MODELS` | — |
| `GOOGLE_GENAI_TRANSCRIBE_MODEL` | `gemini-2.5-flash` |

With neither provider key set, `/status/planner` reports
`features.audioTranscription: false` and the editor hides the microphone button.

## Access and security

The full picture, including what is open and what is closed by default, is on
[security and access](/reference/security). The variables:

| Variable | Read by | What it does |
| - | - | - |
| `ACCESS_PASSWORD_HASH` | orchestrator | The SHA-256 hex digest of a password. `/auth/verify` exchanges the password for a bearer token, and the editor prompts for it. |
| `ORCHESTRATOR_ACCESS_TOKEN` | orchestrator, site, CLI tools | A fixed bearer token, for scripts and CI. The site sends it when it reads drafts over HTTP from a gated orchestrator. `avocado-register` and `avocado qa` send it too. |
| `AGENT_SURFACE` | standalone orchestrator | `on` mounts the `/agent/*` and `/sites-agent/*` routes in production. Off by default. |
| `AGENT_CLI` | standalone orchestrator | `1` opts into the path that spawns the Claude CLI on the host. Off everywhere by default. |
| `AGENT_API_KEY` | orchestrator | Anthropic or OpenAI key for agent mode on `/agent/*`. |
| `ORCHESTRATOR_CORS_ORIGINS` | orchestrator | The standalone server's CORS allowlist. In library mode CORS comes from `corsOrigins` in code instead, and this variable only lists private addresses that `/publish/diff` may fetch. |
| `EDITOR_CORS_ORIGINS` | site | CORS on the site's `/api/editor/*` routes. Unset in production, those routes send no CORS headers. The scaffolds also pass it to `createOrchestrator({ corsOrigins })`. |
| `NEXT_PUBLIC_EDITOR_ORIGIN`, `AVOCADO_EDITOR_ORIGINS` | site | The editor origins the preview bridge will talk to: the allowlist for `postMessage` and for an editor origin that arrives in a URL. `EDITOR_CORS_ORIGINS` does not feed this allowlist, so set the editor's origin in both. |
| `ORCHESTRATOR_PUBLIC_ORIGIN` | standalone orchestrator | The orchestrator's own address, as the browser sees it. Default `http://localhost:4200`. |

<Warning>
  **Library mode refuses every request under `NODE_ENV=production` when none of
  `ACCESS_PASSWORD_HASH`, `ORCHESTRATOR_ACCESS_TOKEN` or a code-level `auth` hook
  is configured.** That is deliberate. See [security and access](/reference/security).
</Warning>

## The site

Read by your app through the SDK or the Astro integration.

| Variable | What it does |
| - | - |
| `DRAFT_MODE_SECRET` | Validates `?secret=` on draft entry and on the preview render. With it unset, `/api/editor/draft` answers 503 `DRAFT_MODE_SECRET is not set`. `avocado-register` generates it when missing and checks it against the orchestrator's before writing it. On Astro, the integration loads it from `.env` itself. `NEXT_DRAFT_MODE_SECRET` is accepted as a fallback name. |
| `ORCHESTRATOR_URL` | Where the SDK reads draft pages from. In library mode, leave it unset or point it at your own mount, for example `http://localhost:3000/api/avocado`. Then the reads happen in-process and skip the access gate. Falls back to `http://127.0.0.1:4200`. |
| `NEXT_PUBLIC_SITE_URL` | The site's own origin. Without it the pages carry no canonical link, no `og:url`, and a relative `og:image` that social crawlers cannot resolve. |
| `AVOCADO_SITE_ID` | The site id, on Astro and other non-Next projects. `avocado-register` writes it. |
| `NEXT_PUBLIC_SITE_NAME`, `NEXT_PUBLIC_DEFAULT_SITE_ID` | Defaults used by the scaffolds |
| `DRAFT_DEFAULT_SITE_ID`, `DRAFT_DEFAULT_SESSION` | Which site and session an unqualified draft request resolves to |
| `PUBLISH_TOKEN` | The site's publish secret. See [publishing](#publishing). |
| `AVOCADO_FORCE_EDITOR` | `1` makes every Astro render an editor render. Meant for one CI build. Never read from `.env`. |

A mismatch between the editor's draft secret and the site's `DRAFT_MODE_SECRET`
degrades to *"the preview shows published content"* rather than to an error,
which is why it goes unnoticed. `avocado-register` checks the secret against the
orchestrator's and stops, writing nothing, when they differ.

## Publishing

| Variable | Read by | What it does |
| - | - | - |
| `PUBLISH_TOKEN` | site and orchestrator | The publish secret. The site's `POST /api/editor/publish` answers 401 without it under `NODE_ENV=production`. The orchestrator sends the same value as `x-publish-token`, and the standalone server serves it to a signed-in editor from `GET /editor/credentials`. |
| `PUBLISH_TARGET` | orchestrator | Forces a publish target by name: `site-contract`, `git` or `deploy-hook`. Unset, the site contract is used when the site can take it. |
| `PUBLISH_MODE` | orchestrator | Fallback when no target is forced and the site contract does not apply: `git` (default) or `deploy_hook`. |
| `PUBLISH_GRACE_SECONDS` | orchestrator | Seconds before a deploy that cannot be polled is reported ready. Default `120`. |
| `PUBLISHED_CONTENT_PATH` | orchestrator | The published-content JSON file the orchestrator reads published pages from. Unset, it looks for the demo site's `apps/site/lib/published-content.json`. |
| `PUBLISH_GIT_BRANCH`, `PUBLISH_GIT_TOKEN`, `PUBLISH_GIT_AUTHOR_NAME`, `PUBLISH_GIT_AUTHOR_EMAIL`, `PUBLISH_GIT_STRICT` | orchestrator | Committing published content back to a repository |
| `VERCEL_TOKEN`, `VERCEL_TEAM_ID`, `VERCEL_DEPLOY_HOOK_URL` | orchestrator | Triggering and reporting a Vercel deploy |
| `SITE_PUBLIC_ORIGIN` | standalone orchestrator | The site's public origin, as the orchestrator should address it. Default `http://localhost:3000`. |

See [publishing](/integration/publishing).

## Persistence

Full detail on [state and backups](/operations/state-and-backups).

| Variable | Default | What it does |
| - | - | - |
| `ORCHESTRATOR_DB_FILE` | `.data/orchestrator.db` under the working directory | SQLite path. Empty means the default. It becomes `:memory:` under `DEMO_MODE=1` or `NODE_ENV=test`. Set the literal `:memory:` to force ephemeral state. |
| `ORCHESTRATOR_STATE_FILE` | — | Legacy JSON state, read once on first boot and then renamed |
| `ORCHESTRATOR_JSON_MIGRATION_TTL_DAYS` | `14` | Retention for that archived JSON |
| `ORCHESTRATOR_DB_BACKUP_INTERVAL_HOURS` | `24` | Snapshot interval. Standalone server only. |
| `ORCHESTRATOR_DB_BACKUP_LIMIT` | `14` | Rolling snapshots kept. Standalone server only. |

## Operations

| Variable | What it does |
| - | - |
| `PORT` | Port the standalone orchestrator listens on. Default `4200`. |
| `HOST` | Address the standalone orchestrator binds. Default `::`, all interfaces. |
| `NODE_ENV` | `production` closes library mode without a credential, closes the site's publish route without `PUBLISH_TOKEN`, and leaves the agent surface unmounted |
| `LOG_LEVEL` | Log verbosity. `silent` for scripted runs. |
| `DEMO_MODE` | `1` runs the locked-down public demo: allow-listed operations, per-IP rate limiting, no AI image generation, no persistence. See [demo mode](/operations/demo-mode). |
| `PUBLIC_DEMO` | `1` meters model-calling routes per IP and closes cross-visitor routes, without restricting operations. See [demo mode](/operations/demo-mode). |
| `CHECKS_ON_APPLY` | `1` or `0` forces the [site health](/features/site-health) run after edits on or off. Unset, it follows `SITE_OPS_AGENTS`. |
| `CHECKS_ON_PUBLISH` | `0` turns off the check run after a publish |
| `AUTO_BOOTSTRAP_SITE_ORIGIN` | Origin to bootstrap an unknown site from |
| `ORCHESTRATOR_TOOL_MANIFEST_PATH` | A [native tool](/integration/tools-mvp) manifest |
| `SITE_OPS_AGENTS` | Pre-alpha. `1` turns on site ops agents and the site assistant. Default off. `AVOCADO_SECRETS_KEY` encrypts the connection secrets those features store. |

## Telemetry

| Variable | Default | What it does |
| - | - | - |
| `CHAT_TELEMETRY_PERSIST` | on, except under `NODE_ENV=test` | Write chat telemetry to disk |
| `CHAT_TELEMETRY_FILE` | `.data/chat-telemetry.ndjson` | Where to write it |
| `CHAT_TELEMETRY_LIMIT` | `500` | Entries kept in memory and reloaded at startup |
| `FEEDBACK_FILE` | `.data/chat-feedback.ndjson` | Where thumbs-up and thumbs-down feedback lands |
| `FEEDBACK_LIMIT` | `1000` | Feedback entries kept |

See [telemetry events](/observability/chat-telemetry-events) and
[token usage](/observability/token-usage-tracking).

## Chat pipeline flags

These tune the planner rather than configure it. **Defaults are tuned for
responsiveness. Turn flags off one at a time when debugging, not as a matter of
course.**

| Variable | Default |
| - | - |
| `CHAT_PARALLEL_PLANNER` | on |
| `CHAT_ROUTER_HEAD_START_MS` | `200` (capped at 1000) |
| `CHAT_LLM_INTENT_ROUTER` | on |
| `CHAT_INCREMENTAL_APPLY` | on |
| `CHAT_INCREMENTAL_PLAN_STREAM` | on |
| `CHAT_STREAMED_OP_APPLY` | on |
| `CHAT_STREAM_APPLY_MIN_STEP_MS` | `260` |
| `CHAT_DEFER_IMAGE_RESOLUTION` | on |
| `CHAT_AUTO_REASONING` | on (`0` turns it off) |
| `CHAT_AUTO_REASONING_BUDGET` | `2048` |
| `CHAT_ADAPTIVE_SCHEMA_CONTEXT` | off. Turn it on for sites with their own block types. See [block schema contracts](/specs/block-schema-contracts). |
| `CHAT_SCHEMA_BUDGET_BYTES` | `10000` |
| `CHAT_STRICT_JSON_RESPONSE` | off |
| `CHAT_STRICT_PRIMARY_OP_MODE` | off |
| `CHAT_PLANNER_FORCE_SONNET` | off |
| `CHAT_COMPACT_CONTEXT_EXPERIMENT` | off |
| `CHAT_MINIMAL_CONTEXT_EXPERIMENT` | off |
| `CHAT_TRANSLATION_CHUNKING` | on |
| `CHAT_TRANSLATION_CHUNK_BYTES` | `900` |
| `CHAT_TRANSLATION_MAX_CHUNKS` | `6` |
| `CHAT_TRANSLATION_CHUNK_MIN_BLOCKS` | `3` |
| `CHAT_TRANSLATION_CHUNK_MIN_BYTES` | `1800` |

## Jira

Only read when the [Jira channel](/integration/jira) is configured.

`JIRA_BASE_URL` · `JIRA_USER_EMAIL` · `JIRA_API_TOKEN` · `JIRA_WEBHOOK_SECRET` ·
`JIRA_SITE_ID` · `JIRA_SESSION` · `JIRA_AGENT_ACCOUNT_ID` ·
`JIRA_TRIGGER_STATUS` · `JIRA_EXECUTE_STATUS` · `JIRA_PREVIEW_STATUS` ·
`JIRA_REVIEW_STATUS` · `JIRA_DONE_STATUS` · `JIRA_FAILED_STATUS` ·
`JIRA_AUTO_PUBLISH` · `JIRA_MAX_REVIEW_PASSES` · `JIRA_POLL_ENABLED` ·
`JIRA_POLL_INTERVAL_MS` · `JIRA_POLL_JQL`

## MCP server

| Variable | Default | What it does |
| - | - | - |
| `AVOCADO_SITE_ID` | — | **Required.** The site this install edits. |
| `ORCHESTRATOR_URL` | `http://localhost:4200` | The orchestrator to call |
| `AVOCADO_SESSION` | `dev` | The session drafts are scoped to |
| `AVOCADO_PUBLISH_TOKEN` | — | Sent as a bearer token on publish |
| `AVOCADO_MCP_PORT` | `4300` | Port for the streamable-HTTP transport |
| `AVOCADO_MCP_BEARER_TOKEN` | — | Bearer token the HTTP transport requires |

See [MCP server](/integration/mcp-server).

## The editor

`avocado-studio start` writes the editor's configuration into the page it
serves. The CLI reads these as an alternative to its flags. See
[CLI and packages](/reference/cli).

`AVOCADO_ORCHESTRATOR_URL` · `AVOCADO_SITE_ORIGIN` · `AVOCADO_PUBLISH_TOKEN` ·
`AVOCADO_SITE_DRAFT_SECRET` (then `DRAFT_MODE_SECRET`) · `PORT` · `HOST`

When you build `apps/editor` from source instead, Vite reads `VITE_*`
variables at build time and inlines them into the JavaScript bundle:

| Variable | What it does |
| - | - |
| `VITE_ORCHESTRATOR_URL` | The orchestrator the editor calls |
| `VITE_SITE_ORIGIN` | The site the preview frames |
| `VITE_SITE_ID` | The default site id |
| `VITE_LOCK_SITE_ID` | `1` hides the site picker |
| `VITE_DEMO_MODE`, `VITE_PUBLIC_DEMO` | The editor halves of `DEMO_MODE` and `PUBLIC_DEMO` |
| `VITE_SITE_DRAFT_SECRET`, `VITE_PUBLISH_TOKEN` | The draft secret and publish token |

<Warning>
  **Every `VITE_*` value is readable by anyone who can load the editor.** Do not
  build `VITE_SITE_DRAFT_SECRET` or `VITE_PUBLISH_TOKEN` into an editor served on
  a public URL. Give the orchestrator `DRAFT_MODE_SECRET`, `PUBLISH_TOKEN` and an
  access password instead. The editor then fetches them from
  `GET /editor/credentials` after sign-in.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.