Skip to content
ChatfuelSDK
Guides

Turn it into a product other people sign up to

Install the auth module and the app stops being your dashboard: strangers sign up, each gets a Chatfuel bot, and one workspace's plan pays for all of them.

Without the auth module the app is yours: it opens on your Chatfuel account and anybody who reaches the URL is you. With it, the app is a product. Strangers sign up, each account gets a workspace of its own with a Chatfuel bot inside it, and the proxy stops trusting the URL and starts asking your Supabase project who is calling.

One fact shapes everything else. Every customer's bot is created in one Chatfuel workspace — the one you name in CHATFUEL_WORKSPACE_ID — with your master token. That workspace's plan is what pays for all of them, so its bot limit is the ceiling for the whole deployment rather than per customer, and a full one turns the next sign-up into 409 WorkspaceFull. Two caps in your own database sit in front of that: 20 bots a workspace and 200 across the deployment, both refusing with 429 BotLimitReached before Chatfuel is ever asked. The numbers, and how to change them, are on Accounts.

What the app does not do is charge anybody. There is no checkout in it and no billing: the routes the module adds are /sign-in, /sign-up, /invite/<token>, /forgot-password, /reset-password, /no-access and /team, and none of them takes money. You pay Chatfuel for the workspace; how you charge your own customers, and whether you charge them at all, happens somewhere else entirely.

What an account gets

ThingWhat it is
A workspaceOne row in cf_tenants, created at sign-up, with a random id the browser never names. created_by is unique, so two tabs cannot open two of them.
BotsAs many as the caps allow, each a real Chatfuel bot in your workspace. Adding one is two steps: the database reserves the row and decides whether they may, then the server creates the bot and attaches it. If either half fails the other is undone.
Rolesowner (exactly one, cannot be demoted or removed), admin (manages members, invites and bots), member (uses the bots they were granted).
InvitesA link, shown once at creation, carrying the role and optionally the bots it grants. The colleague who accepts joins the inviter's workspace and gets no workspace of their own — and no bot.
Per-bot accessGranted per person on the Team page. Owners and admins carry no grant rows at all: they reach every bot in the workspace by role, and listing them would read as something you could revoke.

Take the module

auth is opt-in, which means it is in the interactive picker but --yes never takes it on its own — it needs a Supabase project, and that is not something to arrange behind somebody's back. Name it:

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

It is also a hidden module: no rail item, no /auth address. It wraps the whole shell in its gate instead, puts the user menu in the top bar and answers /team. The one npm package it brings is @supabase/supabase-js.

Adding it to an app that already exists is the --embed story rather than this one — see Add a module.

Point it at a Supabase project

The users live on your Supabase project, not ours. The free plan is enough, and the wizard offers two ways to reach it.

Generate a personal access token at supabase.com/dashboard/account/tokens and paste it when asked. The wizard then picks or creates the project, waits for it to come up healthy, reads the API keys, applies the migrations and configures email + password sign-in. The token is used once, kept in memory, and written nowhere.

A fine-grained token needs the scopes the wizard names when it asks; a classic all-access token works too.

In a scripted run, --supabase-create <name> reuses a project already carrying that name instead of making a second one, so re-running the script does not spend the account's other free project on a duplicate. And a project that already carries a Chatfuel install is named out loud before anything is applied: re-running for the same deployment is the update path, while a second deployment on one project shares its accounts, its bots and its publish queue.

Set the variables

Two of them decide whether the gate exists at all:

VITE_SUPABASE_URL=https://<ref>.supabase.co
VITE_SUPABASE_ANON_KEY=<publishable key>

Both set turns the gate on. Neither is open mode. One without the other is fail-closed — every proxied request answers 500 ProxyAuthMisconfigured, because the proxy refuses to guess which half you meant.

Two more do not touch the gate but decide whether it is any use:

NameWhy
SUPABASE_SERVICE_ROLE_KEYServer-side only. Without it the provisioning route is not mounted, so the server cannot register a bot it just created and sign-up ends on "your workspace is not ready".
CHATFUEL_WORKSPACE_IDThe Chatfuel workspace every customer's bot is created in. Without it the route refuses rather than creating bots that bill nowhere.

CHATFUEL_WORKSPACE_ID is unrelated to VITE_CHATFUEL_WORKSPACE_ID, which stops meaning anything once this module is on: the workspace picker disappears and the bots come from the session. Environment variables has the full table.

The wizard tells you how much room the workspace has when it asks which one to use, and warns you outright when it holds a single bot — then only the first account through the door gets one.

Run the migrations

Your app has a supabase/ directory, and it is yours. On the access-token path the wizard has already run these against the project; on the manual path nobody has.

