This is part 6 of the Your First Plugin tutorial. Make sure you’ve completed 5. Working with Files first.
Node configuration with UI controls
Add configuration fields that appear in the node’s settings panel in the editor:call():
Available config display types
Every widget takes alabel; the argument column shows what else each accepts.
Dynamic configuration
Overrideget_config() to compute config server-side at edit time — for
example, fetching a select’s options from an upstream API. It runs in the
plugin’s worker; mutate the ConfigResponse and return it. Write dynamic
options into config_response.config_values keyed by the field name:
The platform only wakes the worker for
get_config when a ConfigSelect has no static values. Nodes with fully static config never pay the round-trip, so add get_config only when options genuinely depend on context.Polling triggers
A plugin can also emit events on a schedule withBasePollingTrigger. The
platform owns scheduling, state persistence, and turning events into runs — your
trigger only implements poll(ctx, state), returning (events, new_state).
Cursor state round-trips between polls so you emit each event once:
outputs you declared). A trigger can also declare integrations = ["weather_api"]
to require a connected credential.
List handling
When a list output connects to a non-list input, the platform runs the node once per item automatically. To process the whole list in a single run, declare the input as a list instead. On a V2 node that’s a bindablelist[...] config
field:
Plugin-level configuration
UsePluginConfiguration for settings that apply to the entire plugin (not per-node). These are set in Settings → Plugins → Configure:
ctx.plugin_config is a plain dict:
Error handling
Raise the SDK’s typed errors fromnoxus_sdk.errors. The message crosses the
JSON-RPC sandbox boundary and becomes the node’s user-visible failure reason —
the exception type itself does not survive, so make every message actionable:
- Use
IntegrationFailedErrorfor credential/API/user-fixable problems. - Use
UnexpectedErrorfor failures you did not anticipate. - Degrade, don’t raise, for expected external flakiness. A raised exception fails the whole run. For flaky network or parsing, catch it and return an empty typed result; reserve exceptions for genuine misconfiguration (e.g. no credential connected). The Proxy API scrapers do this — a failed scrape yields an empty CSV rather than crashing the flow.
- Chain with
from eand include actionable information in the message.
Runtime & sandbox gotchas
Your code runs as a warm worker inside a sandbox VM, reached over JSON-RPC. That environment imposes a few rules the local dev server does not:-
stdout is the protocol. The worker speaks JSON-RPC on stdout; anything you (or a dependency)
print()there corrupts the channel and breaks the call. Redirect noisy code to stderr: -
Import heavy deps lazily, inside
call/helpers — not at module top. Manifest generation andnoxus plugin validateimport your module in a lightweight environment; a top-levelimport pandas/playwright/curl_cffimakesget_manifest()fail withModuleNotFoundErroreven though the sandbox has the dep. Put the import in the function that uses it. - Degrade, don’t raise, for expected external failures (see above).
-
Don’t shadow a package with a same-named module. A
weather.pynext to aweather/package makesimport your_pkg.weatherresolve to the package — the module becomes unreachable. Name adapters distinctly (weather_adapter.py). -
Zip only source. Exclude
.venv,__pycache__,.git,node_modules. An accidental.venvcan balloon the archive from ~300 KB to tens of MB. -
Browser / JIT plugins can’t run under the default
sydsandbox.syd’s seccomp filter SIGSYS-kills V8’s JIT, so Playwright / Chromium / any Node or JIT-heavy workload dies on startup — even when you only drive a remote browser, the local Playwright Node driver still runs. Those plugins need thegvisorsandbox provider (Linux/EKS) ornone(local dev — gVisor can’tpivot_rootinside Docker Desktop on macOS). This is a deployment setting (SANDBOX_PROVIDER), but know it before shipping a browser plugin. -
Keep
noxus-sdk>=aligned with the platform. The sandbox resolvesnoxus-sdkalongside yourpyproject.tomldeps; pinning an older SDK can win and the worker fails to start. Depend onnoxus-sdkat or above the platform’s version, not an old floor.
Deploy your plugin
Option 1: From a Git repository
Push your plugin to a Git repository, then install from the Noxus UI:- Go to Settings → Plugins → Install Plugin
- Choose Git source
- Enter your repository URL, branch, and path (if the plugin is in a subdirectory)
- For private repos, provide an access token
Option 2: Upload directly
Package and upload:.tar.gz file through the Noxus UI.
Option 3: Marketplace
Publish to the Noxus plugins marketplace for public distribution.Verify installation
Once installed, you should see:- Your plugin listed with status Running in Settings → Plugins
- Your nodes available in the flow editor palette
- Your integration available in workspace control for credential configuration
Complete example
Here’s the fullweather_plugin/__init__.py putting everything together:
Examples
Browse more code examples
Architecture
Understand how plugins run under the hood