Architecture
Three things with three lifetimes — a CLI that writes an app, an app you own, and the Chatfuel API it talks to.
npx @chatfuel/wizard is a CLI that writes an app. When it finishes it is gone — no framework of
ours between you and your code, nothing in package.json pointing back at our repository, and
nothing we publish later that can change what is already on your disk.
So "Chatfuel SDK" is three things with three lifetimes, and most confusion about it comes from treating them as one.
@chatfuel/wizard ──writes──▶ your app ──talks to──▶ Chatfuel
(npx, once) (yours, forever) (GraphQL + WebSocket)The CLI runs once
The wizard's whole job is a directory copy with edits. It copies apps/shell whole, deletes the
module subtrees you did not pick, regenerates the module registry, filters the navigation table,
copies the three source packages in, and rewrites the marked import lines that point at them.
Module manifests are read at that moment and never again: there is no manifest, no registry and no plugin loader inside the app at runtime. What the manifests decided is already baked into the files you have.
The app is yours
What lands is a React + Vite project. Three trees inside it came from us as source rather than as
dependencies — the design system under ~ui, the typed API client under ~api, and the proxy
under vendor/chatfuel-proxy — and none of them is reachable through node_modules. That trade is
the subject of its own page: vendoring, not depending.
The API it talks to
~api is a generated, typed GraphQL client: queries and mutations over HTTP, live updates over
graphql-ws, and REST only for uploads. The schema snapshot and the generated documents are
refreshed by scripts rather than hand-edited.
In the browser the client is created with no token at all — it points at your own origin and the proxy attaches the token server-side. See the token boundary.
Errors arrive in one envelope shape, and classification reads the GraphQL extensions.code rather
than the HTTP status. A module therefore never parses a transport error itself; it gets a typed
refusal with a code it can branch on.
What lives where in the repository
If you are reading the source rather than the app it writes, the three sides are three directories.
| Path | What it is |
|---|---|
packages/wizard | the CLI |
apps/shell | the app it writes — the template is the real app, not a copy of one |
packages/ui, packages/api-client, packages/vite-plugin-proxy | the source trees it copies in |
modules/<id> | per module: the manifest, the handoff note and the agent skill |
apps/shell/src/modules/<id> | per module: the React tree |
A module is two halves in two places, which is the one thing about this layout that surprises people — see modules.
What this costs you
Owning the code means owning the bugs in it. Nothing we publish can break your app, and equally
nothing we publish can fix it: an upgrade is a re-run of the wizard or a diff you apply yourself,
never npm update.
How it fits together
Why the token never reaches the browser, why the code is copied in rather than installed, and how a module, a route and a store fit together.
The token boundary
A Chatfuel token can read and change every bot in an account, so it never leaves the server — and everything about the proxy follows from that.