Documentation Index Fetch the complete documentation index at: https://docs.noxus.ai/llms.txt
Use this file to discover all available pages before exploring further.
Node configuration with UI controls
Add configuration fields that appear in the node’s settings panel in the editor:
from noxus_sdk.ncl import (
ConfigSelect,
ConfigToggle,
ConfigNumberSlider,
Parameter,
)
class GetWeatherConfig ( NodeConfiguration ):
units: str = Parameter(
default = "metric" ,
title = "Temperature Units" ,
description = "Choose temperature units" ,
display = ConfigSelect( options = [ "metric" , "imperial" , "kelvin" ]),
)
include_humidity: bool = Parameter(
default = False ,
title = "Include Humidity" ,
description = "Also return humidity data" ,
display = ConfigToggle(),
)
Access config values in your node’s call():
async def call ( self , ctx : RemoteExecutionContext, city : str ) -> dict :
units = self .config.units # "metric", "imperial", or "kelvin"
include_humidity = self .config.include_humidity
# ...
Available config display types
Type Description ConfigText()Single-line text input ConfigBigText()Multi-line textarea ConfigSelect(options=[...])Dropdown select ConfigMultiSelect(options=[...])Multi-select dropdown ConfigToggle()Boolean switch ConfigNumber()Number input ConfigNumberSlider(min, max, step)Slider ConfigRichTextVariables()Rich text with variable insertion
Dynamic configuration
Override get_config() to generate options dynamically based on context:
class GetWeatherNode (BaseNode[GetWeatherConfig]):
# ...
@ classmethod
async def get_config ( cls , ctx , config_response , * , skip_cache = False ):
# You could fetch available options from an API,
# populate dropdowns based on credentials, etc.
return config_response
List handling
When a list output connects to a non-list input, the node automatically runs once per item. If you need to process the whole list at once, declare the input as a list:
# This node receives the full list
inputs = [
Connector(
name = "cities" ,
label = "Cities" ,
definition = TypeDefinition( data_type = DataType.str, is_list = True ),
)
]
async def call ( self , ctx , cities : list[ str ]) -> dict :
# Process all cities at once
results = []
for city in cities:
results.append( await fetch_weather(city))
return { "results" : results}
Plugin-level configuration
Use PluginConfiguration for settings that apply to the entire plugin (not per-node). These are set in Settings → Plugins → Configure :
class WeatherPluginConfig ( PluginConfiguration ):
default_units: str = Parameter(
default = "metric" ,
title = "Default Units" ,
display = ConfigSelect( options = [ "metric" , "imperial" ]),
)
cache_ttl: int = Parameter(
default = 300 ,
title = "Cache TTL (seconds)" ,
display = ConfigNumberSlider( min = 60 , max = 3600 , step = 60 ),
)
Access plugin config in any node:
async def call ( self , ctx : RemoteExecutionContext, city : str ) -> dict :
plugin_config = ctx.plugin_config
default_units = plugin_config.get( "default_units" , "metric" )
# ...
Error handling
Raise exceptions with clear messages. The platform captures them and shows them to the user:
async def call ( self , ctx , city : str ) -> dict :
if not city or not city.strip():
raise ValueError ( "City name cannot be empty" )
try :
result = await fetch_weather(city)
except httpx.HTTPStatusError as e:
if e.response.status_code == 404 :
raise ValueError ( f "City ' { city } ' not found" ) from e
raise RuntimeError ( f "Weather API error: { e.response.status_code } " ) from e
return { "temperature" : result[ "temp" ], "description" : result[ "desc" ]}
Best practices:
Use ValueError for input validation errors (user-fixable)
Use RuntimeError for unexpected failures (system errors)
Always chain exceptions with from e for better stack traces
Include actionable information in error messages
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:
noxus plugin package --path ./weather-plugin --output weather-plugin.tar.gz
Then upload the .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 settings for credential configuration
Complete example
Here’s the full weather_plugin/__init__.py putting everything together:
from noxus_sdk.files import File
from noxus_sdk.integrations.base import BaseIntegration, BaseCredentials
from noxus_sdk.ncl import ConfigSelect, ConfigText, ConfigToggle, Parameter
from noxus_sdk.nodes.base import BaseNode, NodeConfiguration
from noxus_sdk.nodes.connector import Connector
from noxus_sdk.nodes.types import DataType, NodeCategory, TypeDefinition
from noxus_sdk.plugins import BasePlugin, PluginConfiguration
from noxus_sdk.plugins.context import RemoteExecutionContext
from noxus_sdk.plugins.types import PluginCategory
# ── Integration ──────────────────────────────────────────────────────
class WeatherAPICredentials ( BaseCredentials ):
type : str = "weather_api"
api_key: str = Parameter(
default = "" , title = "API Key" ,
description = "OpenWeatherMap API key" ,
display = ConfigText(),
)
def is_ready ( self ) -> bool :
return bool ( self .api_key)
class WeatherAPIIntegration (BaseIntegration[WeatherAPICredentials]):
type = "weather_api"
display_name = "Weather API"
image = "https://cdn-icons-png.flaticon.com/512/1779/1779940.png"
# ── Node Configuration ──────────────────────────────────────────────
class GetWeatherConfig ( NodeConfiguration ):
units: str = Parameter(
default = "metric" , title = "Units" ,
display = ConfigSelect( options = [ "metric" , "imperial" ]),
)
include_humidity: bool = Parameter(
default = False , title = "Include Humidity" ,
display = ConfigToggle(),
)
# ── Node ─────────────────────────────────────────────────────────────
class GetWeatherNode (BaseNode[GetWeatherConfig]):
node_name = "get_weather"
title = "Get Weather"
description = "Fetches current weather for a city"
category = NodeCategory. DATA
color = "#4A90E2"
integrations = { "weather_api" : [ "api_key" ]}
inputs = [
Connector(
name = "city" , label = "City" ,
definition = TypeDefinition( data_type = DataType.str),
)
]
outputs = [
Connector(
name = "temperature" , label = "Temperature" ,
definition = TypeDefinition( data_type = DataType.str),
),
Connector(
name = "description" , label = "Description" ,
definition = TypeDefinition( data_type = DataType.str),
),
]
async def call ( self , ctx : RemoteExecutionContext, city : str ) -> dict :
import httpx
creds = ctx.get_integration_credentials( "weather_api" )
api_key = creds.get( "api_key" , "" )
if not api_key:
raise ValueError ( "Weather API key not configured" )
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.openweathermap.org/data/2.5/weather" ,
params = { "q" : city, "appid" : api_key, "units" : self .config.units},
)
response.raise_for_status()
data = response.json()
result = {
"temperature" : f " { data[ 'main' ][ 'temp' ] } ° { 'C' if self .config.units == 'metric' else 'F' } " ,
"description" : data[ "weather" ][ 0 ][ "description" ].capitalize(),
}
if self .config.include_humidity:
result[ "description" ] += f " (Humidity: { data[ 'main' ][ 'humidity' ] } %)"
return result
# ── Plugin ───────────────────────────────────────────────────────────
class WeatherPluginConfig ( PluginConfiguration ):
pass
class WeatherPlugin (BasePlugin[WeatherPluginConfig]):
name = "weather-plugin"
display_name = "Weather Plugin"
version = "0.1.0"
description = "Weather data nodes with OpenWeatherMap integration"
category = PluginCategory. GENERAL
author = "Your Name"
def nodes ( self ):
return [GetWeatherNode]
def integrations ( self ):
return [WeatherAPIIntegration]
Examples Browse more code examples
Architecture Understand how plugins run under the hood