Skip to content
ChatfuelSDK
Concepts

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.

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

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.

module.json
handoff.md
index.tsx

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

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.

FieldWhat it decides
idthe directory name on both sides, and the module's first path segment
statusready is offered by the picker; planned never is
selectionopt-in is offered but never taken by --yes
hiddencontributes no rail item and no route of its own
requiresmodule ids that must be installed alongside it
recommendsids the wizard suggests, and you can decline
skill.installAsthe directory name the agent skill is installed as
app.envthe environment variables this module needs, and which wizard step supplies each
app.embedthe roots and entry component that --embed copies into a host project
permissionsthe 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

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

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

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

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.

On this page