Skip to main content
This is part 2 of the Your First Plugin tutorial. Make sure you’ve completed 1. Plugin Definition first.
Nodes are where your plugin’s logic lives. Let’s create a node that takes a city name and returns a formatted weather description.

Define the node

Add to weather_plugin/__init__.py:
from noxus_sdk.nodes.base import BaseNode, NodeConfiguration
from noxus_sdk.nodes.connector import Connector
from noxus_sdk.nodes.types import TypeDefinition, DataType, NodeCategory
from noxus_sdk.plugins.context import RemoteExecutionContext


class GetWeatherConfig(NodeConfiguration):
    """Node has no configuration fields yet."""
    pass


class GetWeatherNode(BaseNode[GetWeatherConfig]):
    node_name = "get_weather"
    title = "Get Weather"
    description = "Returns weather data for a given city"
    category = NodeCategory.DATA
    color = "#4A90E2"

    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:
        # For now, return mock data
        return {
            "temperature": "22°C",
            "description": f"Sunny skies in {city}",
        }

Register the node in your plugin

Update the plugin class to include the node:
class WeatherPlugin(BasePlugin[WeatherPluginConfig]):
    # ... same metadata as before ...

    def nodes(self):
        return [GetWeatherNode]

Test it locally

Restart the plugin server and test the node execution:
noxus plugin serve --path ./weather-plugin
curl -X POST http://localhost:8505/nodes/get_weather/execute \
  -H "Content-Type: application/json" \
  -d '{
    "ctx": {"plugin_config": {}, "integration_credentials": {}},
    "inputs": {"city": {"definition": {"data_type": "str"}, "value": "London"}},
    "config": {}
  }'
You should get back:
{
  "temperature": {"definition": {"data_type": "str"}, "value": "22°C"},
  "description": {"definition": {"data_type": "str"}, "value": "Sunny skies in London"}
}

Key concepts

ConceptDescription
node_nameUnique identifier — must be unique across all plugins
inputs / outputsConnectors that define what data flows in and out
TypeDefinitionSpecifies data type (str, dict, File, Image, etc.) and whether it’s a list
call()The async method that runs when the node executes. Receives ctx + input values, returns a dict mapping output names to values
NodeConfigurationPydantic model for config fields the user sets in the editor

Available data types

TypeDefinition(data_type=DataType.str)              # Text
TypeDefinition(data_type=DataType.dict)             # JSON / dict
TypeDefinition(data_type=DataType.File)             # Any file
TypeDefinition(data_type=DataType.Image)            # Image file
TypeDefinition(data_type=DataType.Audio)            # Audio file
TypeDefinition(data_type=DataType.str, is_list=True)  # List of strings
TypeDefinition(data_type=DataType.File, is_list=True) # List of files

Node metadata

FieldRequiredDescription
node_nameYesUnique snake_case identifier
titleYesDisplay name in the UI
descriptionYesShown in node palette and tooltips
categoryYesGroups the node in the palette (DATA, AI_TEXT, INTEGRATIONS, LOGIC, etc.)
colorYesHex color for the node in the editor
imageNoIcon URL (PNG/SVG, recommended 48×48)
integrationsNoDict mapping integration types to required credential fields

Next: First Integration →

Define credentials and an integration for an external API.