# 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.

Page: https://sdk.chatfuel.com/docs/guides/add-a-module
Markdown: https://sdk.chatfuel.com/docs/guides/add-a-module.md

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`.

<Steps>
  <Step>
    ### Run the wizard in embed mode [#run-the-wizard-in-embed-mode]

    ```bash
    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.
  </Step>

  <Step>
    ### Read what it wrote, and what it left alone [#read-what-it-wrote-and-what-it-left-alone]

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

    <Files>
      <Folder name="src/chatfuel">
        <Folder name="modules">
          <File name="types.ts" />

          <Folder name="deals" />

          <Folder name="contacts" />
        </Folder>

        <Folder name="vendor">
          <Folder name="ui" />

          <Folder name="api" />

          <Folder name="chatfuel-proxy" />
        </Folder>

        <File name="client.ts" />
      </Folder>
    </Files>

    `.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.
  </Step>

  <Step>
    ### Move the module into your own tree [#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.

    ```bash
    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.
  </Step>

  <Step>
    ### Register it in both tables [#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:

    ```ts title="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:

    ```tsx title="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`.
  </Step>

  <Step>
    ### Install what the module needs [#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.
  </Step>

  <Step>
    ### Check it [#check-it]

    ```bash
    cd my-app
    npm run check
    npm run dev
    ```

    Open [localhost:5173/deals](http://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.
  </Step>
</Steps>

<Callout type="warn">
  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.
</Callout>

## Ask your agent instead [#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](/docs/concepts/agent-skills) are for.

Next: [write a module of your own](/docs/guides/build-a-module), or
[what the wizard writes](/docs/what-you-get) for the rest of the tree.
