Skip to main content

Bundling and Uploading

How your TypeScript source becomes a row in the plugins table.

The bundler

The plugin SDK ships a thin wrapper around esbuild (packages/plugin-sdk/tools/bundle-plugin.ts) that:

  • Targets ES2020 (compatible with QuickJS and V8)
  • Outputs CommonJS (module.exports)
  • Bundles all imports into a single file
  • Strips comments and tree-shakes
  • Writes the bundle to dist/plugin.js

You can invoke it directly:

cd packages/plugin-sdk/examples/rolling-avg-rpm
pnpm tsx ../../tools/bundle-plugin.ts
ls dist/plugin.js # → the bundle

The upload flow

A plugin row is keyed by (accountId, manifestId, version). The upload endpoint is idempotent — uploading the same id+version overwrites the row.

# Body: { manifest, bundleBase64 }
curl -X POST http://localhost:6161/plugins/upload \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @upload.json

Where upload.json is:

{
"manifest": { "id": "...", "version": "1.0.0", "interfaces": ["math"], "capabilities": [] },
"bundleBase64": "...base64-encoded plugin.js..."
}

The server:

  1. Validates the manifest against the Zod schema
  2. Decodes the bundle base64
  3. Writes the bundle to MinIO / R2 via the storage port
  4. Upserts the plugins row, attaches the storage key, links declared capabilities

The one-liner: pnpm seed:demo-plugin

For the seeded demo plugin, there's a single command from the repo root:

pnpm seed:demo-plugin

This runs packages/plugin-sdk/scripts/bundle-and-upload.ts, which:

  1. Bundles the rolling-avg-rpm example
  2. Reads its plugin.json
  3. Reads the resulting dist/plugin.js as base64
  4. Logs in as the seed user
  5. POSTs { manifest, bundleBase64 } to /plugins/upload

The script is the template — adapt it for your own plugins.

After upload

The plugin is immediately:

  • Visible at GET /plugins
  • Loadable by the API when an invocation hits it
  • Visible in the Flutter client's Plugin Runner (/analysis/plugin-runner)

What you can do today

  • Bundle any plugin matching the SDK's interface contracts
  • Upload via POST /plugins/upload or pnpm seed:demo-plugin
  • Re-upload the same id+version to update (idempotent)
  • Invoke via the Plugin Runner UI

What's coming

  • Plugin source download endpoint (GET /plugins/:id/source) — needed before the in-browser flutter_js adapter can run
  • Plugin signing for the marketplace (Phase 5)
  • Hot-reload during authoring
  • Bundle size limit + warning (today: unlimited)