# Plugin SDK Reference
## SDK Identity
Current SDK pair:
- `sdkVersion: 1`
- `sdkRevision: 4`
`sdkVersion` is the API level.
`sdkRevision` is the contract revision within that API level. Revisions let WPrint 3D evolve the SDK without immediately forcing a full API-level jump.
## Platform Communication Model
WPrint 3D keeps the host in control of plugin discovery, execution, UI mounting, and hardware access. Plugins do not talk to serial devices, cameras, USB discovery, or host UI primitives directly. They communicate through host-owned APIs, actions, hooks, effects, and declared UI surfaces.
### System Diagram
```mermaid
flowchart LR
User[User] --> FE[Frontend host shell
Expo / React Native]
FE --> UIHost[PluginHostRenderer
declarative / WebView / custom bundle]
FE --> API[Plugin API
Laravel controllers]
API --> PM[PluginManagerService]
PM --> Registry[Plugin registry client]
PM --> Dependencies[Plugin dependency service
images / requirements / managed services]
PM --> Assets[Plugin asset resolver]
PM --> Actions[Action dispatcher]
Actions --> RuntimeRegistry[PluginRuntimeRegistry]
RuntimeRegistry --> PhpRuntime[PHP runtime adapter]
RuntimeRegistry --> BridgeRuntime[Bridge runtime adapter]
PhpRuntime --> PhpPlugin[PHP plugin package]
BridgeRuntime --> BridgePlugin[Bridge service plugin]
AppBoot[App boot] --> OneShot[PluginHookDispatcher]
PrintJob[Print job lifecycle] --> OneShot
Serial[Serial driver] --> HookCompiler[PluginHookCompiler]
Camera[Hardware camera driver] --> HookCompiler
HookCompiler --> CompiledHooks[Precompiled hook closures]
OneShot --> RuntimeRegistry
CompiledHooks --> RuntimeRegistry
RuntimeRegistry --> Effects[PluginEffectExecutor]
Effects --> Queue[Queue printer command]
Effects --> Toast[Toast / log / host feedback]
Dependencies --> ContainerRuntime[Docker host runtime]
Queue --> Serial
Serial --> USB[USB serial device / printer firmware]
Camera --> Capture[fswebcam / libcamera / v4l2]
UIHost --> Assets
Assets --> UIBundle[Plugin assets and browser modules]
```
### Hook Execution Flow
```mermaid
sequenceDiagram
participant Driver as Serial / Camera / Print job
participant Compiler as PluginHookCompiler
participant Hook as Compiled hook closure
participant Registry as PluginRuntimeRegistry
participant Plugin as PHP or bridge plugin
participant Effects as PluginEffectExecutor
participant Host as Host runtime / hardware
Driver->>Compiler: compile hook set once at boundary
Compiler-->>Driver: hookName -> plain closure
Driver->>Hook: invoke(context)
Hook->>Registry: use captured runtime adapter
Registry->>Plugin: invoke hook payload
Plugin-->>Hook: result + effects
Hook->>Effects: execute allowed effects
Effects->>Host: queue command / toast / log
```
### Frontend Flow
```mermaid
sequenceDiagram
participant User
participant App as Frontend host shell
participant UI as PluginHostRenderer
participant API as Backend plugin API
participant Plugin as Plugin runtime
User->>App: Open Settings / navbar / printer page
App->>API: fetch plugin inventory + UI extensions
API-->>App: manifest-derived extension metadata
App->>UI: mount declared surface
alt declarative or remote_component
UI-->>User: host-rendered React Native surface
else webview or custom_bundle
UI->>API: resolve authenticated asset URL + theme metadata
API-->>UI: host asset endpoint
UI-->>User: isolated browser surface
end
User->>UI: trigger action
UI->>API: POST action payload
API->>Plugin: invoke action
Plugin-->>API: result + effects
API-->>UI: normalized payload
UI-->>User: updated UI / toast / queued behavior
```
### Hardware Boundaries
- Printer communication stays inside the host serial driver.
- Camera capture stays inside host camera tooling.
- USB discovery stays inside the host mapper/runtime environment.
- Plugins can observe and influence host behavior only through approved hooks, actions, and effects.
- Bridge plugins can live outside the default host process, but they still receive host-shaped payloads rather than raw hardware access.
## Manifest Fields
### Required
- `id`
- `name`
- `version`
- `sdkVersion`
- `runtime`
### Recommended
- `sdkRevision`
- `description`
- `author`
- `homepageUrl`
- `documentationUrl`
- `sourceUrl`
- `minCoreVersion`
- `icon`
- `permissions`
- `actions`
- `uiExtensions`
- `signature`
- `assets`
- `components`
- `updateSource`
- `requirements`
- `images`
- `i18n`
### Plugin Translation Files
Plugins may declare optional host-managed translation files:
```json
"i18n": {
"defaultLocale": "en",
"files": {
"es": "asset://translations/es.json",
"es_AR": "asset://translations/es.json"
}
}
```
Rules:
- `i18n.defaultLocale` is optional and defaults to the host fallback locale.
- `i18n.files` keys are locale tags such as `es`, `fr`, or `es_AR`.
- Each file reference must point at a declared manifest asset.
- Files must be JSON objects.
Supported translation sections:
- `plugin`
- `actions`
- `uiExtensions`
- `components`
Browser-based plugin UIs also receive:
- `locale`
- `fallbackLocale`
as query parameters on the embedded asset URL.
### Signature Schema
Unsigned manifests use:
```json
"signature": {
"algorithm": "none"
}
```
Signed release packages use:
```json
"signature": {
"algorithm": "openssl-sha256",
"keyId": "sha1-of-embedded-public-key",
"publicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n",
"publicKeySha256": "sha256-of-embedded-public-key",
"value": "base64-signature"
}
```
Notes:
- `plugin:pack --signing-key=...` signs the canonical manifest payload with SHA-256.
- The embedded `publicKey` is there for transparency and offline verification.
- Host trust still depends on configured or synced trusted keys, not on blindly trusting the package’s embedded key.
### Canonical Plugin URLs
Public plugins should declare these manifest fields so registry entries and landing pages do not guess URLs from the monorepo:
- `homepageUrl`: the canonical repository or project homepage
- `documentationUrl`: the primary install or usage docs page
- `sourceUrl`: the canonical source repository
All three must be absolute `http` or `https` URLs when present.
## Runtime Contracts
### PHP Runtime
Manifest:
```json
"runtime": {
"type": "php",
"entry": "plugin.php"
}
```
Actions and hooks point at relative PHP files such as:
- `actions/ping.php`
- `hooks/on_boot.php`
The host invokes them with JSON on `STDIN`.
Available environment variables:
- `WPRINT3D_PLUGIN_ID`
- `WPRINT3D_PLUGIN_STORAGE_PATH`
- `WPRINT3D_BASE_PATH`
- `WPRINT3D_VENDOR_AUTOLOAD`
### Bridge Runtime
Manifest:
```json
"runtime": {
"type": "bridge",
"baseUrl": "http://bridge-service:9310",
"healthcheck": "/health"
}
```
Or, for a host-managed bridge service:
```json
"runtime": {
"type": "bridge",
"managedImageId": "metrics-service",
"healthcheck": "/health"
}
```
The host performs:
- `GET /health` when enabling the plugin
- `POST` to hook/action paths for runtime work
- starts the managed service container automatically when `managedImageId` is declared
Action payload:
```json
{
"kind": "action",
"action": "host_metrics",
"payload": {},
"context": {
"userId": "...",
"printerId": null
}
}
```
Hook payload:
```json
{
"kind": "hook",
"hook": "app.boot",
"context": {}
}
```
### Heavyweight Dependencies
Manifest:
```json
"requirements": {
"memoryMb": 1024,
"cpuCores": 2
},
"images": [
{
"id": "metrics-service",
"image": "ghcr.io/acme/metrics-service:1.2.3",
"engine": "auto",
"healthcheck": {
"command": ["curl", "-f", "http://127.0.0.1:9310/health"],
"timeoutSecs": 15
},
"service": {
"port": 9310,
"networkAlias": "acme-metrics"
}
}
]
```
Rules:
- No `images` means the plugin is `lightweight`.
- Any declared image makes the plugin `heavyweight`.
- Install/update pulls declared images automatically.
- `healthcheck.command` is optional.
- `requirements.memoryMb` and `requirements.cpuCores` are advisory host checks that surface warnings in the UI when the current system is below the plugin's declared minimum target.
- `runtime.managedImageId` can point at an image with `service.port` so WPrint 3D can run a self-contained bridge sidecar.
## OctoPrint Compatibility Layer
The current SDK revision adds the compatibility primitives needed to port most OctoPrint plugins with an AI agent instead of a full manual rewrite.
### Mixin Mapping
| OctoPrint concept | WPrint 3D equivalent |
| --- | --- |
| `SettingsPlugin.get_settings_defaults()` | `plugin.json -> settings.defaults` |
| `on_settings_save()` | `PUT /api/plugins/{id}/settings` plus runtime action reads |
| `TemplatePlugin` navbar/settings templates | `uiExtensions` with `surface: "navbar_widget"` and `surface: "settings_tab"` |
| `AssetPlugin` static files | manifest `assets` resolved through `asset://...` |
| `_plugin_manager.send_plugin_message(...)` | effect `send_plugin_message` or `publish_state` |
| `SimpleApiPlugin` AJAX calls | action endpoints under `/api/plugins/{id}/actions/{actionId}` |
| Knockout view models reading plugin state | `/api/plugins/{id}/state` or `octoprint-compat.js` `watchState()` |
### Settings And State Endpoints
Authenticated plugin surfaces can now rely on:
- `GET /api/plugins/{pluginId}/settings`
- `PUT /api/plugins/{pluginId}/settings`
- `GET /api/plugins/{pluginId}/state`
- `GET /api/plugins/{pluginId}/logs`
Settings are seeded from `plugin.json -> settings.defaults` and persisted per installed plugin record. State is updated by runtime effects such as `send_plugin_message`.
When a plugin exposes a `settings_tab`, WPrint 3D wraps the surface in a host-owned settings shell. That shell now defaults to a collapsed plugin-details hero so the plugin's actual settings UI stays visible immediately; users can expand the header to inspect metadata, trust badges, and warnings on demand.
### Graceful Load Failure Model
Plugin startup is no longer treated as all-or-nothing process termination for the host.
Installed plugins now expose a load state:
- `disabled`
- `ready`
- `failed`
When startup or development-source synchronization fails, WPrint 3D:
- keeps the host running
- marks the plugin as `failed`
- stores `lastError`
- records lifecycle log entries
- keeps the failure visible to the frontend through the installed-plugin payload
That is the preferred pattern for future runtime additions too: record failure state on the plugin record instead of letting plugin-specific exceptions masquerade as host crashes.
### Browser Helper For Ported Plugins
`GET /api/plugins/sdk/octoprint-compat.js` exposes `window.WPrint3DOctoPrintCompat`.
It provides:
- `fromWindow()` to build a host bridge from query-string metadata injected by WPrint 3D
- `getSettings()`
- `saveSettings(settings)`
- `getState()`
- `invokeAction(actionId, payload)`
- `watchState(callback, options)`
- `getTheme()`
This is the preferred bridge for ported OctoPrint settings pages that previously depended on Knockout models or direct AJAX helpers.
### Native Navbar Ports
OctoPrint navbar plugins should prefer a host-rendered `data_strip` widget instead of recreating the entire navbar inside a WebView. On `surface: "navbar_widget"`, the host renders `data_strip` as an inline telemetry lane that expands across the center navbar slot so ports like NavbarTemp feel native to the shell instead of looking like detached chip stacks.
Example declarative schema:
```json
{
"component": "data_strip",
"dataActionId": "snapshot",
"pollIntervalMs": 10000,
"itemsPath": "items",
"gap": 4
}
```
The action should return:
```json
{
"data": {
"items": [
{
"id": "tool-0",
"icon": "printer-3d-nozzle",
"text": "Tool: 205.0°C"
}
]
},
"effects": [
{
"type": "send_plugin_message",
"merge": false,
"data": {
"items": [
{
"id": "tool-0",
"text": "Tool: 205.0°C"
}
]
}
}
]
}
```
That gives you:
- native theme parity in the WPrint 3D navbar
- browser settings pages that can still preview the same state
- a single action result that updates both the host-rendered widget and the browser-side settings panel
### PHP Runtime Bootstrap For Ports
If a port needs access to Laravel models, configuration, or host services, the PHP runtime now exposes:
- `WPRINT3D_BOOTSTRAP_APP`
Typical pattern:
```php
$bootstrapPath = getenv('WPRINT3D_BOOTSTRAP_APP');
if ($bootstrapPath && is_file($bootstrapPath)) {
$app = require $bootstrapPath;
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
}
```
That is how the `OctoPrint NavbarTemp Port` reads the active printer state and SBC temperature from host-owned services without bypassing the plugin runtime contract.
## Supported Permissions
- `printer.read`
- `printer.command.queue`
- `camera.read`
- `host.metrics.read`
- `network.outbound`
- `storage.read`
- `storage.write`
- `ui.settings_tab`
- `ui.navbar_widget`
- `ui.printer_panel`
- `ui.printer_action`
- `ui.modal`
- `ui.page`
- `ui.webview`
- `ui.custom_bundle`
## Supported Hooks
- `app.boot`
- `serial.command.before_send`
- `serial.command.response_received`
- `serial.line.received`
- `camera.snapshot.before_take`
- `camera.snapshot.after_take`
- `print.job.started`
- `print.job.failed`
- `print.job.finished`
Hot serial and camera hooks are compiled once into plain closures and reused inside their loops. One-shot lifecycle hooks such as `app.boot` still use the regular dispatcher path because they are not performance-sensitive.
## Supported Surfaces
- `settings_tab`
- `navbar_widget`
- `printer_panel`
- `printer_action`
- `modal`
- `page`
## UI Modes
### Declarative
Required field:
- `schema`
Supported host-rendered components:
- `host.section`
- `host.card`
- `host.surface`
- `host.stack`
- `host.column`
- `host.row`
- `host.scroll`
- `host.text`
- `host.heading`
- `host.caption`
- `host.divider`
- `host.list`
- `host.key_value`
- `host.button`
- `host.input`
- `host.switch`
- `host.form`
- `host.chip_group`
- `host.badge`
- `host.progress`
- `host.progress_cluster`
- `host.data_strip`
- `host.spacer`
- `host.remote_component`
Legacy short names such as `section`, `text`, `button`, `progress_cluster`, and `remote_component` remain supported for backward compatibility, but new plugins should prefer the `host.*` ids.
`host.progress_cluster` fields:
- `dataActionId`
- `pollIntervalMs`
- `items[]`
Each item supports:
- `id`
- `label`
- `valueKey`
### Host Component Registry
The declarative host path is now a stable component registry rather than a handful of ad hoc renderer cases.
Rules:
- Prefer `host.*` component ids in new manifests.
- Treat the documented fields as the supported API surface.
- Do not assume every underlying React Native Paper prop is exposed to plugins.
- Use `webview` or `custom_bundle` when the host registry is not sufficient.
The current registry covers the common host-safe primitives plugin authors need across web and native:
- layout: `host.stack`, `host.row`, `host.surface`, `host.scroll`, `host.spacer`
- typography: `host.text`, `host.heading`, `host.caption`
- actions and forms: `host.button`, `host.input`, `host.switch`, `host.form`
- display: `host.section`, `host.card`, `host.list`, `host.key_value`, `host.badge`, `host.chip_group`, `host.progress`
- dynamic widgets: `host.progress_cluster`, `host.data_strip`, `host.remote_component`
Example:
```json
{
"component": "host.stack",
"gap": 10,
"children": [
{
"component": "host.heading",
"variant": "titleMedium",
"text": "Host component registry"
},
{
"component": "host.caption",
"text": "This UI is rendered by the host on web and native."
},
{
"component": "host.chip_group",
"items": [
{
"text": "settings_tab",
"icon": "cog-outline"
},
{
"text": "declarative",
"icon": "view-dashboard-outline"
}
]
},
{
"component": "host.button",
"label": "Run action",
"actionId": "ping"
}
]
}
```
### WebView
Required field:
- `url`
Use `asset://ui/settings.html` for plugin-packaged assets.
### Custom Bundle
Required field:
- `bundle.url`
Use `asset://ui/settings.html` for plugin-packaged assets unless you intentionally host the bundle elsewhere.
## Manifest Components
The top-level `components` field declares reusable plugin components.
Example:
```json
"components": [
{
"id": "hostMetricsCard",
"kind": "browser_module",
"entry": "asset://components/host-metrics-card.js",
"exports": "mount"
}
]
```
Supported today:
- `kind: "remote_component"` for host-rendered declarative UI on web and native
- `kind: "browser_module"`
- `browser_module` use from `webview` and `custom_bundle` surfaces
Not supported today:
- arbitrary host-rendered React components inside declarative schema nodes
UI extensions may reference declared component IDs:
```json
"uiExtensions": [
{
"id": "host-metrics-settings",
"surface": "settings_tab",
"mode": "custom_bundle",
"title": "Host Metrics",
"bundle": {
"url": "asset://ui/settings.html"
},
"components": ["hostMetricsCard"]
}
]
```
Declarative schemas may mount a declared remote component directly:
```json
{
"component": "host.remote_component",
"componentId": "hostMetricsPanel",
"props": {
"title": "Host telemetry",
"description": "This is rendered by the host."
}
}
```
## Asset Rules
- Every plugin-packaged elevated UI entrypoint must be declared in `assets`.
- Every manifest component module must also be declared in `assets`.
- Asset paths must stay inside the plugin runtime directory.
- `..` segments are rejected.
- Asset-backed UI references are rewritten into authenticated host URLs at runtime.
Example:
```json
"assets": [
{
"path": "ui/settings.html"
}
]
```
## Management API
### Inventory And Metadata
- `GET /api/plugins`
- `GET /api/plugins/{pluginId}`
- `GET /api/plugins/sdk`
- `GET /api/plugins/ui?surface=settings_tab`
### Install / Remove / Update
- `POST /api/plugins/install`
- `POST /api/plugins/{pluginId}/enable`
- `POST /api/plugins/{pluginId}/disable`
- `POST /api/plugins/{pluginId}/update`
- `DELETE /api/plugins/{pluginId}`
### Runtime
- `POST /api/plugins/{pluginId}/actions/{actionId}`
- `GET /api/plugins/{pluginId}/assets/{assetPath}`
Action calls are always host-mediated:
1. frontend or host code requests an action
2. the backend resolves the installed manifest
3. the runtime adapter invokes the plugin
4. the host executes any permitted returned effects
5. the caller receives normalized result data
### Registry
- `GET /api/plugins/registry`
- `GET /api/plugins/registry/sources`
- `PUT /api/plugins/registry/sources`
### Development
- `GET /api/plugins/development`
- `POST /api/plugins/install` with `unpackedPath`
## CLI
- From the host checkout, prefer `./plugin.sh ` so the plugin Artisan commands run inside the backend container.
- `php artisan plugin:make`
- `php artisan plugin:pack`
- `php artisan plugin:verify`
- `php artisan plugin:restore`
- `php artisan plugin:publish`
- `php artisan plugin:install`
- `php artisan plugin:search`
- `php artisan plugin:list`
- `php artisan plugin:enable`
- `php artisan plugin:disable`
- `php artisan plugin:remove`
- `php artisan plugin:update`
- `php artisan plugin:doctor`
`plugin:make` is now interactive and can scaffold any runtime/UI shape plus optional heavyweight image metadata. Use `--shape`, `--image`, `--memory`, and `--cpu` when you want a fully non-interactive generator.
`plugin:pack ` writes to `/builds/.w3dp` by default. Use `--output` only when you need a custom location.
`plugin:verify ` checks the embedded signer metadata and can fail closed with `--require-trusted`.
`plugin:restore ` expands a package back into a source tree, which is useful for forks, incident response, and restoring packages that disappeared from the registry.
Signing helpers from the repo root:
- `./plugin.sh keygen`
- `./plugin.sh pack --wizard`
- `./plugin.sh verify `
- `./plugin.sh verify --require-trusted`
- `./plugin.sh restore --output plugins/`
- `python3 scripts/plugin_sign.py --private-key=/path/to/key.pem`
- `python3 scripts/plugin_verify_signature.py inspect `
- `python3 scripts/plugin_verify_signature.py verify `
## Host Theme Metadata For Elevated UI
Elevated UI entrypoints receive query parameters for:
- `pluginId`
- `pluginName`
- `extensionId`
- `extensionMode`
- `pluginApiBase`
- `actionId`
- `components`
- `componentIds`
- `theme`
The `theme` value is JSON and includes the current Paper theme color tokens such as:
- `primary`
- `secondary`
- `tertiary`
- `surface`
- `surfaceVariant`
- `background`
- `onSurface`
- `onSurfaceVariant`
- `outline`
- `outlineVariant`
- `error`
Elevated browser surfaces also receive the plugin API base URL and declared component metadata so they can call host actions and load manifest-declared browser modules without bypassing host control.
## Example Use Cases
- `php + declarative`: compact settings panels, navbar widgets, quick actions
- `php + webview`: lightweight HTML dashboards that still use host actions
- `php + custom_bundle`: heavier UI with richer client logic but local PHP runtime
- manifest-declared browser components: asset-backed JS modules mounted by elevated UI shells
- `bridge + declarative`: external integrations rendered in host-owned cards
- `bridge + webview`: full external-service integrations with isolated HTML UI
- `bridge + custom_bundle`: richest integration path when both runtime and UI live outside the default host path