Skip to content
ChatfuelSDK
Reference

The environment the wizard reads and writes

Every variable the wizard itself reads, the .env and .env.example it leaves behind, the lock it writes beside them, what --embed appends instead of overwriting, and the guard that refuses to persist a token.

This page covers the environment the wizard reads and the files it leaves behind; the variables the app itself reads at build and run time are on Environment variables.

What the wizard reads

NameWhere it is readWhat it does
CHATFUEL_TOKENThe token step, first attempt.Used instead of the prompt, and still checked against the API. Under --yes, or with no TTY, its absence — or its rejection — ends the run rather than falling through to a prompt nobody can answer.
SUPABASE_ACCESS_TOKENOnce, as the run starts.The Supabase Management API token, when --supabase-token is not given. It is resolved into memory rather than read back later, so nothing the wizard spawns inherits it, and it is registered with the log scrubber so no error body can echo it. The auth step and the deploy step both take it from there.
SUPABASE_SERVICE_ROLE_KEYThe auth step, manual path only.Taken from the environment instead of being asked for a second time. Because nothing can check that the key belongs to the project being set up, it is named next to that project: at a prompt you can decline it, and under --yes it is used with a warning.
ADMIN_PASSWORDThe admin step, when --admin-password is not given.Used as-is, after clearing the same floor the flag clears — sixteen characters, no whitespace — or the run stops saying so. Only when neither is present does the wizard generate one.
ALLOWED_ORIGINSThe origins step, when --allowed-origins is not given.The browser origins that may call the proxy besides the app's own. Read the same way the flag is parsed, so a value that is not a list of origins is refused here rather than at the deployment.
CHATFUEL_APPS_REPOThe app step, when --apps-repo is not given.Where --app clones the preset catalog from. A catalog that is not the official one is described and confirmed before it is fetched, because a preset is code. Under --yes this variable is refused outright and --apps-repo is what says a catalog is meant: this one can be set in a shell profile and forgotten, and an unattended run has no prompt in which anyone would notice.
GITHUB_TOKEN, GH_TOKENThe app step, on a failed clone.Retried once for an https:// catalog that needs credentials, injected through git config in the environment rather than on the command line. Ambient git credentials are tried first.
CHATFUEL_CONTENT_REFEvery run, before anything reads a manifest.The branch to follow, or a full 40-character commit sha to install exactly — which is answered without a network request at all. Unset, the wizard follows main.
CHATFUEL_CONTENT_ORIGINEvery content fetch.An http or https origin to fetch the content from instead of https://raw.githubusercontent.com, for a fork, a test or a machine with no path to the internet. Setting it also pins the run to the commit the wizard shipped with, because resolving a branch against GitHub would name a commit the mirror does not hold. Credentials in the URL are registered with the scrubber before anything can print it.
CHATFUEL_WIZARD_CACHEEvery content fetch, and the branch resolution.Where the fetched content and the resolution cache are kept, instead of $XDG_CACHE_HOME/chatfuel-wizard or ~/.cache/chatfuel-wizard.
XDG_CACHE_HOMEThe same, and the gh download.The base for both caches when neither is set explicitly. On Windows the gh download uses LOCALAPPDATA.
PUBLISHING_MEDIA_BUCKETThe auth migrations, with the publishing module.Which Supabase Storage bucket the composer's uploads go into. Resolved once and written into the app's .env as well as into the migration, so both sides name the same string.
CHATFUEL_BOT_CAP, CHATFUEL_BOT_TOTAL_CAPThe auth migration, as it is applied.The per-workspace and whole-deployment ceilings on bot creation, substituted into the SQL. Read once by the wizard and never by the app; afterwards they are two functions in your own database. Anything that is not a plain number is left alone, and the file keeps the default it shipped with.
INIT_CWDEvery path the wizard resolves.Where you were standing when you started it. A package-manager run moves process.cwd(), so --dir ./app would otherwise land inside the wrong directory.
npm_config_user_agentPreflight.Whether pnpm is even considered. pnpm is chosen only when pnpm launched the wizard and is really installed; otherwise npm, which ships with Node.
https_proxy, http_proxy, all_proxy, no_proxyEvery outbound call the wizard makes, in lowercase and uppercase, lowercase winning.Node's built-in fetch ignores these, so the wizard reads them itself — otherwise every call behind a company proxy times out and the token check reports "could not reach the API", which reads as a bad token. NO_PROXY is honoured and loopback is always exempt. doctor prints the one it resolved, with any credentials in it masked.
TERM, TERM_PROGRAM, and four Windows terminal variablesThe module picker.Whether this terminal can draw the picker's boxes, or gets the plain form.

Two more are read only to be deleted. npm_config_package and npm_config_call are exported by npx --package and npx -c to every child process, and a nested npx — which is how the deploy script reaches the Vercel CLI and how the handoff starts an agent — reads them as its own configuration and runs the package spec as a command. They are dropped from the process before anything is spawned.

