Skip to content
ChatfuelSDK
Guides

Add a module to an app you already made

The wizard has one door into a project that exists — --embed — and it was written for somebody else's app. What it writes, what it never overwrites, and the four things you finish by hand.

Scaffolding is subtractive, so a module you did not pick was never written into your project — there is nothing to switch on. And the scaffold refuses a directory that exists and is not empty, so you cannot point it at your own app either.

The one door the wizard has into a project that already exists is --embed, and it was built for somebody else's app rather than for one it wrote itself. It drops the module into a namespaced directory beside your tree, appends what it can to .env, installs the skill, and stops.

The finishing is yours, and it is four things: move the module into your own tree, add it to the registry, add it to the nav table, install whatever npm packages it needs. They are the same four whether you make them or your coding agent does.

What follows adds deals to an app the wizard scaffolded with livechat and contacts.

Run the wizard in embed mode

npx @chatfuel/wizard --embed --dir ./my-app --modules deals

--embed is the only thing that chooses embed mode — --yes on its own stays standalone. --dir is resolved against the directory you started the wizard in, and defaults to that directory.

--modules takes the wizard's own ids, and deals requires contacts, so the run installs contacts alongside it and prints the line saying so. In an app that already has contacts, that means a second copy of a module you have — harmless, and step three throws it away with the rest.

The run still asks for your Chatfuel token and your workspace. What it skips in embed mode: the brand step (your app already has a name, a public/ and a mark, and none of the three are the wizard's to overwrite), the Vercel deploy, the GitHub push, and starting a dev server.

Read what it wrote, and what it left alone

One directory, namespaced, plus four small writes outside it:

types.ts
client.ts

.env is appended to, never rewritten. Only keys your file does not already define are added, under a # Added by chatfuel-wizard comment. A key that exists with a different value is reported and left exactly as it was — so re-running against a different workspace does not move your VITE_CHATFUEL_WORKSPACE_ID, and if that is what you wanted, you change it yourself.

.gitignore gets a .env line if it has none; the wizard asks first, and refuses to write your token to disk if you say no. Under --yes it adds the line without asking.

The skill goes to .claude/skills/chatfuel-deals/ or .agents/skills/chatfuel-deals/, depending on the agent. An install that is already there is replaced only after a confirm that names the wizard version and date it came from — a stamp the wizard writes into every skill directory it owns. Under --yes it is replaced without one.

CLAUDE.md or AGENTS.md gets a section between <!-- chatfuel:begin --> and <!-- chatfuel:end -->, appended when the file exists and refreshed in place on a later run. Instructions of your own outside those markers are untouched.

And that is the list. No vite.config.ts edit, no tsconfig.json edit, no CSS edit. The one host mutation the wizard offers is installing the dependencies, which it asks about, performs with whichever package manager your lockfile names, and never does under --yes — an unattended run must not write to a project it did not create.

Move the module into your own tree

Your app already has the module contract, the three vendored trees and client.ts. src/chatfuel/ is a second copy of all four, at whatever wizard version npx fetched this time.

mv my-app/src/chatfuel/modules/deals my-app/src/modules/deals
rm -rf my-app/src/chatfuel

The move works because a module imports only React, ~ui, ~api, the shell contract at ../types and its own files. ~ui and ~api are aliases your tsconfig.json already points at src/vendor/, and ../types resolves to src/modules/types.ts — the same contract file, one directory up from where it was.

If your app is much older than the wizard version you ran, compare the two copies of src/chatfuel/vendor/ui and src/vendor/ui before deleting: the newer module may want a component your vendored copy does not have yet.

Register it in both tables

src/modules/index.ts is the registry, and it is generated — the wizard rewrites it from the modules you picked, one import per module under a fixed name. Add one more:

src/modules/index.ts
import { moduleDescriptor as deals } from './deals';

Then put deals in the MODULES array below it, which is the list the shell routes and draws the rail from.

src/modules/navGroups.tsx is the menu, and it is hand-curated — the wizard filters it rather than rewriting it. Put the id in a group:

src/modules/navGroups.tsx
{
  id: 'crm',
  title: 'CRM',
  icon: <IconUsers />,
  items: ['contacts', 'deals', 'bookings'],
},

If the whole group was removed at scaffold time — because none of its modules were picked — the group object is gone and so is its icon import from ~ui, which the wizard drops once nothing mentions it. Put both back.

Skip this file and nothing breaks: a module nobody placed in a group still appears, under a heading called More.

Install what the module needs

A scaffold carries only the dependencies of the modules it took, so a module you add later may want one your package.json no longer has. Today that is one package, and one module: auth needs @supabase/supabase-js. The wizard prints the exact command for your package manager at the end of the embed run — the union of the base set and whatever the modules you named declare.

deals declares none, so there is nothing to install for it.

Check it

cd my-app
npm run check
npm run dev

Open localhost:5173/deals. The board renders, and the rail shows Deals under CRM beside Contacts. npm run check is the half that catches the registry: the descriptor is typed, so a missing import or a misspelled id is a compiler error with a file name on it.

An address whose first segment matches no id in the registry does not 404 — the shell falls back to the first module the registry holds and opens it at its root. So an app where you moved the files but forgot src/modules/index.ts answers /deals with whichever module happens to be first, quietly, and reads as a routing bug rather than a missing registration. If a new module's address shows you a different module, the registry is the first place to look.

Ask your agent instead

Embed mode ends by writing a checklist addressed to your coding agent — the dependency command, the aliases, the CSS entry, the proxy and the entry component to mount — and it is written for whichever agent the run picked. Claude Code reads it as /chatfuel:finish-setup; Codex CLI reads it as a skill. In an app the wizard already scaffolded, most of that checklist is done, and what is left is the four moves above:

The wizard put src/chatfuel/ into this project. Move src/chatfuel/modules/deals to
src/modules/deals, delete the rest of src/chatfuel, add the module to src/modules/index.ts
and to the CRM group in src/modules/navGroups.tsx. Then run npm run check.

The module's own skill is installed either way, so the next question you ask about deals is answered from the module's structure and the traps in the API behind it. That is what agent skills are for.

Next: write a module of your own, or what the wizard writes for the rest of the tree.

On this page