Docs

Upload third-party extension bundles, review scopes, activate, roll back.

Extensions (Install & Manage)

This page is for the tenant admin installing an extension someone else built. For the author's side — scaffolding, iterating, publishing — head to the Extension Authoring recipe.

Upload flow

From /admin/extensions:

  1. Click Upload.
  2. Pick a .zip containing manifest.json + dist/bundle.mjs.
  3. The three-layer security scanner runs (MIME magic → VirusTotal hash lookup → LLM content inspection). A Malicious verdict rejects the upload outright. Suspicious / Skipped pass with a visible badge on the row.
  4. On success the extension is installed and active (is_active = 1).

SHA-256 of the bundle is recorded alongside the row so you can cross-check against the author's published checksum.

Reviewing scopes before activation

Every manifest declares required_scopes, e.g. ["entities:read", "jobs:read"]. The admin shell shows the declared set prominently in the upload confirmation dialog. Reject the upload if the scopes don't match what you expect from the extension's documented purpose.

See the full scope vocabulary in the Auth page.

Versioning

Uploads are keyed by (tenant_id, extension_id, version). The important rules:

  • Same (extension_id, version) uploaded twice → 409 Conflict. You can't overwrite history.
  • Different version of the same extension_id → the previous active row flips to is_active = 0; the new one lands active. Only one version of a given extension is active at a time per tenant.
  • Historical rows stay in the DB forever — the ledger is your rollback source.

The version ledger is append-only. There is no "delete this version" action; uninstall below flips is_active = 0 but keeps the row.

Rolling back

The detail view for an extension id shows every uploaded version with its created_at, actor, and scan verdict. To roll back:

  1. Click the desired historical version.
  2. Activate — flips the current row to is_active = 0 and the selected one to is_active = 1 in one transaction.
  3. The UI reloads pointing at the newly-active bundle.

No bytes are re-uploaded; the blob in the old row is served directly from tenant_extensions.bundle_blob.

Uninstalling

"Uninstall" means setting is_active = 0 on every row for the extension id. The bundle blobs stay in the DB (for potential future re-activation + audit). If you need to remove the bytes outright, that's a platform-admin operation — not exposed in the tenant admin shell.

What an installed extension can do

Depending on the manifest:

  • Pages — mount at /ext/:ext_id/<path>. Show up in the admin nav under an Extensions heading.
  • Components — custom block types available in the Page Designer palette.

Every extension runs inside a FastYokeProvider with a short-lived 15-minute JWT carrying ext_id + scopes. The host provides react, react-dom, and @fastyoke/sdk via the import map so every extension shares one React instance + one SDK client with the shell — context-based state (current user, tenant, feature flags) crosses cleanly.

Troubleshooting

SymptomCause
Upload rejected: "bundle rejected by security scan"Scanner flagged the bundle. Ask the author to re-scan locally + compare; often a third-party dep with heuristic-positive code.
Component renders blankHost/extension React instance mismatch. See the authoring recipe's dev-mode note.
409 on re-installVersion already uploaded. Ask the author to bump manifest.json → version.