Plugin System Overview

Two-Tier enterprise plugin platform with certified in-process plugins and sandboxed marketplace plugins.

The Plugin System is SCRIPE's extensibility engine. It allows platform operators to install certified Tier 1 plugins that run in-process with full infrastructure access, and third-party Tier 2 plugins that run in a sandboxed REST gateway with an isolated key-value data store.

ClaimCurrent Code RealityDocumentation Rule
Tier 2 plugin sandboxGateway, scoped auth, rate limiting, execution logs, iframe bridge, and key-value data boundariesDo not describe as process or container isolation
Tier 1 pluginsTrusted in-process extensionsOnly install trusted or certified code
Marketplace pluginsInfrastructure exists but production safety depends on review, isolation, billing, and payout hardeningMark as partial or postponed for production commerce

What Is the Plugin System?

The Plugin System provides a Two-Tier architecture for extending SCRIPE with additional capabilities. Tier 1 plugins are trusted, certified modules that integrate directly into the .NET runtime via IPluginStartup. Tier 2 plugins are third-party applications that integrate via a secure REST gateway and communicate with the host using a postMessage-based SDK.

  • Tier 1 — Certified Plugins

    In-process plugins with full DI access, Module Federation frontend, and IPluginStartup contract.

  • Tier 2 — Sandboxed Plugins

    Third-party plugins isolated via REST gateway with rate limiting, scoped auth tokens, and an isolated key-value store.

  • Plugin SDK

    postMessage-based communication protocol with typed bridge classes for theme, auth, navigation, and toast.

  • API Gateway

    Authenticated plugin-api gateway with per-tenant rate limiting, execution logging, and API key management.

  • Execution Logs

    Append-only execution log per installation. Records endpoint, duration, status code, and success/failure.

  • Webhook Events

    7 platform events (installed, uninstalled, activated, deactivated, upgraded, health failed, rate limited).

Two-Tier Comparison

The Two-Tier model separates trusted internal plugins from third-party marketplace plugins with clear security boundaries.

AspectTier 1 (Certified)Tier 2 (Marketplace)
WhoInternal / certified pluginsThird-party marketplace plugins
RuntimeIn-process (.NET shared runtime)Sandboxed REST gateway
FrontendModule Federation (shared React)iframe + postMessage SDK
Data AccessFull DI accessIsolated key-value store only
AuthHost JWTScoped plugin token
QuotaNone (trusted)60 API calls per minute

System Architecture

The plugin system orchestrates from catalog registration through lifecycle management, health monitoring, and execution logging.

Plugin Catalog — Registry of all available plugin definitions with tier, status, manifest.
install
Installation — Tenant-scoped install record with settings JSON and health status.
Tier 1
Tier 1 Host — IPluginHost — discovers IPluginStartup and activates in-process.
Tier 2 Gateway — IPluginGateway — forwards HTTP to plugin BaseUrl with auth.
rate check
Rate Limiter — PluginSandbox — 60 req/min per tenant+installation sliding window.
Execution Logs — Append-only PluginExecutionLog records per gateway call.

Plugin Registration Lifecycle

Plugins transition through formal registration stages from manifest validation and sandbox testing to production publication.

Register Plugin — Submit plugin manifest, bundle schema, and permission declarations.
Draft to Review
Publish to Registry — Validate cryptographic signatures and publish to tenant marketplace.
Review to Publish
SemVer Release — Immutable version tagging with database migration scripts.
Publish to Versioning
Tenant Installation — Provision isolated tenant sandbox, storage quotas, and webhooks.
Version to Active Install
Zero-Downtime Upgrade — Execute automated schema migrations and seamless iframe hot-swap.
Clean Deprovision — Archive plugin data store and revoke granted authorization scopes.

Backend Architecture

The backend follows SCRIPE's standard 3-project Clean Architecture module layout: Plugins.Domain → Plugins.Application → Plugins.Infrastructure.

