Skip to main content

Plugin Setup and Config

Reference for plugin packaging (package.json metadata), manifests (fluffbuzz.plugin.json), setup entries, and config schemas.
Looking for a walkthrough? The how-to guides cover packaging in context: Channel Plugins and Provider Plugins.

Package metadata

Your package.json needs an fluffbuzz field that tells the plugin system what your plugin provides: Channel plugin:
{
  "name": "@myorg/fluffbuzz-my-channel",
  "version": "1.0.0",
  "type": "module",
  "fluffbuzz": {
    "extensions": ["./index.ts"],
    "setupEntry": "./setup-entry.ts",
    "channel": {
      "id": "my-channel",
      "label": "My Channel",
      "blurb": "Short description of the channel."
    }
  }
}
Provider plugin:
{
  "name": "@myorg/fluffbuzz-my-provider",
  "version": "1.0.0",
  "type": "module",
  "fluffbuzz": {
    "extensions": ["./index.ts"],
    "providers": ["my-provider"]
  }
}

fluffbuzz fields

FieldTypeDescription
extensionsstring[]Entry point files (relative to package root)
setupEntrystringLightweight setup-only entry (optional)
channelobjectChannel metadata: id, label, blurb, selectionLabel, docsPath, order, aliases
providersstring[]Provider ids registered by this plugin
installobjectInstall hints: npmSpec, localPath, defaultChoice
startupobjectStartup behavior flags

Deferred full load

Channel plugins can opt into deferred loading with:
{
  "fluffbuzz": {
    "extensions": ["./index.ts"],
    "setupEntry": "./setup-entry.ts",
    "startup": {
      "deferConfiguredChannelFullLoadUntilAfterListen": true
    }
  }
}
When enabled, FluffBuzz loads only setupEntry during the pre-listen startup phase, even for already-configured channels. The full entry loads after the gateway starts listening.
Only enable deferred loading when your setupEntry registers everything the gateway needs before it starts listening (channel registration, HTTP routes, gateway methods). If the full entry owns required startup capabilities, keep the default behavior.

Plugin manifest

Every native plugin must ship an fluffbuzz.plugin.json in the package root. FluffBuzz uses this to validate config without executing plugin code.
{
  "id": "my-plugin",
  "name": "My Plugin",
  "description": "Adds My Plugin capabilities to FluffBuzz",
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "webhookSecret": {
        "type": "string",
        "description": "Webhook verification secret"
      }
    }
  }
}
For channel plugins, add kind and channels:
{
  "id": "my-channel",
  "kind": "channel",
  "channels": ["my-channel"],
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {}
  }
}
Even plugins with no config must ship a schema. An empty schema is valid:
{
  "id": "my-plugin",
  "configSchema": {
    "type": "object",
    "additionalProperties": false
  }
}
See Plugin Manifest for the full schema reference.

Setup entry

The setup-entry.ts file is a lightweight alternative to index.ts that FluffBuzz loads when it only needs setup surfaces (onboarding, config repair, disabled channel inspection).
// setup-entry.ts
import { defineSetupPluginEntry } from "fluffbuzz/plugin-sdk/core";
import { myChannelPlugin } from "./src/channel.js";

export default defineSetupPluginEntry(myChannelPlugin);
This avoids loading heavy runtime code (crypto libraries, CLI registrations, background services) during setup flows. When FluffBuzz uses setupEntry instead of the full entry:
  • The channel is disabled but needs setup/onboarding surfaces
  • The channel is enabled but unconfigured
  • Deferred loading is enabled (deferConfiguredChannelFullLoadUntilAfterListen)
What setupEntry must register:
  • The channel plugin object (via defineSetupPluginEntry)
  • Any HTTP routes required before gateway listen
  • Any gateway methods needed during startup
What setupEntry should NOT include:
  • CLI registrations
  • Background services
  • Heavy runtime imports (crypto, SDKs)
  • Gateway methods only needed after startup

Config schema

