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

# 6. Advanced Techniques

> Config UI controls, dynamic config, list handling, error handling, and deployment

<Note>
  This is **part 6** of the [Your First Plugin](/developers/plugins/your-first-plugin) tutorial. Make sure you've completed [5. Working with Files](/developers/plugins/tutorial/working-with-files) first.
</Note>

## Node configuration with UI controls

Add configuration fields that appear in the node's settings panel in the editor:

```python theme={null}
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(values=["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()`:

```python theme={null}
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(values=[...])`         | Dropdown select                   |
| `ConfigMultiSelect(values=[...])`    | 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:

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

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

```mermaid theme={null}
graph TD
    subgraph "Non-list input (auto iteration)"
        L1["List: A, B, C"] --> N1["Node runs 3×"]
        N1 --> R1["Result A"]
        N1 --> R2["Result B"]
        N1 --> R3["Result C"]
    end

    subgraph "List input (single run)"
        L2["List: A, B, C"] --> N2["Node runs 1×"]
        N2 --> R4["[Result A, B, C]"]
    end
```

## Plugin-level configuration

Use `PluginConfiguration` for settings that apply to the entire plugin (not per-node). These are set in **Settings → Plugins → Configure**:

```python theme={null}
class WeatherPluginConfig(PluginConfiguration):
    default_units: str = Parameter(
        default="metric",
        title="Default Units",
        display=ConfigSelect(values=["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:

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

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

1. Go to **Settings → Plugins → Install Plugin**
2. Choose **Git** source
3. Enter your repository URL, branch, and path (if the plugin is in a subdirectory)
4. For private repos, provide an access token

### Option 2: Upload directly

Package and upload:

```bash theme={null}
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](https://github.com/Noxus-AI/noxus-plugins) 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:

```python theme={null}
from typing import ClassVar

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: ClassVar[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(values=["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]
```

<CardGroup cols={2}>
  <Card title="Examples" icon="code" href="/developers/examples/nodes">
    Browse more code examples
  </Card>

  <Card title="Architecture" icon="sitemap" href="/developers/architecture">
    Understand how plugins run under the hood
  </Card>
</CardGroup>