The file it writes

.env, at mode 0600, in the app the wizard has written. It opens with the four variables that belong to the deployment rather than to any module — VITE_APP_NAME, VITE_APP_LOGO, VITE_CHATFUEL_DASHBOARD_URL and ALLOWED_ORIGINS — followed by the union of the selected modules' declared variables and then an app preset's, first declaration winning. That order is a rule and not a style: a preset can add variables of its own but can never redefine CHATFUEL_TOKEN or anything a module declared.

Each value is resolved in one order: a value a step already settled (the workspace, brand, auth, admin and origins steps), then the Chatfuel token, then the manifest's default, then empty. A variable that is declared optional and resolves to nothing is written as a commented # NAME= line, so the file documents it without setting it. A value carrying a line break is refused by name before the file is written — the format has no escape, so such a value would not produce a broken line, it would declare a second variable.

.env.example sits beside it. That one is not generated: it travels with the app template and is copied whole, which is why it carries a paragraph of reasoning above every name instead of a value.

The auth module also declares SUPABASE_PROJECT_REF, which the wizard fills in whenever it knows the ref. Nothing reads it at runtime and no deploy pushes it — it is bookkeeping, so you can find the project the app belongs to without opening the URL.

A set-but-empty variable reads as configured everywhere downstream, which is why the wizard writes # NAME= rather than NAME= and why the deploy script skips both. Filling one of those in with an empty value is how a deployment ends up half-gated.

npm start loads a .env sitting next to the app, and real environment variables win over it — so the file the wizard wrote keeps working after a build, and a platform that sets real variables is never overridden by it.

The lock it leaves beside them

.chatfuel/lock.json is the second file the wizard writes that is not part of the app. It records what the app was made of, so update can answer the only question that matters when new content lands upstream: for each file, has anybody edited it since?

FieldWhat it holds
modestandalone or embed.
wizardVersionThe version of the wizard that wrote it.
repo, commitWhere the content came from, and the exact commit. Present only when the wizard ran from a published package — a run from a repository checkout is pinned to nothing anybody else can resolve, and update says so rather than fetching against it.
modulesThe module ids that were installed.
skillsOne entry per skill directory: the module it came with, where its files came from, whether it went into the app or into your home directory, and managed: false for a directory that was already there and was kept.
filesOne entry per file: where the bytes came from as a path in the content repository, the sha256 of what was written to disk, the sha256 upstream held when the wizard changed the bytes on the way in, the name of the producer for a file the wizard generated rather than copied, and the names of any transforms it applied.

It records provenance and never values. .env is not one of the paths and no entry carries content — a file whose name starts with .env, the .example pair aside, is never even digested, because a digest is not the secret but it is a record of a file nobody meant to describe. Only the trees the wizard actually wrote are walked, which is what keeps an embed's lock from claiming the host's own files, and a lock that would name a path outside the app is refused rather than written.

The scaffold writes it as part of the app and again once the skills are in; the handoff adds its two files afterwards; auth amends it when the token rotation has to add a .gitignore line.

What --embed appends instead of overwriting

An embed lands in a project you already have, which may already have a .env. So the step appends only the keys that file does not define, under a # Added by chatfuel-wizard comment, and never edits a line that is already there.

CaseWhat happens
The key is absentAppended, and reported as added.
The key exists with a different valueLeft untouched, and reported: .env already defines NAME with a different value.
The key exists as a # NAME= placeholder the wizard wrote beforeSkipped, so a re-run does not append it twice.
The file's mode lets other accounts read itTightened to 0600 — it now holds a Chatfuel token — and said out loud, because it is a change to a file you did not ask us to touch.

That tree, those .env keys, a .gitignore line, the skills, the lock and the agent's handoff files are the whole footprint. No vite.config, no tsconfig and no CSS of yours is edited — the wiring is the agent's job, guided by the playbooks in the installed skills. The one exception is the dependency install, which you confirm and your own package manager performs.

The guard that refuses to persist a token

Before either mode writes anything to .env, the wizard checks that git would leave it out of a commit. It asks git rather than reading .gitignore, because the text of one file is a guess and git holds the answer: a rule can live in a parent directory, in .git/info/exclude or in core.excludesFile, and a .env that is already tracked is not made safe by any line added anywhere — git keeps following a file it already follows. That case is refused outright, with the git rm --cached .env that fixes it.

Otherwise, if the file is not covered, the wizard asks. Say no and it writes no environment file at all — the app is still scaffolded, and both the wizard and the agent's instructions then say which variables you have to create it with. Under --yes the line is appended without asking, and said, because there is nobody there to ask and the alternative is an unattended run that produces an app with no token in it.

Either way the line is written and then git is asked again whether it took. A !.env further down, or a rule in a parent this file cannot override, would otherwise leave the token exposed behind a success message.

On this page