Plugin config is validated against the JSON Schema in your manifest. Users configure plugins via:
{
  plugins: {
    entries: {
      "my-plugin": {
        config: {
          webhookSecret: "abc123",
        },
      },
    },
  },
}
Your plugin receives this config as api.pluginConfig during registration. For channel-specific config, use the channel config section instead:
{
  channels: {
    "my-channel": {
      token: "bot-token",
      allowFrom: ["user1", "user2"],
    },
  },
}

Building channel config schemas

Use buildChannelConfigSchema from fluffbuzz/plugin-sdk/core to convert a Zod schema into the ChannelConfigSchema wrapper that FluffBuzz validates:
import { z } from "zod";
import { buildChannelConfigSchema } from "fluffbuzz/plugin-sdk/core";

const accountSchema = z.object({
  token: z.string().optional(),
  allowFrom: z.array(z.string()).optional(),
  accounts: z.object({}).catchall(z.any()).optional(),
  defaultAccount: z.string().optional(),
});

const configSchema = buildChannelConfigSchema(accountSchema);

Setup wizards

Channel plugins can provide interactive setup wizards for fluffbuzz onboard. The wizard is a ChannelSetupWizard object on the ChannelPlugin:
import type { ChannelSetupWizard } from "fluffbuzz/plugin-sdk/channel-setup";

const setupWizard: ChannelSetupWizard = {
  channel: "my-channel",
  status: {
    configuredLabel: "Connected",
    unconfiguredLabel: "Not configured",
    resolveConfigured: ({ cfg }) => Boolean((cfg.channels as any)?.["my-channel"]?.token),
  },
  credentials: [
    {
      inputKey: "token",
      providerHint: "my-channel",
      credentialLabel: "Bot token",
      preferredEnvVar: "MY_CHANNEL_BOT_TOKEN",
      envPrompt: "Use MY_CHANNEL_BOT_TOKEN from environment?",
      keepPrompt: "Keep current token?",
      inputPrompt: "Enter your bot token:",
      inspect: ({ cfg, accountId }) => {
        const token = (cfg.channels as any)?.["my-channel"]?.token;
        return {
          accountConfigured: Boolean(token),
          hasConfiguredValue: Boolean(token),
        };
      },
    },
  ],
};
The ChannelSetupWizard type supports credentials, textInputs, dmPolicy, allowFrom, groupAccess, prepare, finalize, and more. See bundled plugins (e.g. extensions/discord/src/channel.setup.ts) for full examples. For DM allowlist prompts that only need the standard note -> prompt -> parse -> merge -> patch flow, prefer the shared setup helpers from fluffbuzz/plugin-sdk/setup: createPromptParsedAllowFromForAccount(...), createTopLevelChannelParsedAllowFromPrompt(...), and createNestedChannelParsedAllowFromPrompt(...). For channel setup status blocks that only vary by labels, scores, and optional extra lines, prefer createStandardChannelSetupStatus(...) from fluffbuzz/plugin-sdk/setup instead of hand-rolling the same status object in each plugin. For optional setup surfaces that should only appear in certain contexts, use createOptionalChannelSetupSurface from fluffbuzz/plugin-sdk/channel-setup:
import { createOptionalChannelSetupSurface } from "fluffbuzz/plugin-sdk/channel-setup";

const setupSurface = createOptionalChannelSetupSurface({
  channel: "my-channel",
  label: "My Channel",
  npmSpec: "@myorg/fluffbuzz-my-channel",
  docsPath: "/channels/my-channel",
});
// Returns { setupAdapter, setupWizard }

Publishing and installing

External plugins: publish to ClawHub or npm, then install:
fluffbuzz plugins install @myorg/fluffbuzz-my-plugin
FluffBuzz tries ClawHub first and falls back to npm automatically. You can also force a specific source:
fluffbuzz plugins install buzzhub:@myorg/fluffbuzz-my-plugin   # ClawHub only
fluffbuzz plugins install npm:@myorg/fluffbuzz-my-plugin       # npm only
In-repo plugins: place under extensions/ and they are automatically discovered during build. Users can browse and install:
fluffbuzz plugins search <query>
fluffbuzz plugins install <package-name>
For npm-sourced installs, fluffbuzz plugins install runs npm install --ignore-scripts (no lifecycle scripts). Keep plugin dependency trees pure JS/TS and avoid packages that require postinstall builds.