Skip to content
Last updated Give Feedback

Plugins

The Plugins panel (/plugins in super-admin) is the catalog view across the framework. Operators use it to:

  • See every plugin known to the platform (whether shipped in plugins/ or published to npm.ecommus.cloud)
  • Enable / disable a plugin for a specific tenant
  • Pin a tenant to a specific plugin version (important for upgrade staging)
  • Publish a new plugin version to the private registry (when marketplace_publisher is on)

This is not the same as the per-tenant tenant admin’s “Plugins” page. The per-tenant page lets a tenant admin enable/disable plugins their license already includes. The super-admin panel here is the platform-wide catalog and grant layer.

The catalog is the union of:

SourceWhen
plugins/ directory in this monorepoBuilt-in plugins. Shipped with every customer copy. Auto-discovered at API boot.
npm.ecommus.cloud registryPremium plugins. Customer pulls them when their license JWT grants the licensed_plugins.
Customer-uploaded private plugins(Enterprise only) the customer can publish their own plugin to a private scope @<customer>/<plugin>.

The panel shows all three with badges (Built-in, Premium, Private).

{
"id": "niche-booking",
"scope": "@ecommus-plugin", // npm scope the package lives under
"version": "2.4.1", // active version on npm.ecommus.cloud
"tier_minimum": "pro", // minimum tier required
"niches": ["services", "travel"], // which niches it's relevant to
"default_in_packages": ["services"], // which niche packages bundle it by default
"tenants_using": 17, // how many tenants currently have it enabled
"supports_versions": ["2.4.1", "2.4.0", "2.3.5"], // versions still on the registry
"deprecated": false,
"publisher": "MediaDesign SRL",
"signed_with_kid": "k1", // code-signing kid on the bundle
"manifest": {
/* ecommus.plugin.json snapshot */
},
"registered_at": "2026-03-12T...",
"updated_at": "2026-04-28T..."
}

For a tenant on the right plan + license, the operator can enable or disable individual plugins:

GET /api/super-admin/tenants/:id/plugins → list with enabled flag
POST /api/super-admin/tenants/:id/plugins/:plugin/enable { version?: "2.4.1" }
POST /api/super-admin/tenants/:id/plugins/:plugin/disable

Server-side checks before enabling:

  1. The plugin id is in the tenant’s license licensed_plugins. If not, the route refuses with 403 license_denied.
  2. The plugin’s tier_minimum is ≤ the tenant’s tier. Otherwise 403 tier_too_low.
  3. The plugin’s niches contains the tenant’s niche. Otherwise 409 niche_mismatch.
  4. If the version is pinned, the version exists on the registry. Otherwise 404 version_not_found.

Enable is idempotent — if already enabled at the same version, no-op.

When enabled, the API:

  • Pulls the npm package on the customer’s install (next deploy or on-demand via /api/admin/plugins/install)
  • Runs the plugin’s migrations (ctx.registerMigration()) — idempotent via _ecommus_plugin_migrations
  • Subscribes the plugin’s hooks + filters to the EventBus
  • Registers the plugin’s routes + admin slots

When disabled:

  • The plugin’s hooks + routes are removed from the live process
  • Migrations are not rolled back. Plugin code is removed from the boot path but its DB tables stay (they’re idempotent on re-enable). Manual cleanup requires a separate SQL operation per ADR-027.

By default, customers receive the latest non-deprecated version of every plugin in their license. To stage an upgrade for a single tenant before the rollout:

POST /api/super-admin/tenants/:id/plugins/niche-booking/pin
{ "version": "2.5.0-beta.1" }

The pin overrides the default-latest until cleared. Use cases:

  • Beta testing a new plugin version with one customer before broad rollout
  • Holding a customer back on an older version because the new one breaks their custom theme overrides
  • Investigating a regression — pin to the previous version while the bug is fixed

Clear with DELETE /api/super-admin/tenants/:id/plugins/:plugin/pin (returns to default-latest).

Publishing a new version (requires marketplace_publisher)

Section titled “Publishing a new version (requires marketplace_publisher)”
POST /api/super-admin/plugins/:id/publish

Wizard flow:

  1. Upload — operator uploads the bundle .tar.gz (built by the plugin’s CI, signed with the platform code-signing key)
  2. Verify signature — server checks the bundle against ECOMMUS_CODE_SIGNING_PUBLIC_KEY_FILE
  3. Run smoke tests — boots a sandbox API in a container with the plugin and runs the standard plugin smoke harness (boots cleanly, registers without errors, migrations apply)
  4. Stage on a canary tenant — operator picks one tenant marked canary: true (set in /tenants detail) and pins the new version there
  5. Wait period — 24 h default (configurable in /settings) — observability stack monitors error rates on the canary tenant. If errors spike >2× baseline, the publish auto-rolls-back.
  6. Promote — after the wait, the operator clicks “promote” and the new version becomes default-latest on the registry. All tenants without a pin automatically pick it up on the next install / pm2 reload.

Publishing without marketplace_publisher is denied at the API layer. The flag is a hard gate (CLAUDE.md §0 — “publishing” requires the flag, not just plugin installation).

POST /api/super-admin/plugins/:id/deprecate flags the plugin’s catalog row + emits a banner in the per-tenant admin UI for every tenant using it. Tenants stay on it until they explicitly migrate away — deprecate doesn’t auto-disable.

DELETE /api/super-admin/plugins/:id removes the plugin from the registry (refuses if any tenant still has it enabled). This is a high-trust action — only owner operators, audit-logged.

A plugin failing to init() does not take the API down — it’s logged and skipped (CLAUDE.md §0.4). The panel surfaces these failures as warning banners on the affected plugin row. Drill in to see the stack trace and which customer installs are affected.

If a plugin starts failing at runtime (post-init), the per-tenant admin panel shows a yellow flag on the plugin’s settings page; the super-admin panel aggregates these into a “Plugin health” widget on the dashboard.