Plugin SDK Reference
SDK Identity
Current SDK pair:
sdkVersion: 1sdkRevision: 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
flowchart LR
User[User] --> FE[Frontend host shell<br/>Expo / React Native]
FE --> UIHost[PluginHostRenderer<br/>declarative / WebView / custom bundle]
FE --> API[Plugin API<br/>Laravel controllers]
API --> PM[PluginManagerService]
PM --> Registry[Plugin registry client]
PM --> Dependencies[Plugin dependency service<br/>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
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
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
idnameversionsdkVersionruntime
Recommended
sdkRevisiondescriptionauthorhomepageUrldocumentationUrlsourceUrlminCoreVersioniconpermissionsactionsuiExtensionssignatureassetscomponentsupdateSourcerequirementsimagesi18n
Plugin Translation Files
Plugins may declare optional host-managed translation files:
"i18n": {
"defaultLocale": "en",
"files": {
"es": "asset://translations/es.json",
"es_AR": "asset://translations/es.json"
}
}
Rules:
i18n.defaultLocaleis optional and defaults to the host fallback locale.i18n.fileskeys are locale tags such ases,fr, ores_AR.Each file reference must point at a declared manifest asset.
Files must be JSON objects.
Supported translation sections:
pluginactionsuiExtensionscomponents
Browser-based plugin UIs also receive:
localefallbackLocale
as query parameters on the embedded asset URL.
Signature Schema
Unsigned manifests use:
"signature": {
"algorithm": "none"
}
Signed release packages use:
"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
publicKeyis 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 homepagedocumentationUrl: the primary install or usage docs pagesourceUrl: the canonical source repository
All three must be absolute http or https URLs when present.
Runtime Contracts
PHP Runtime
Manifest:
"runtime": {
"type": "php",
"entry": "plugin.php"
}
Actions and hooks point at relative PHP files such as:
actions/ping.phphooks/on_boot.php
The host invokes them with JSON on STDIN.
Available environment variables:
WPRINT3D_PLUGIN_IDWPRINT3D_PLUGIN_STORAGE_PATHWPRINT3D_BASE_PATHWPRINT3D_VENDOR_AUTOLOAD
Bridge Runtime
Manifest:
"runtime": {
"type": "bridge",
"baseUrl": "http://bridge-service:9310",
"healthcheck": "/health"
}
Or, for a host-managed bridge service:
"runtime": {
"type": "bridge",
"managedImageId": "metrics-service",
"healthcheck": "/health"
}
The host performs:
GET /healthwhen enabling the pluginPOSTto hook/action paths for runtime workstarts the managed service container automatically when
managedImageIdis declared
Action payload:
{
"kind": "action",
"action": "host_metrics",
"payload": {},
"context": {
"userId": "...",
"printerId": null
}
}
Hook payload:
{
"kind": "hook",
"hook": "app.boot",
"context": {}
}
Heavyweight Dependencies
Manifest:
"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
imagesmeans the plugin islightweight.Any declared image makes the plugin
heavyweight.Install/update pulls declared images automatically.
healthcheck.commandis optional.requirements.memoryMbandrequirements.cpuCoresare advisory host checks that surface warnings in the UI when the current system is below the plugin’s declared minimum target.runtime.managedImageIdcan point at an image withservice.portso 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 |
|---|---|
|
|
|
|
|
|
|
manifest |
|
effect |
|
action endpoints under |
Knockout view models reading plugin state |
|
Settings And State Endpoints
Authenticated plugin surfaces can now rely on:
GET /api/plugins/{pluginId}/settingsPUT /api/plugins/{pluginId}/settingsGET /api/plugins/{pluginId}/stateGET /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:
disabledreadyfailed
When startup or development-source synchronization fails, WPrint 3D:
keeps the host running
marks the plugin as
failedstores
lastErrorrecords 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 3DgetSettings()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.
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:
$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.readprinter.command.queuecamera.readhost.metrics.readnetwork.outboundstorage.readstorage.writeui.settings_tabui.navbar_widgetui.printer_panelui.printer_actionui.modalui.pageui.webviewui.custom_bundle
Supported Hooks
app.bootserial.command.before_sendserial.command.response_receivedserial.line.receivedcamera.snapshot.before_takecamera.snapshot.after_takeprint.job.startedprint.job.failedprint.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_tabnavbar_widgetprinter_panelprinter_actionmodalpage
UI Modes
Declarative
Required field:
schema
Supported host-rendered components:
host.sectionhost.cardhost.surfacehost.stackhost.columnhost.rowhost.scrollhost.texthost.headinghost.captionhost.dividerhost.listhost.key_valuehost.buttonhost.inputhost.switchhost.formhost.chip_grouphost.badgehost.progresshost.progress_clusterhost.data_striphost.spacerhost.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:
dataActionIdpollIntervalMsitems[]
Each item supports:
idlabelvalueKey
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
webvieworcustom_bundlewhen 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.spacertypography:
host.text,host.heading,host.captionactions and forms:
host.button,host.input,host.switch,host.formdisplay:
host.section,host.card,host.list,host.key_value,host.badge,host.chip_group,host.progressdynamic widgets:
host.progress_cluster,host.data_strip,host.remote_component
Example:
{
"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:
"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 nativekind: "browser_module"browser_moduleuse fromwebviewandcustom_bundlesurfaces
Not supported today:
arbitrary host-rendered React components inside declarative schema nodes
UI extensions may reference declared component IDs:
"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:
{
"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:
"assets": [
{
"path": "ui/settings.html"
}
]
Management API
Inventory And Metadata
GET /api/pluginsGET /api/plugins/{pluginId}GET /api/plugins/sdkGET /api/plugins/ui?surface=settings_tab
Install / Remove / Update
POST /api/plugins/installPOST /api/plugins/{pluginId}/enablePOST /api/plugins/{pluginId}/disablePOST /api/plugins/{pluginId}/updateDELETE /api/plugins/{pluginId}
Runtime
POST /api/plugins/{pluginId}/actions/{actionId}GET /api/plugins/{pluginId}/assets/{assetPath}
Action calls are always host-mediated:
frontend or host code requests an action
the backend resolves the installed manifest
the runtime adapter invokes the plugin
the host executes any permitted returned effects
the caller receives normalized result data
Registry
GET /api/plugins/registryGET /api/plugins/registry/sourcesPUT /api/plugins/registry/sources
Development
GET /api/plugins/developmentPOST /api/plugins/installwithunpackedPath
CLI
From the host checkout, prefer
./plugin.sh <command>so the plugin Artisan commands run inside the backend container.php artisan plugin:makephp artisan plugin:packphp artisan plugin:verifyphp artisan plugin:restorephp artisan plugin:publishphp artisan plugin:installphp artisan plugin:searchphp artisan plugin:listphp artisan plugin:enablephp artisan plugin:disablephp artisan plugin:removephp artisan plugin:updatephp 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 <plugin-path> writes to <plugin-path>/builds/<plugin-dir>.w3dp by default. Use --output only when you need a custom location.
plugin:verify <package.w3dp> checks the embedded signer metadata and can fail closed with --require-trusted.
plugin:restore <package.w3dp> 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 <plugin-path> --wizard./plugin.sh verify <package.w3dp>./plugin.sh verify <package.w3dp> --require-trusted./plugin.sh restore <package.w3dp> --output plugins/<fork-name>python3 scripts/plugin_sign.py <plugin-path> --private-key=/path/to/key.pempython3 scripts/plugin_verify_signature.py inspect <package.w3dp>python3 scripts/plugin_verify_signature.py verify <package.w3dp>
Host Theme Metadata For Elevated UI
Elevated UI entrypoints receive query parameters for:
pluginIdpluginNameextensionIdextensionModepluginApiBaseactionIdcomponentscomponentIdstheme
The theme value is JSON and includes the current Paper theme color tokens such as:
primarysecondarytertiarysurfacesurfaceVariantbackgroundonSurfaceonSurfaceVariantoutlineoutlineVarianterror
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 actionsphp + webview: lightweight HTML dashboards that still use host actionsphp + custom_bundle: heavier UI with richer client logic but local PHP runtimemanifest-declared browser components: asset-backed JS modules mounted by elevated UI shells
bridge + declarative: external integrations rendered in host-owned cardsbridge + webview: full external-service integrations with isolated HTML UIbridge + custom_bundle: richest integration path when both runtime and UI live outside the default host path