Skip to content
ChatfuelSDK
Guides

Open the operator panel

The panel reads and changes the whole Chatfuel account behind your token, so it opens on a password held next to that token and on nothing else. Installing it, the sixteen-character floor, and why /admin is the only way in.

The panel reads and changes every workspace and every bot behind this deployment's CHATFUEL_TOKEN. That is more than any customer of yours should see, and it is not something either identity already in the app can authorize. There is no second Chatfuel login to check — the deployment holds one token for one account, and asking the operator to sign in to Chatfuel from inside their own app would only prove they hold an account the token already speaks for. A Supabase role is no better: the auth module is opt-in, a deployment without it has no accounts at all, and one with it belongs to its customers.

So the credential is a password, held next to the token in the server-only half of the environment. Whoever can read that file already holds the token, so the panel grants nothing the credential did not.

Install the module

admin is opt-in, which means --yes leaves it out. Name it:

npx @chatfuel/wizard --modules admin,livechat,contacts

The wizard then has three ways to get a password, in order: the --admin-password flag, an ADMIN_PASSWORD already in your environment, and a generated one. Generated is the default rather than a prompt, because the thing being chosen is a secret nobody has to remember — it goes into .env, which is where the panel reads it from, and a password typed by hand is mostly a weaker one.

npx @chatfuel/wizard --modules admin --admin-password '<16 characters at least, no spaces>'

A flag that cannot work is answered before the first question, not halfway through the run. And a password the wizard invented is printed once, at the end, where you are still looking — it goes into .env and into no other file, because the handoff note and the agent instructions are files that get committed.

Put the password in the server environment

.env
ADMIN_PASSWORD=<16 characters at least>

Unprefixed, beside CHATFUEL_TOKEN. A VITE_ name would put it in the browser bundle.

Sixteen characters is a floor the proxy enforces, and it 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 on the platform this is most likely to run on, rate limiting is best-effort and the length of this value is the defence that survives.

Three configurations, three behaviours:

ADMIN_PASSWORDWhat happens
UnsetThe routes are not claimed at all. The host answers its own 404 and the module says there is no panel here.
Shorter than 16 charactersEvery route answers 500 AdminMisconfigured, and the module names the reason.
Set and long enoughThe panel runs.

The app tells those apart on boot rather than showing a form that can never be right: a 404 means absent, an AdminMisconfigured code means the password is too short, anything else means locked.

Open /admin and bookmark it

The panel is routed like any other module and appears in the navigation rail like no other module: never. railHidden is on its descriptor, and it stays on after you unlock — the rail is the list of places a product's users go, and an item that appeared once the password was accepted would still be an item anybody could see the moment they borrowed the machine.

/admin is therefore the whole way in, before unlocking and after. The next segment picks the tab (/admin/health), ?w= names the workspace on the rail and ?b= the bot whose drawer is open.

Typing the password posts it to POST /chatfuel/admin/session. Both sides are hashed with SHA-256 and compared in constant time — hashed first so the buffers are always the same length, because a length check in front of a constant-time compare gives away how long the real password is. On success one cookie is set, Path=/, HttpOnly, SameSite=Strict, and Secure whenever the request arrived over TLS. It is good for twelve hours. Every /chatfuel/admin/* call after that carries the cookie and an x-cf-admin header — a form posted from another origin can send a cookie but cannot set a header.

The HMAC key is derived from the password itself and nothing is stored, which is what lets the panel work on a host that answers each request from a fresh instance. It is also why changing ADMIN_PASSWORD signs every open session out: the key that signed them is gone.

In front of all of it sits a flat 250 ms pause on every attempt, right or wrong, so a wrong answer costs the same as a right one from the outside. Behind that, three wrong answers are free — a typo must not cost a minute — and then the wait doubles from a second up to five minutes. It counts against the forwarded address, which the caller can forge, so it is a speed bump and the password length is the real defence.

One thing to know before you use it: the admin routes never call the proxy's admission sequence, never consult the auth gate and never apply the workspace fence. This is the only place in the proxy where a fence is skipped, and it is deliberate — those exist to keep a request away from bots that are not the caller's, and the account-wide view they withhold is the panel's entire purpose.

Verify: unlock it and read the Health tab

npm run dev

Open localhost:5173/admin and type the password. You land on Bots: the account's workspaces on a rail with their bot counts against the plan, one workspace's bots beside them, and a drawer over one bot.

Then /admin/health, which is the check that the deployment is wired rather than merely reachable. It reports the upstream base and which variable the token came from, whether the token is present and whether Chatfuel still accepts it, the account it belongs to, which fence is in force and how many bots it holds, the auth and admin modes, whether Supabase is configured and answering, whether the publish queue routes are mounted, the egress route, and the list of problems the proxy found at startup.

It reports secrets as present or absent and never prints one — a browser screen that showed the token would be as sensitive as the environment file, and a bot's apiToken is never even selected from the API.

There is no session revocation other than changing the password. The cookie is stateless, which is what makes it work on per-request functions, and a stolen one is good until it expires — twelve hours — or until ADMIN_PASSWORD changes, which invalidates every live session at once.

The admin module covers what each tab does and what it refuses to do. The auth gate and the token boundary are the two fences the panel steps around, and environment is the full variable table.

On this page