Skip to content

Writing Plugins

A plugin is a default-exported object that satisfies SbtPlugin. It can add CLI commands and hook into core operations like status, generate-atlas, and docs.

Scaffold

bash
npx sbt scaffold-plugin my-plugin          # packages/plugin-my-plugin/
npx sbt scaffold-plugin my-plugin --external  # plugin-my-plugin/ at project root
npx sbt scaffold-plugin my-plugin --hooks     # include Atlas/status/OpenAPI stubs

Minimal Plugin

ts
import type { SbtPlugin, PluginContext } from "@sbtools/sdk";
import { ui } from "@sbtools/sdk";

const plugin: SbtPlugin = {
  name: "@sbtools/plugin-my-plugin",
  version: "1.0.0",

  commands: [
    {
      name: "my-command",
      description: "Does something useful",
      async run(args: string[], ctx: PluginContext) {
        ui.step("Working...");
        // ctx.projectRoot, ctx.paths, ctx.pluginConfig are available
        ui.success("Done");
      },
    },
  ],
};

export default plugin;

Register it in supabase-tools.config.json:

json
{
  "plugins": [
    { "path": "./packages/plugin-my-plugin", "config": {} }
  ]
}

Run it: npx sbt my-command

PluginContext

Every command and hook receives ctx:

PropertyTypeDescription
projectRootstringAbsolute path to the project root
toolsDirstringAbsolute path to the core package (Docker files)
sbtDataDirstringProject-local runtime data (.sbt/)
artifactsDirstringArtifact storage path (.sbt/artifacts/)
apiUrlstringSupabase API URL
pathsResolvedPathsmigrations, snapshot, docsOutput, functions — all absolute
pluginConfigRecord<string, unknown>Plugin-specific config from the config block
siblingPluginsSbtPlugin[]Other loaded plugins for cross-plugin collaboration

Reading Config

Use the typed config helpers from the SDK (preferred over raw casts):

ts
import { getConfigString, getConfigNumber, getConfigStringArray, resolveConfigPath } from "@sbtools/sdk";

const output   = getConfigString(ctx, "outputDir", "docs/my-output");
const port     = getConfigNumber(ctx, "port", 3333);
const scanDirs = getConfigStringArray(ctx, "scanPaths", ["src/"]);
const typesOut = resolveConfigPath(ctx, "typesOutput", "src/types/supabase.ts");

CLI Flags

ts
import { hasFlag, getArg } from "@sbtools/sdk";

async run(args: string[], ctx: PluginContext) {
  if (hasFlag(args, "--json"))  { /* output JSON */ }
  if (hasFlag(args, "-h", "--help")) { /* show help */ }
  const port = getArg(args, "--port") ?? "3000";
}

Hooks

Hooks let your plugin contribute to core commands. All are optional.

HookCalled byReturns
getAtlasData(ctx)sbt generate-atlas{ categories: Record<string, any[]>, stats: { label, value }[] }
getDashboardView()sbt dashboard{ sections: DashboardSectionDef[] } — JSON-serializable
getStatusLines(ctx)sbt statusstring[] — lines appended to status output
getOpenApiSpec(ctx)sbt docsPartial OpenAPI 3.0 object (deep-merged into the PostgREST spec)

getStatusLines Example

ts
const plugin: SbtPlugin = {
  name: "@sbtools/plugin-example",
  version: "1.0.0",
  commands: [/* ... */],

  async getStatusLines(ctx) {
    return ["  My Plugin: 3 items tracked"];
  },
};

getAtlasData Example

ts
const plugin: SbtPlugin = {
  name: "@sbtools/plugin-example",
  version: "1.0.0",
  commands: [/* ... */],

  async getAtlasData(ctx) {
    return {
      categories: { my_items: [{ name: "Item 1" }, { name: "Item 2" }] },
      stats: [{ label: "My Items", value: 2 }],
    };
  },
};

getDashboardView Example

Create a dashboard.ts file in your plugin:

ts
// src/dashboard.ts
import type { DashboardSectionDef, DashboardView } from "@sbtools/sdk";

export function getMyPluginDashboardView(): DashboardView {
  return {
    sections: [
      {
        id: "my-items",
        title: "My Items",
        description: "Items extracted from the database.",
        dataKey: "my_items",
        layout: "cards",
        card: {
          titleField: "name",
          subtitleField: "type",
          searchFields: ["name", "description"],
          badges: [{ field: "status", toneMap: { active: "good", inactive: "warn" } }],
          details: [{ label: "Description", field: "description" }],
        },
      },
    ],
  };
}

Then in src/index.ts:

ts
import { getMyPluginDashboardView } from "./dashboard.js";

const plugin: SbtPlugin = {
  name: "@sbtools/plugin-example",
  version: "1.0.0",
  commands: [/* ... */],
  getDashboardView: () => getMyPluginDashboardView(),
};

See SDK API — Dashboard View for full documentation.

SDK Utilities

See the full list in SDK API. Most commonly used:

ts
import { ui, ensureDir, writeFileInDir, readText, openFile } from "@sbtools/sdk";

ensureDir(outputDir);
writeFileInDir(outputDir, "report.html", html);
const content = readText(filePath);
openFile(outputPath); // open in default app

Publishing

  1. Build: npm run build (or tsc in your plugin directory)
  2. Publish: npm publish --access public
  3. Users install via npm install @your-scope/plugin-name and add it to their config