PluginsDbContext.csC#
public abstract class PluginsDbContext : DbContext
{
    public DbSet<PluginDefinition>         PluginDefinitions         { get; set; }
    public DbSet<PluginVersion>            PluginVersions            { get; set; }
    public DbSet<PluginInstallation>       PluginInstallations       { get; set; }
    public DbSet<PluginPermissionGrant>    PluginPermissionGrants    { get; set; }
    public DbSet<PluginDataStore>          PluginDataStores          { get; set; }
    public DbSet<PluginApiKey>             PluginApiKeys             { get; set; }
    public DbSet<PluginWebhookSubscription>PluginWebhookSubscriptions{ get; set; }
    public DbSet<PluginExecutionLog>       PluginExecutionLogs       { get; set; }
}

CQRS Commands & Queries

The Plugins module registers 15 request handlers via AstraFlow.Mediator. All commands have a corresponding FluentValidation validator.

TypeHandlerDescription
CommandInstallPluginCommandInstall a plugin definition for a tenant
CommandUninstallPluginCommandRemove a plugin installation and clean up data
CommandActivatePluginCommandSet installation status to Active
CommandDeactivatePluginCommandSet installation status to Disabled
CommandUpgradePluginCommandUpgrade to a new plugin version
CommandUpdatePluginSettingsCommandUpdate the installation settings JSON
CommandRegisterPluginDefinitionCommandRegister a new plugin definition in the catalog
CommandSetPluginDataCommandUpsert a key-value entry in the plugin data store
CommandGrantPluginPermissionCommandGrant a permission to a plugin installation
CommandSubscribePluginWebhookCommandSubscribe to a platform webhook event
QueryGetPluginCatalogQueryList all published plugins in the catalog
QueryGetInstalledPluginsQueryList all installations for a tenant
QueryGetPluginDetailsQueryGet full details of a plugin definition
QueryGetPluginDataQueryRead data store entries for a namespace
QueryGetPluginExecutionLogsQueryGet paginated execution logs for an installation

Domain Entities

The Plugins domain defines 8 entities. PluginExecutionLog is append-only (Entity<Guid>); all others are AuditableEntity<Guid> with soft-delete support.

EntityBase ClassPurpose
PluginDefinitionAuditableEntity<Guid>Plugin catalog entry — global, not tenant-scoped
PluginVersionAuditableEntity<Guid>Version history per plugin definition
PluginInstallationAuditableEntity<Guid>Per-tenant installation record with settings JSON
PluginPermissionGrantAuditableEntity<Guid>Consent record for a permission granted to a plugin
PluginDataStoreAuditableEntity<Guid>Tier 2 key-value sandbox (namespace + key + value JSON)
PluginApiKeyAuditableEntity<Guid>SHA-256 hashed API key for gateway authentication
PluginWebhookSubscriptionAuditableEntity<Guid>Webhook subscription to platform events
PluginExecutionLogEntity<Guid>Append-only gateway call log (no soft-delete)

Frontend Architecture

The frontend follows SCRIPE's MVVM pattern with strict layer separation. Views are dumb UI; ViewModels handle all state and mutations via TanStack Query.

Module StructureText
src/modules/plugins/
├── di.ts                              # DI container (PluginService → PluginRepository)
├── index.ts                           # Public exports (views, components, viewmodels)
└── src/
    ├── domain/
    │   ├── entities/                  # PluginCatalogItem, PluginInstallation,
    │   │                              #   PluginDefinition, PluginManifest
    │   └── interfaces/                # IPluginRepository, IPluginService
    ├── data/
    │   ├── models/PluginModels.ts     # API DTOs + PagedResult<T>
    │   ├── mappers/PluginMapper.ts    # DTO ↔ Entity conversion
    │   ├── repositories/              # PluginRepository (implements IPluginRepository)
    │   └── services/                  # PluginService (Axios via IApiService)
    └── presentation/
        ├── viewmodels/                # usePluginCatalogViewModel
        │                              # useInstalledPluginsViewModel
        │                              # usePluginSettingsViewModel
        │                              # usePluginLogsViewModel
        ├── views/                     # PluginCatalogView, InstalledPluginsView
        │                              # PluginSettingsView, PluginLogsView
        └── components/                # PluginCard, PluginInstallDialog
                                       # PluginHealthBadge

