Skip to content
ChatfuelSDK
ReferenceAPI client

Generated operations

The eleven generated document families, what each covers, how the names are formed, and what happens when you add an operation.

Codegen runs one entry per module, over that module's own operations.graphql, so identical fragment names repeated across modules never collide in a single run. Which modules those are is read off the disk rather than listed: three of the fourteen ship no operations at all, and a list would be a fourth copy of a fact the directory already answers.

The eleven families

FamilyImportRead byWhat it covers
core~api/generated/core/graphqlThe shell, and every moduleIdentity, the bot and workspace lists, the role-and-permission read, file readback, per-user storage, the preview pair a test chat starts from, and the bot's connected channels with their connection and access-refresh links. 18 operations, no subscription.
livechat~api/generated/livechat/graphqllivechat (Inbox)The conversation list and one thread's messages, sends for each channel, read state, take-over, assignment, notes and WhatsApp templates. 46 operations, 5 of them subscriptions.
contacts~api/generated/contacts/graphqlcontactsContact lists and counts, the bot attributes catalog, per-contact attribute edits, assignment, stage, and CSV import and export as background tasks. 40 operations, 4 subscriptions.
deals~api/generated/deals/graphqldealsA deal is a contact carrying a salesStageV2 value: board columns, totals, stage moves, field edits, the table view and its exports. 28 operations, 5 subscriptions.
knowledge-base~api/generated/knowledge-base/graphqlknowledge-baseThe structured record under bot.fuelyConfig.knowledgeBase, FAQs, the goods catalog, specialists, and the read-only conversation scan the Gaps source runs. 23 operations, and no subscription — there is none in the schema to have.
coworker~api/generated/coworker/graphqlcoworker (Copilot)The operator-facing assistant chat: conversations per user and bot, sends that return before the reply arrives, tool approvals, streaming stop. 16 operations, 2 subscriptions.
bookings~api/generated/bookings/graphqlbookingsAppointments over bookingsV2, availability, staff and their Google Calendar links, the services catalog, the booking config and the bot time zone. 37 operations, 4 subscriptions.
automations~api/generated/automations/graphqlautomations (AI agent)How the AI behaves, one automation per scope: the settings, their inherit-from-parent twins, the media and ad pickers, and the preview conversation. 49 operations, 3 subscriptions.
flow-builder~api/generated/flow-builder/graphqlflow-builderFlows, groups, blocks, block elements, buttons and connections. 233 operations, 224 of them mutations, because every element type has its own add mutation.
ads-optimization~api/generated/ads-optimization/graphqlads-optimizationEvent sets over the one automation scope that carries them, their ad lists and event lists, and the inheritance between a custom set and the base one. 14 operations.
publishing~api/generated/publishing/graphqlpublishingAccount state, the media library, four publish mutations and the connect URL. 11 operations.

Counts are top-level definitions in each operations.graphql — 515 operations and 109 fragments across the eleven. Outside the app — a Node script, the wizard — the same files are reachable as @chatfuel/api-client/generated/<family>.

admin, auth and channels have no family. auth talks to Supabase over RPC; admin goes through the proxy's own admin routes; channels reads and writes through core, which is where its operations live.

A module may import its own family or core, and nothing else. Validate pass 10 fails the build on a crossing import, which is what keeps one module's screen from quietly depending on another module's schema knowledge.

How the names are formed

documentMode: 'string' means every document is a printed TypedDocumentString, not an AST. An operation becomes <OperationName>Document and a fragment becomes <Name>FragmentDoc: query InstagramAccountState is exported as InstagramAccountStateDocument, and fragment InstagramAccountRef as InstagramAccountRefFragmentDoc.

The string mode was chosen for size. Every AST mode inlines each fragment's object literal into every operation that spreads it — a 5.5 MB chunk no bundler can dedupe. String mode has the same problem in text: codegen prints an operation's whole transitive fragment closure into that operation's string, and ElementParts alone came out 153 times, three quarters of a 1.9 MB file. So a post-codegen pass prints each fragment once per file and interpolates the constant instead. The operation stays first in the text, which is the only thing getDocMeta reads it for, and each fragment appears exactly once per operation so no server ever sees two definitions of one name. The pass is idempotent: running it twice changes nothing.

You never name the generated types in module code. Import the document, hand it to the client, and the result type comes with it:

