Environment variables
Every variable the app reads, which side reads it, and what it is.
A VITE_-prefixed secret is not a secret. Anything with that prefix is compiled into the browser
bundle at build time; everything else is read by the server at runtime. That is why the Chatfuel
token, the Supabase service-role key and the admin password have no prefix — Vite cannot bake them
into the client even in principle.
Keep the build environment and the runtime environment identical. Where they differ, the failure surfaces at the gate rather than at the mismatch — see the rule that catches everyone once.
Always
| Name | Side | What it is |
|---|---|---|
CHATFUEL_TOKEN | server | The dashboard token, from the token page. Read only by the proxy, never sent to the browser. |
VITE_CHATFUEL_WORKSPACE_ID | client | The workspace the app opens on. Not a fence: every workspace the token's account owns is in the picker. Unused when the auth module is installed. |
CHATFUEL_API_BASE | server | Upstream API base. https://panel.chatfuel.com unless you are pointed at a staging environment. |
VITE_APP_NAME | client | Browser tab, top bar, sign-in screen. |
VITE_APP_LOGO | client | A file in public/, or an absolute URL. The tab icon itself is in index.html — the head is parsed before any of this is read. |
PORT | server | Port for npm start. Default 3000. |
The auth module
VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY decide the gate: both set turns it on, neither is
open mode, and one without the other is ProxyAuthMisconfigured — the proxy refuses every request
rather than guessing which half you meant.
The other two are what the gate needs to be useful. Without SUPABASE_SERVICE_ROLE_KEY the server
cannot register a bot it just created; without CHATFUEL_WORKSPACE_ID it has no Chatfuel workspace
to create one in, and reports ProxyWorkspaceMissing.
| Name | Side | What it is |
|---|---|---|
VITE_SUPABASE_URL | client and server | Project URL. |
VITE_SUPABASE_ANON_KEY | client and server | Publishable key. |
SUPABASE_SERVICE_ROLE_KEY | server | Secret key. Not optional under auth: it registers each bot the server creates, and it mounts the password-recovery route. |
CHATFUEL_WORKSPACE_ID | server | The one workspace every customer's bot is created in — the one whose plan pays for them all, so its bot limit is the ceiling for the whole deployment. Unrelated to VITE_CHATFUEL_WORKSPACE_ID. |
The publishing module
Needed only for posts that go out unattended. Without them the module still composes and publishes on the spot; it offers no schedule.
| Name | Side | What it is |
|---|---|---|
PUBLISHING_SECRET | server | What the app and its database use to prove a request came from the other. The database stores only its sha256. |
PUBLIC_URL | server | Where this deployment answers from outside, e.g. https://posts.example.com. Without it the address is taken from the Host of whichever request turned scheduling on — fine on one domain, not behind a load balancer or on a preview URL. |
VERCEL_AUTOMATION_BYPASS_SECRET | server | Set automatically on deployments whose production URL is protected, so the scheduling callback is not bounced at the edge before any code runs. |
The admin module
| Name | Side | What it is |
|---|---|---|
ADMIN_PASSWORD | server | The only thing that opens /admin, which reads and changes the whole account behind CHATFUEL_TOKEN. Unset means the admin routes are not mounted at all. Changing it signs every open session out. |
At least sixteen characters, or the panel refuses to run and says why. That floor is not fussiness: the wrong-password counter lives in one process's memory, and a host that answers each request from a fresh instance has nothing to share it in, so the length of this value is the defence that survives.
/admin is never in the navigation rail. The URL is the whole way in.
Serving from a sub-path
| Name | Side | What it is |
|---|---|---|
VITE_BASE_PATH | client | Build-time base, e.g. /app. |
BASE_PATH | server | The same value at runtime. |
Both or neither, and not on Vercel — see Serving from a sub-path.
Behind a company proxy
Node's built-in fetch ignores these unless the process was started with a flag nobody types, and
ws never reads them at all — so the proxy reads them itself, for HTTP and for the WebSocket
relay. The wizard's own outbound calls go through the same code.
| Name | Side | What it is |
|---|---|---|
https_proxy, HTTPS_PROXY | server | The proxy to send outbound requests through. First in the order that wins. |
http_proxy, HTTP_PROXY | server | Next in that order. |
all_proxy, ALL_PROXY | server | Last in that order. |
no_proxy, NO_PROXY | server | A comma list of hosts, each matching itself and its subdomains, or * for all of them. |
Lowercase wins over uppercase, and an empty value means unset. Loopback is always exempt, whatever
NO_PROXY says — a dev server on this machine is not the proxy's business.
What has to be reachable: panel.chatfuel.com always, registry.npmjs.org to install anything,
api.vercel.com to deploy, api.github.com to push.
Where the values go on a deployment
npm run deploy copies your .env into the Vercel project with vercel env add --force, for
production and preview. The file itself is never uploaded, so the project environment is the only
source there. Which values go up sensitive is on the Vercel page.