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:
.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/chatfuelThe 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:
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:
{
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 devOpen 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.
Guides
Four procedures end to end — adding a module to an app that exists, writing one of your own, retuning the design system, and mounting the modules inside somebody else's app.
Write a module of your own
A module is a directory, a manifest, a React tree and two registration tables. Copy the smallest one in the repository, keep the conventions the gates check, and finish on pnpm validate.