# Modules

> A module is a directory, a manifest and a React tree — plus two registration tables, one generated by the wizard and one curated by hand.

Page: https://sdk.chatfuel.com/docs/concepts/modules
Markdown: https://sdk.chatfuel.com/docs/concepts/modules.md

There is no plugin registry to publish to, and nothing in the running app loads a module. A module
is a directory, a manifest and a React tree; the manifest is read while the wizard runs and has no
presence afterwards. By the time the app starts, "which modules are installed" is not a question —
it is the set of directories that exist.

## What a module is made of [#what-a-module-is-made-of]

Two halves, in two places. The content half describes the module and teaches an agent about it; the
app half is the React tree the shell mounts.

<Files>
  <Folder name="modules">
    <Folder name="deals">
      <File name="module.json" />

      <File name="handoff.md" />

      <Folder name="skill" />
    </Folder>
  </Folder>

  <Folder name="apps/shell/src/modules">
    <Folder name="deals">
      <File name="index.tsx" />

      <Folder name="components" />

      <Folder name="hooks" />

      <Folder name="lib" />
    </Folder>
  </Folder>
</Files>

`index.tsx` exports one thing under a fixed name, `moduleDescriptor` — the id, the title, the icon
and a lazily imported root component. Everything else in the subtree is the module's own business:
module code imports React, `~ui`, `~api`, the shell contract and its own files, and nothing else.

## The manifest [#the-manifest]

`module.json` is validated against a JSON Schema, so a field that does not exist is a failed gate
rather than a silent no-op, and the TypeScript type beside the schema has to move with it.

| Field             | What it decides                                                                  |
| ----------------- | -------------------------------------------------------------------------------- |
| `id`              | the directory name on both sides, and the module's first path segment            |
| `status`          | `ready` is offered by the picker; `planned` never is                             |
| `selection`       | `opt-in` is offered but never taken by `--yes`                                   |
| `hidden`          | contributes no rail item and no route of its own                                 |
| `requires`        | module ids that must be installed alongside it                                   |
| `recommends`      | ids the wizard suggests, and you can decline                                     |
| `skill.installAs` | the directory name the agent skill is installed as                               |
| `app.env`         | the environment variables this module needs, and which wizard step supplies each |
| `app.embed`       | the roots and entry component that `--embed` copies into a host project          |
| `permissions`     | the Chatfuel permissions its surfaces need                                       |

`status: "planned"` is how a module lands in the repository before it is ready to be chosen. It is
in the tree, it is validated like the others, and the picker never offers it.

## Scaffolding is subtractive [#scaffolding-is-subtractive]

The wizard copies `apps/shell` whole and then deletes. Module subtrees you did not pick are removed,
the registry is regenerated from what is left, the navigation table is filtered to the same set, and
the `tsconfig` paths that no longer resolve are pruned.

What you get is not a template with holes in it. There is no dead import, no commented-out route and
no path pointing at nothing — the code you did not ask for was never written into your project at
all.

## Two tables register a module, and only one is yours to edit [#two-tables-register-a-module-and-only-one-is-yours-to-edit]

They are different on purpose.

`src/modules/index.ts` is **generated**. The wizard rewrites it from the modules you picked, in
directory order. Editing it by hand is fine until the next scaffold.

`src/modules/navGroups.tsx` is **hand-curated** — the menu's information architecture, and the one
place it lives. It is a table of groups (AI Agent, Live Chat, CRM, Growth) naming module ids in the
order they should read. The wizard filters that table to the modules this app installed rather than
rewriting it, so the menu reads the same way in every deployment.

Three behaviours follow from keeping them apart:

* an id in the table that was never installed vanishes, and a group left empty does not
  render;
* a module that nobody placed in a group still appears, under a fallback heading — a menu that
  silently dropped a page would be worse than one with an extra heading in it;
* when only one module is left there is no nav at all.

Two flags take a module out of the menu, and they are not the same thing. `hidden` removes the
route as well — `auth` is hidden because it wraps the shell instead of being a page in it.
`railHidden` keeps the route and only stops the listing: the admin panel is reached by typing its
address, which is the point of it.

## `requires` and `recommends` [#requires-and-recommends]

A module may not import another module. `requires` is the exception, and it is a knowledge
dependency rather than an import path — `deals` requires `contacts` because it is a view of the same
records. `recommends` is a suggestion the picker makes and you can decline: `livechat` recommends
`contacts`, `admin` recommends `auth`.

For the cases in between, the shell tells each module which ids this deployment actually has. That is
how `knowledge-base` can link into `bookings` when bookings is installed and edit the same data
itself when it is not — without importing it, and without guessing.

## Permissions are declared, not discovered [#permissions-are-declared-not-discovered]

Each manifest also names the Chatfuel permissions its surfaces need — `People / View` to read the
deals board, `People / Edit` to move a card between stages. A token whose account lacks one gets a
refusal from the API rather than a blank screen, and the module knows which permission the call
wanted and says so.

The wizard reads the same list when it writes the handoff notes, so the agent that continues the
work knows what the token has to be allowed to do.

What each module actually does, screen by screen, is in [the module
reference](/docs/modules).

<ModuleCards />