supabase/migrations/
  0001_chatfuel_auth.sql        the whole schema: cf_profiles, cf_tenants, cf_members,
                                cf_bots, cf_bot_members, cf_invites, cf_migrations,
                                cf_invite_probes, cf_recovery_events, cf_resource_owner,
                                cf_admin_attempts and every cf_* RPC
  0010_chatfuel_publishing.sql  only with the publishing module: the publish queue
  0020_chatfuel_admin.sql       only with the admin module: its service-role functions

Apply them in name order — everything above 0001 reads what 0001 makes — by pasting each into the SQL editor at https://supabase.com/dashboard/project/<ref>/sql. Every file is create … if not exists / create or replace throughout, so a second run changes nothing. The wizard's own run stops at the first failure rather than attempting the rest, for the same reason.

There is nothing to seed. A workspace appears when somebody signs up.

Later schema changes go in a new file, 0002_….sql — editing one that already ran makes two deployments drift. The numbering leaves each module a block of ten, which is why the publishing module's file arrives as 0010_ and the admin module's as 0020_.

The two bot caps live in 0001_chatfuel_auth.sql as one-line functions. Setting CHATFUEL_BOT_CAP or CHATFUEL_BOT_TOTAL_CAP in the environment of this wizard run writes your own numbers into the file as it is applied; afterwards they are SQL in your database, and re-running the create or replace is what changes them.

Finish the Supabase settings

On the access-token path the wizard configures sign-in for you, on a project it created, and asks who may open an account:

--signupWhat it asserts
openSign-ups on, addresses taken on trust. The default: a fresh project has no SMTP, so a confirmation mail would never arrive and nobody could sign in.
confirm-emailSign-ups on, and a new account must confirm its address. Needs mail that arrives — the built-in sender manages about two an hour.
closedThe sign-up form is off; you create accounts in the Supabase dashboard.

On a project you brought, the wizard leaves all three alone and says so — a closed sign-up stays closed and a turned-on confirmation stays on. Then, by hand or to check:

  1. Authentication → Providers → Email. "Confirm email" off is what open means; with it on, the default provider rejects addresses it considers undeliverable and caps the rest at two emails an hour.
  2. Authentication → URL configuration. The redirect allowlist has to hold http://localhost:5173/** and your deployed origin, https://app.example.com/**. The wizard merges the app origin either way, and adds the localhost entry only to a project it made.
  3. SUPABASE_SERVICE_ROLE_KEY in the app's .env (Project Settings → API Keys → secret). The cf_bot_created function is granted to service_role and to nobody else.

Password-reset mail needs your own SMTP (Authentication → SMTP). Until you configure it, admins issue reset links from the row menu on the Team page — those open in any browser, while the emailed link on a free project carries a PKCE code that only works in the browser that asked. That route delivers by writing the link to the server log, so it is off unless you turn it on: AUTH_RECOVERY_LINK_LOG=true, plus PUBLIC_URL for the address the link points at. The wizard asks about it once, and the reasoning is on Accounts.

Sign up as your own first customer

Start the app and open /sign-up. Do this before you share the URL.

npm run dev

Three things prove it. The account exists: sign-up returns a session immediately, with no email round-trip. The bot exists: the app calls POST /chatfuel/auth/provision, the server creates a real Chatfuel bot in CHATFUEL_WORKSPACE_ID with your master token, and the bot appears in the top-bar switcher — go and look at it in the Chatfuel dashboard, it is really there. The gate is on: read the startup line, which begins chatfuel proxy: auth gate on (bots per account in workspace and then names the workspace and every route it mounted.

The startup log is the only place the mode is named. The health route is the same path on every host — the dev server, the bundled Node server and the Vercel function — and answers liveness and nothing else, because it is open to anyone who can reach the deployment:

GET /chatfuel/healthz  →  {"ok":true}

It answers 503 with {"ok":false} when the proxy found a configuration problem at startup, and a missing CHATFUEL_WORKSPACE_ID is one of them. Which problem it is, is in the log beside the mode.

Then open /team, invite a second address, and check that the invited account lands in your workspace rather than getting one of its own.

Under open sign-up, anybody who can reach the URL can create an account, addresses are never verified, and every account spends a bot from your Chatfuel plan — as does every extra bot they add afterwards. A workspace whose plan holds one bot gives it to the first person through the door, and that need not be you. The server says so at every boot while sign-ups are open. To close the door, use --signup closed or the project's own Allow new users to sign up toggle; to restrict which accounts get a workspace, put a check inside cf_claim_workspace in a new 0002_….sql.

The gate itself — what it checks, what it caches for thirty seconds, and the operations no fence can cover — is the auth gate. The codes it answers with are in Errors, and the module's own surface is Accounts.

On this page