Plugin SDK

The Plugin SDK lives in src/core/plugins/ and provides all infrastructure for host-plugin communication via iframe postMessage for Tier 2, and Module Federation for Tier 1.

SDK StructureText
src/core/plugins/
├── plugin-sdk/
│   ├── types.ts               # HostToPluginMessage | PluginToHostMessage
│   ├── PluginBridge.ts        # postMessage channel (origin-validated)
│   ├── PluginFrame.tsx        # iframe host component
│   ├── PluginThemeSync.ts     # dark/light/accent/RTL push to iframe
│   ├── PluginAuthRelay.ts     # scoped token delivery on request
│   ├── PluginNavigationBridge.ts  # iframe → host router.push()
│   ├── PluginToastBridge.ts   # iframe → host toast notifications
│   └── PluginEventBus.ts      # in-process plugin event dispatcher
├── plugin-host/
│   ├── usePluginHost.ts       # manages active plugin bridge map
│   └── PluginHostProvider.tsx # React context wiring all bridges
└── tier1/
    ├── federation-config.ts   # Module Federation shared deps manifest
    └── ModuleFederationLoader.tsx  # runtime loader for Tier 1 remotes

API Endpoints

All plugin endpoints are under /api/v1/plugins/ for admin operations and /api/v1/plugin-api/v1/ for the sandboxed Tier 2 gateway.

MethodEndpointDescriptionAuth
GET/api/v1/plugins/catalogBrowse published plugins in the catalog🔒 Required
GET/api/v1/plugins/catalog/{id}Get full details of a specific plugin definition🔒 Required
GET/api/v1/plugins/installedList all plugins installed for a tenant🔒 Required
POST/api/v1/plugins/installInstall a plugin for a tenant🔒 Required
DELETE/api/v1/plugins/installed/{id}Uninstall a plugin and trigger data cleanup🔒 Required
POST/api/v1/plugins/installed/{id}/activateActivate a disabled plugin installation🔒 Required
POST/api/v1/plugins/installed/{id}/deactivateDeactivate an active plugin installation🔒 Required
POST/api/v1/plugins/installed/{id}/upgradeUpgrade an installation to a new version🔒 Required
PUT/api/v1/plugins/installed/{id}/settingsUpdate the JSON settings for an installation🔒 Required
GET/api/v1/plugins/installed/{id}/logsGet paginated execution logs (page + pageSize)🔒 Required
POST/api/v1/plugins/definitionsRegister a new plugin definition (super admin only)🔒 Required
GET/api/v1/plugins/context/tenantGet tenant profile and enabled features for plugin context🔒 Required
POST/api/v1/plugins/webhooks/subscribeSubscribe an installation to a platform webhook event🔒 Required
DELETE/api/v1/plugins/webhooks/{id}Unsubscribe from a webhook event🔒 Required
POST/api/v1/plugin-api/v1/auth/tokens/exchangeExchange API key for a short-lived scoped access token🔒 Required
GET/api/v1/plugin-api/v1/data/{installId}/{ns}List all key-value entries in a data store namespace🔒 Required
PUT/api/v1/plugin-api/v1/data/{installId}/{ns}/{key}Upsert a value in the data store (max 64KB)🔒 Required
DELETE/api/v1/plugin-api/v1/data/{installId}/{ns}/{key}Delete a key-value entry from the data store🔒 Required

Webhook Events

The Plugin System publishes 7 webhook events that third-party systems can subscribe to via the webhook subscription API.

