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.
| Claim | Current Code Reality | Documentation Rule |
|---|---|---|
| Tier 2 plugin sandbox | Gateway, scoped auth, rate limiting, execution logs, iframe bridge, and key-value data boundaries | Do not describe as process or container isolation |
| Tier 1 plugins | Trusted in-process extensions | Only install trusted or certified code |
| Marketplace plugins | Infrastructure exists but production safety depends on review, isolation, billing, and payout hardening | Mark 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.
| Aspect | Tier 1 (Certified) | Tier 2 (Marketplace) |
|---|---|---|
| Who | Internal / certified plugins | Third-party marketplace plugins |
| Runtime | In-process (.NET shared runtime) | Sandboxed REST gateway |
| Frontend | Module Federation (shared React) | iframe + postMessage SDK |
| Data Access | Full DI access | Isolated key-value store only |
| Auth | Host JWT | Scoped plugin token |
| Quota | None (trusted) | 60 API calls per minute |
System Architecture
The plugin system orchestrates from catalog registration through lifecycle management, health monitoring, and execution logging.
Plugin Registration Lifecycle
Plugins transition through formal registration stages from manifest validation and sandbox testing to production publication.
Backend Architecture
The backend follows SCRIPE's standard 3-project Clean Architecture module layout: Plugins.Domain → Plugins.Application → Plugins.Infrastructure.
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.
| Type | Handler | Description |
|---|---|---|
| Command | InstallPluginCommand | Install a plugin definition for a tenant |
| Command | UninstallPluginCommand | Remove a plugin installation and clean up data |
| Command | ActivatePluginCommand | Set installation status to Active |
| Command | DeactivatePluginCommand | Set installation status to Disabled |
| Command | UpgradePluginCommand | Upgrade to a new plugin version |
| Command | UpdatePluginSettingsCommand | Update the installation settings JSON |
| Command | RegisterPluginDefinitionCommand | Register a new plugin definition in the catalog |
| Command | SetPluginDataCommand | Upsert a key-value entry in the plugin data store |
| Command | GrantPluginPermissionCommand | Grant a permission to a plugin installation |
| Command | SubscribePluginWebhookCommand | Subscribe to a platform webhook event |
| Query | GetPluginCatalogQuery | List all published plugins in the catalog |
| Query | GetInstalledPluginsQuery | List all installations for a tenant |
| Query | GetPluginDetailsQuery | Get full details of a plugin definition |
| Query | GetPluginDataQuery | Read data store entries for a namespace |
| Query | GetPluginExecutionLogsQuery | Get 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.
| Entity | Base Class | Purpose |
|---|---|---|
| PluginDefinition | AuditableEntity<Guid> | Plugin catalog entry — global, not tenant-scoped |
| PluginVersion | AuditableEntity<Guid> | Version history per plugin definition |
| PluginInstallation | AuditableEntity<Guid> | Per-tenant installation record with settings JSON |
| PluginPermissionGrant | AuditableEntity<Guid> | Consent record for a permission granted to a plugin |
| PluginDataStore | AuditableEntity<Guid> | Tier 2 key-value sandbox (namespace + key + value JSON) |
| PluginApiKey | AuditableEntity<Guid> | SHA-256 hashed API key for gateway authentication |
| PluginWebhookSubscription | AuditableEntity<Guid> | Webhook subscription to platform events |
| PluginExecutionLog | Entity<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.
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
# PluginHealthBadgePlugin 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.
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 remotesAPI Endpoints
All plugin endpoints are under /api/v1/plugins/ for admin operations and /api/v1/plugin-api/v1/ for the sandboxed Tier 2 gateway.
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/v1/plugins/catalog | Browse 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/installed | List all plugins installed for a tenant | 🔒 Required |
| POST | /api/v1/plugins/install | Install 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}/activate | Activate a disabled plugin installation | 🔒 Required |
| POST | /api/v1/plugins/installed/{id}/deactivate | Deactivate an active plugin installation | 🔒 Required |
| POST | /api/v1/plugins/installed/{id}/upgrade | Upgrade an installation to a new version | 🔒 Required |
| PUT | /api/v1/plugins/installed/{id}/settings | Update the JSON settings for an installation | 🔒 Required |
| GET | /api/v1/plugins/installed/{id}/logs | Get paginated execution logs (page + pageSize) | 🔒 Required |
| POST | /api/v1/plugins/definitions | Register a new plugin definition (super admin only) | 🔒 Required |
| GET | /api/v1/plugins/context/tenant | Get tenant profile and enabled features for plugin context | 🔒 Required |
| POST | /api/v1/plugins/webhooks/subscribe | Subscribe 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/exchange | Exchange 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.
| Event | Trigger | Description |
|---|---|---|
| plugin.installed | InstallPluginCommandHandler success | Fired after a plugin is successfully installed for a tenant |
| plugin.uninstalled | UninstallPluginCommandHandler + cleanup | Fired after uninstall and data store cleanup completes |
| plugin.activated | ActivatePluginCommand success | Fired when an installation status changes to Active |
| plugin.deactivated | DeactivatePluginCommand success | Fired when an installation status changes to Disabled |
| plugin.upgraded | UpgradePluginCommand success | Fired when an installation is upgraded to a new version |
| plugin.health_failed | PluginHealthCheckJob | Fired when a health check returns non-2xx for an active plugin |
| plugin.rate_limit_exceeded | PluginSandbox.IsAllowed() = false | Fired 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 ID | Schedule | Description |
|---|---|---|
| plugins-soft-delete-cleanup | Daily at 03:00 | Hard-deletes soft-deleted plugin entities older than 30 days |
| plugins-health-check | Every 5 minutes | Calls GET {baseUrl}/health for every active Tier 2 installation |
| plugins-data-cleanup | Daily at 02:00 | Removes 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 Key | Grants Access To |
|---|---|
| plugins_catalog.view | Browse published plugin catalog |
| plugins_catalog.install | Install plugins for a tenant |
| plugins_catalog.uninstall | Uninstall plugins from a tenant |
| plugins_installed.view | View installed plugins list |
| plugins_installed.manage | Activate, deactivate, upgrade, update settings |
| plugins_execution_logs.view | View execution logs for an installation |
| plugins_definition.create | Register new plugin definitions (platform admin) |
| plugins_permissions.manage | Grant and revoke plugin permissions |
| plugins_webhooks.manage | Subscribe and unsubscribe webhook events |
Quick Start Guide
Run Database Migration
Create the Plugins module database and apply migrations using the SCRIPE CLI.Shellscripe db add-migration Initial -m Plugins scripe db update -m PluginsRegister a Plugin Definition
Register your plugin in the catalog by calling the definitions endpoint as super admin.JSONPOST /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\"}" }Install for a Tenant
Install the plugin for a specific tenant using the install endpoint.JSONPOST /api/v1/plugins/install { "pluginDefinitionId": "<definitionId>", "tenantId": "<tenantId>", "installedByUserId": "<userId>" }Activate the Installation
Activate the installation to make it available to users.HTTPPOST /api/v1/plugins/installed/{installationId}/activate?tenantId={tenantId}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