Plugins
The extension model: registering a plugin, declaring configuration, hooking into platform events and shipping it safely.
On this page
Plugins run inside the platform. They are how connectors, analytics integrations and custom workflow steps are built — including ours, which use exactly the surface described here.
Reach for a plugin when logic needs to run on platform events with platform data. Reach for webhooks and the API when the logic belongs in a system you already own.
Registering
A plugin declares an id, the capabilities it provides, a configuration schema and its handlers.
registerPlugin({
id: 'warehouse-priority',
name: 'Warehouse priority routing',
capabilities: ['orders.route'],
config: {
priority: { type: 'array', items: { type: 'string' }, label: 'Location priority' },
allowSplit: { type: 'boolean', default: false, label: 'Allow split shipments' }
},
handlers: {
'orders.route': async ({ order, locations, config }) => {
const ranked = config.priority
.map(id => locations.find(location => location.id === id))
.filter(Boolean)
return assign(order, ranked, { allowSplit: config.allowSplit })
}
}
})
The config schema drives the settings form in the interface. You describe the fields; you do not build the screen.
Capabilities
| Capability | Called when | Typical use |
|---|---|---|
catalog.push | Listings need to reach a channel | Marketplace connectors |
catalog.pull | A channel owns some content | Importing from an existing platform |
inventory.push | Availability changes | Feed-based channels |
orders.pull | New orders are fetched | Marketplace order ingestion |
orders.route | An order needs locations assigned | Custom routing strategies |
pricing.resolve | A price is being calculated | Contract pricing, repricers |
content.transform | Content is rendered for a channel | Title patterns, attribute maps |
analytics.track | A tracked event occurs | Analytics and product telemetry |
A plugin declares only what it implements. Anything it does not declare is never called, which keeps the failure surface small and legible.
Configuration and secrets
Configuration is stored per project, so the same plugin serves several brands with different settings. Fields marked as secrets are encrypted at rest and write-only — the interface and the API return a masked placeholder, never the value.
Lifecycle
Runs once per project. Create whatever external resources you need and validate the credentials before reporting success.
Toggling a plugin stops it being called without discarding its configuration. Disable is the recovery path when something misbehaves at 2am.
Your handlers run on platform events, scoped to the project that installed the plugin.
Clean up external resources. Platform data — orders, listings, history — is never deleted by an uninstall.
Rules that keep plugins boring
- Be idempotent. Handlers can be retried after a partial failure; a second run must not create a second record.
- Be quick. Handlers have a time budget. Long work belongs on a queue that you drain on your own schedule.
- Fail with intent. Throw with a clear message; the error reaches the operator with the plugin id attached and lands in the retry queue.
- Log at the boundaries. The outbound request and the response, not every line in between.
Testing
Run the plugin against a sandbox project with a copy of your catalog, exercise it with replayed webhook traffic, and check the audit log rather than your own logs — the audit log is what an operator will read when something goes wrong six months from now.
That is the end of the developer section. If something here is unclear or missing, tell us — these pages are maintained against real integration work, and a question that had to be asked once is usually a page that needs editing.




