> ## 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.

# 2. First Node

> Create a node with inputs, outputs, and logic — then test it locally

<Note>
  This is **part 2** of the [Your First Plugin](/developers/plugins/your-first-plugin) tutorial. Make sure you've completed [1. Plugin Definition](/developers/plugins/tutorial/plugin-definition) first.
</Note>

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`:

```python theme={null}
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:

```python theme={null}
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:

```bash theme={null}
noxus plugin serve --path ./weather-plugin
```

```bash theme={null}
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:

```json theme={null}
{
  "temperature": {"definition": {"data_type": "str"}, "value": "22°C"},
  "description": {"definition": {"data_type": "str"}, "value": "Sunny skies in London"}
}
```

## Key concepts

| Concept              | Description                                                                                                                     |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `node_name`          | Unique identifier — must be unique across all plugins                                                                           |
| `inputs` / `outputs` | Connectors that define what data flows in and out                                                                               |
| `TypeDefinition`     | Specifies 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 |
| `NodeConfiguration`  | Pydantic model for config fields the user sets in the editor                                                                    |

### Available data types

```python theme={null}
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

| Field          | Required | Description                                                                       |
| -------------- | -------- | --------------------------------------------------------------------------------- |
| `node_name`    | Yes      | Unique snake\_case identifier                                                     |
| `title`        | Yes      | Display name in the UI                                                            |
| `description`  | Yes      | Shown in node palette and tooltips                                                |
| `category`     | Yes      | Groups the node in the palette (`DATA`, `AI_TEXT`, `INTEGRATIONS`, `LOGIC`, etc.) |
| `color`        | Yes      | Hex color for the node in the editor                                              |
| `image`        | No       | Icon URL (PNG/SVG, recommended 48×48)                                             |
| `integrations` | No       | Dict mapping integration types to required credential fields                      |

<Card title="Next: First Integration →" icon="arrow-right" href="/developers/plugins/tutorial/first-integration">
  Define credentials and an integration for an external API.
</Card>