content/shell/src/modules/publishing/hooks/useAccount.ts
import { InstagramAccountStateDocument } from '~api/generated/publishing/graphql';
content/shell/src/modules/publishing/hooks/useAccount.ts
  useEffect(() => {
    let live = true;
    setGate({ state: 'loading' });
    client
      .query(InstagramAccountStateDocument, { botID: botId })
      .then((data) => {
        if (!live) return;
        const scope = data.bot.contactScopes.find(
          (candidate): candidate is Extract<typeof candidate, { instagramAccount: Account }> =>
            'instagramAccount' in candidate && Boolean(candidate.instagramAccount),
        );
        const account = scope?.instagramAccount ?? null;
        if (!account) setGate({ state: 'absent' });
        else setGate(canPublish(account) ? { state: 'ready', account } : { state: 'unpermitted', account });
      })
      .catch((err: unknown) => {
        if (!live) return;
        setGate({ state: 'error', message: errorMessage(err) });
      });
    return () => {
      live = false;
    };
  }, [client, botId, tick, refreshToken]);

What the generator emits

The generator body is content/codegen/, and it is copied into every scaffolded app as scripts/codegen/ so both ends run the same one. Nothing in it imports @graphql-codegen/* — the config shape is written out by hand in types.ts, because an app that never extends the API still runs tsc over its scripts/ directory and would otherwise go red on a package it has no reason to install. This repository is where the two are held together: content/api-client/codegen.ts assigns the built config to a real CodegenConfig, so the type check fails the day the hand-written shape stops being one.

The schema everything is checked against is content/schema/schema.graphql — 6,896 lines, generated from a larger one, names and types only, with no descriptions and no deprecated members. possible-types.json sits beside it, holding each interface's and union's concrete types.

Adding an operation in your app

A scaffolded app carries the same three inputs under src/vendor/, and regenerates from them without leaving the repository.

PathWhat it is
src/vendor/schema/schema.graphqlThe SDL everything is checked against.
src/vendor/api/operations/<module>.graphqlThe documents, one file per module. These are the inputs you edit.
src/vendor/api/generated/The output: typed documents, one namespace per module.
codegen.tsThe config, at the root of the app.
scripts/codegen/The generator: the config body, the fragment hoisting, the stamp.
  1. Edit src/vendor/api/operations/<module>.graphql.
  2. Run npm run codegen. The first run prints the one command that installs the toolchain — filled in for whichever package manager wrote your lockfile, versions exact — and stops.
  3. Commit the regenerated files under src/vendor/api/generated/ together with the document.

Then npm run check. Never edit src/vendor/api/generated/ by hand: it is output, and the next run overwrites the edit with no trace. Regenerating an app you have not touched leaves an empty diff, so a diff you did not expect is a real difference in the inputs.

src/vendor/api/generated/.codegen-inputs.json is the stamp — the digest of the schema and of every operation document as they were when the client beside them was generated. It answers "are these types current?" without regenerating to find out. update refreshes the inputs but never the generated files, so when it moves one it says so under regenerate the client and leaves the run to you.

A new operation also has to reach src/operationDocs.ts. The proxy builds its registry from the namespaces that barrel exports and refuses everything else with 403 OperationNotInRegistry, so an operation you generated into a namespace nothing imports is refused on the first request — a refusal, not a type error.

Adding one here

Edit content/modules/<id>/skill/examples/operations.graphql — that file is the source document, not the generated one — then run pnpm codegen at the repository root, which runs graphql-codegen and the fragment hoist. pnpm codegen:check re-runs it and fails on any diff. pnpm validate is what checks the result.

GateWhat it fails on
Pass 0, documentsAn operation that does not validate against the bundled schema, with the line it is on.
Pass 3, name collisionsTwo operations anywhere with the same name. Fragments may repeat across families only with byte-identical bodies — which is why bookings prefixes its specialist and service operations even though knowledge-base reaches the same entities.
Pass 4, possible typespossible-types.json is re-derived from the SDL and compared.
Pass 4c, schema reachabilityA declaration in the SDL that no operation can reach from Query, Mutation or Subscription — a surface described with no way in.
Pass 9, codegen coverageA module with operations and no generated namespace, a generated namespace with no operations behind it, or a ./generated/* export map that does not match the directory. A module with no Chatfuel operations at all — auth, admin, channels — is fine and stays out.
Pass 10, import boundariesA module importing a generated family that is neither its own nor core.
Pass 17, operation allowlistThe proxy's allowedOperations.ts re-derived from the same documents. A module that gained an operation and a list that did not is a feature that 403s in a deployment with the fence on.
Pass 17b, operation docscontent/shell/src/operationDocs.ts must name every generated namespace, or that module's every request is refused.
Pass 18, markdown GraphQLA GraphQL name in a shipped markdown block that the SDL does not have — the reference pages an agent reads before it writes a query.

The same operations.graphql is copied into a scaffolded app as part of the module's agent skill, so the coding agent working in that app reads the validated operations rather than guessing at the schema. What runs them is the client the module receives.

On this page