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 WorkspaceFull. Nothing in the app caps how many bots an account creates; the Chatfuel plan is the cap.

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, 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 they add, 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.

An earlier schema gave a workspace exactly one bot. That stopped being true with the 0002_multi_bot migration, and any wording that still says "one tenant per deployed bot" is stale.

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 switches email + password sign-in on. 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.

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.

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
                                and every cf_* RPC
  0002_chatfuel_multi_bot.sql   moves a project that ran an older 0001 across

Apply them in name order — 0002 moves 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 and the data migrations are guarded, so a second run changes nothing. On a project the current 0001 created, 0002 does nothing at all.

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

Later schema changes go in a new file, 0003_….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_.

Finish the three Supabase settings

The access-token path sets the first two for you.

  1. Authentication → Providers → Email → turn OFF "Confirm email". A fresh project has no SMTP, so a confirmation mail never arrives and nobody can sign in. It is also what makes sign-up work on the free plan at all: with confirmation on, the default provider rejects addresses it considers undeliverable outright and caps the rest at two emails an hour, after which every sign-up is rate-limited.
  2. Authentication → URL configuration. The redirect allowlist has to hold http://localhost:5173/** and your deployed origin, https://app.example.com/**.
  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.

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: the dev server has no health route, so it says so on its startup line, which begins chatfuel proxy: auth gate on (bots per account in workspace and then names the workspace and every route it mounted.

On a deployment, ask the health route instead — /healthz on your own server, /chatfuel/healthz on Vercel:

{ "ok": true, "auth": "on" }

off means the two Supabase variables never reached the server. misconfigured means one of them did and the other did not. On Vercel the same answer also carries a problems array, and the status is 503 rather than 200 when it is not empty — a missing CHATFUEL_WORKSPACE_ID shows up there as ProxyWorkspaceMissing.

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

Sign-up is open to anybody who can reach the URL, addresses are never verified, and every account that signs up 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. To restrict who may sign up, put a check inside cf_claim_workspace in a new 0003_….sql; to cap bots per account, put one in cf_new_bot.

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