EventTriggerDescription
plugin.installedInstallPluginCommandHandler successFired after a plugin is successfully installed for a tenant
plugin.uninstalledUninstallPluginCommandHandler + cleanupFired after uninstall and data store cleanup completes
plugin.activatedActivatePluginCommand successFired when an installation status changes to Active
plugin.deactivatedDeactivatePluginCommand successFired when an installation status changes to Disabled
plugin.upgradedUpgradePluginCommand successFired when an installation is upgraded to a new version
plugin.health_failedPluginHealthCheckJobFired when a health check returns non-2xx for an active plugin
plugin.rate_limit_exceededPluginSandbox.IsAllowed() = falseFired when a Tier 2 plugin exceeds its 60 req/min quota

Background Jobs

Three background jobs manage plugin health, data cleanup, and soft-delete purging.

Job IDScheduleDescription
plugins-soft-delete-cleanupDaily at 03:00Hard-deletes soft-deleted plugin entities older than 30 days
plugins-health-checkEvery 5 minutesCalls GET {baseUrl}/health for every active Tier 2 installation
plugins-data-cleanupDaily at 02:00Removes orphaned data store entries for uninstalled plugins

Permissions Reference

All plugin endpoints are protected by permission-based authorization. Permissions are seeded at startup by PluginsPermissionProvider.

Permission KeyGrants Access To
plugins_catalog.viewBrowse published plugin catalog
plugins_catalog.installInstall plugins for a tenant
plugins_catalog.uninstallUninstall plugins from a tenant
plugins_installed.viewView installed plugins list
plugins_installed.manageActivate, deactivate, upgrade, update settings
plugins_execution_logs.viewView execution logs for an installation
plugins_definition.createRegister new plugin definitions (platform admin)
plugins_permissions.manageGrant and revoke plugin permissions
plugins_webhooks.manageSubscribe and unsubscribe webhook events

Quick Start Guide

  1. Run Database Migration

    Create the Plugins module database and apply migrations using the SCRIPE CLI.
    Shell
    scripe db add-migration Initial -m Plugins
    scripe db update -m Plugins
  2. Register a Plugin Definition

    Register your plugin in the catalog by calling the definitions endpoint as super admin.
    JSON
    POST /api/v1/plugins/definitions
    {
      "key": "my-plugin",
      "name": "My Plugin",
      "nameAr": "مكوني",
      "description": "A test plugin",
      "descriptionAr": "وصف",
      "tier": 2,
      "scope": 1,
      "baseUrl": "https://plugin.example.com",
      "frontendUrl": "https://plugin.example.com/ui",
      "manifestJson": "{\"key\":\"my-plugin\",\"version\":\"1.0.0\",\"tier\":\"tier2\"}"
    }
  3. Install for a Tenant

    Install the plugin for a specific tenant using the install endpoint.
    JSON
    POST /api/v1/plugins/install
    {
      "pluginDefinitionId": "<definitionId>",
      "tenantId": "<tenantId>",
      "installedByUserId": "<userId>"
    }
  4. Activate the Installation

    Activate the installation to make it available to users.
    HTTP
    POST /api/v1/plugins/installed/{installationId}/activate?tenantId={tenantId}
  5. Open the Plugin UI

    Navigate to /plugins/installed in the frontend. You will see the plugin with its health badge, and can click Settings or Logs for per-installation views.

Security

The Plugin System enforces multiple security boundaries to protect tenants from malicious or buggy plugins.

  • Do

    • Validate event.origin in every iframe message listener
    • Use the plugins_definition.create permission for catalog registration
    • Store sensitive config in SettingsJson (encrypted at rest)
    • Monitor execution logs for unusual latency spikes
  • Don't

    • Allow iframe navigation to external (non-/) paths
    • Store raw API keys in the database (only KeyHash is stored)
    • Grant plugins_definition.create to non-admin roles
    • Disable the PluginSandbox rate limiter in production