Overview

Plugins

Widgets

Reference

Publishing

Plugin Manifest

Every plugin zip must contain a plugin.json at its root. This file is the single source of truth for the plugin's identity, services, datasources, events, and actions.

Full example

{
  "key": "my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "description": "Integrates the Acme device into Hiclaro.",
  "hasAuth": false,
  "deviceManufacturer": "Acme Corp.",
  "config": {
    "ACME_API_URL": {
      "label": "Device API URL",
      "type": "string",
      "required": true,
      "description": "e.g. http://acme-device.local"
    },
    "ACME_API_KEY": {
      "label": "API Key",
      "type": "string",
      "required": true,
      "secret": true,
      "description": "Found in the device web interface under Settings → API"
    }
  },
  "services": [
    {
      "name": "My Plugin Service",
      "runtime": "python-uv",
      "entrypoint": "main.py",
      "env": {
        "PYTHONUNBUFFERED": "1"
      }
    }
  ],
  "datasources": [
    { "key": "my-plugin.device.temperature", "label": "Device: Temperature", "unit": "°C", "type": "number" },
    { "key": "my-plugin.device.status", "label": "Device: Status", "type": "string" }
  ],
  "events": [
    { "key": "device_offline", "label": "Device Went Offline", "hasPayload": false }
  ],
  "actions": [
    { "key": "restart_device", "label": "Restart Device", "hasValue": false }
  ],
  "hasDesktopBundle": true,
  "widgetColor": "#4CAF50",
  "widgets": [
    {
      "tag": "my-plugin-sensor",
      "name": "My Sensor",
      "version": "1.0.0",
      "frameworkRequires": ">=1.0.0 <2.0.0",
      "inputs": {
        "title": { "type": "string", "label": "Title" },
        "datasourceId": { "type": "datasource", "label": "Datasource" }
      },
      "preview": {
        "instanceConfig": { "title": "Device Temperature", "datasourceId": "preview::temp" },
        "telemetry": {
          "latest": {
            "preview::temp": { "default": { "value": 42 } }
          }
        }
      }
    }
  ]
}

Top-level fields

FieldTypeRequiredDescription
key

string

Yes

Unique identifier for the plugin. Used as the plugin's primary key throughout the system. Use lowercase with hyphens (e.g. my-plugin).

name

string

Yes

Human-readable display name shown in the dashboard and plugin registry.

version

string

Yes

Semantic version string (e.g. 1.0.0).

description

string

Yes

A short description of what the plugin integrates. Shown in the plugin registry.

hasAuth

boolean

Yes

Whether the plugin requires a username and password. If true, the user is prompted to enter credentials after install and they are injected as PLUGIN_USERNAME / PLUGIN_PASSWORD. Use config instead for arbitrary named fields.

config

object

No

Map of user-configurable fields presented to the user in the plugin settings UI. Each key is the exact environment variable name injected into service subprocesses at runtime. Omit if the plugin has no configurable settings.

deviceManufacturer

string

Yes

Name of the hardware or service manufacturer (e.g. TP-Link Systems Inc.).

services

array

Yes

List of service subprocesses this plugin runs. Can be empty if the plugin ships widgets only.

datasources

array

Yes

List of datasource definitions this plugin will publish telemetry to. Can be empty.

events

array

Yes

List of events this plugin can emit. Can be empty.

actions

array

Yes

List of actions this plugin exposes for use in automations. Can be empty.

widgets

array

No

List of widget manifest entries this plugin provides. Omit if the plugin ships no widgets. See the Widget SDK docs for the full field reference.

hasDesktopBundle

boolean

No

Set to true if the plugin zip includes widgets/desktop/bundle.js. The desktop client uses this flag to know whether to fetch and inject the bundle. Defaults to false.

widgetColor

string

No

Optional hex color (e.g. #4CAF50) used as the background of all dashboard widgets from this plugin. Helps users identify which plugin a widget belongs to at a glance. When omitted, widgets use the default theme background: white in light mode, #007399 in dark mode.

services[]

Each entry in services is launched as a managed subprocess by the Hiclaro service host. The host restarts crashed services automatically and reports their status back to the API.

FieldTypeRequiredDescription
name

string

Yes

Display name for the service subprocess.

runtime

string

Yes

Runtime used to launch the entrypoint. One of: python-uv, node, binary.

entrypoint

string

Yes

Path to the entrypoint file, relative to the services/ directory inside the zip — not the zip root. So main.py refers to services/main.py.

env

object

No

Static environment variables baked into the manifest and injected into the subprocess (e.g. PYTHONUNBUFFERED=1). For user-supplied values, use the top-level config field instead.

Python dependencies (python-uv runtime)

A pyproject.toml declaring your dependencies under [project] dependencies must be present inside services/ in the zip. On first install the service host creates a virtual environment and runs uv sync to install them. Every subsequent launch uses uv run --no-sync — the --no-sync flag is intentional; installation is a one-time step, not repeated on every restart. A missing or incomplete pyproject.toml will not fail at install time but will cause a ModuleNotFoundError at runtime.

[project]
name = "my-plugin"
version = "1.0.0"          # required by PEP 621 — uv will refuse to parse without it
requires-python = ">=3.11"
dependencies = [
    "paho-mqtt>=2.1.0",
    "requests>=2.32.0",
]

config{}

The optional config object declares user-configurable fields for your plugin. Each key is the exact environment variable name injected into your service subprocesses at runtime — so a field keyed ACME_API_URL is available in your service as os.getenv("ACME_API_URL").

After install, Hiclaro presents a settings form with the declared fields. When the user saves new values, all running services for the plugin are restarted automatically so they pick up the new environment.

FieldTypeRequiredDescription
label

string

Yes

Human-readable label displayed in the plugin settings form.

type

string

Yes

Input type. One of: string, number, boolean, select.

description

string

No

Helper text shown below the input field.

required

boolean

No

If true, the field must be filled before the plugin services will run. Defaults to false.

secret

boolean

No

If true, the value is masked in the UI (password input). Use for API tokens, passwords, and other sensitive values. Defaults to false.

default

string

No

Default value pre-filled in the settings form.

options

array

No

Required when type is select. Array of { label, value } objects listing the allowed choices.

min

number

No

Minimum value. Only applicable when type is number.

max

number

No

Maximum value. Only applicable when type is number.

step

number

No

Increment step for number inputs. Only applicable when type is number.

Example

{
  "config": {
    "HA_URL": {
      "label": "Home Assistant URL",
      "type": "string",
      "required": true,
      "description": "e.g. http://homeassistant.local:8123"
    },
    "HA_TOKEN": {
      "label": "Long-Lived Access Token",
      "type": "string",
      "required": true,
      "secret": true,
      "description": "Generate in HA under Profile → Long-Lived Access Tokens"
    }
  }
}

Reading these in your service:

import os

ha_url   = os.getenv("HA_URL")
ha_token = os.getenv("HA_TOKEN")

datasources[]

Datasource definitions tell Hiclaro what telemetry your plugin will publish. They are upserted by key on plugin install and whenever the service registers them over MQTT.

FieldTypeRequiredDescription
key

string

Yes

Dotted-path identifier for the datasource (e.g. tapo.plug.current_power). Must be unique across the system.

label

string

Yes

Human-readable name shown in the dashboard datasource picker.

unit

string

No

Unit of measurement (e.g. W, °C, %). Omit if not applicable.

type

string

Yes

One of: number, string, boolean. See type reference below.

meta

object

No

Additional metadata. Currently supports { min, max } for datasources with a known range (used by gauge widgets).

Datasource types

TypeDescription
number

A numeric value. Stored in the telemetry table with hourly rollups (avg / min / max). Use for anything you want to chart historically — temperature, power draw, SOC, remaining capacity.

string

A text value representing a mode or status (e.g. charging / idle, eco / comfort / boost). Stored in the event telemetry table; consecutive identical values are deduplicated.

boolean

A true/false toggle (e.g. is_charging, is_heating, plug on/off). Stored as the strings "true" or "false" in the event telemetry table; consecutive duplicates are deduplicated.

events[]

Events are discrete occurrences your plugin can emit, available as triggers in the automation builder. An event with hasPayload: true carries a value that can be used in automation conditions.

FieldTypeRequiredDescription
key

string

Yes

Identifier used in automation triggers (e.g. plug_state_changed).

label

string

Yes

Human-readable label shown in the automation builder.

hasPayload

boolean

Yes

Whether this event carries a payload value.

valueType

string

No

Type of the payload value: string, number, or boolean. Required when hasPayload is true.

actions[]

Actions are operations the automation engine can invoke on your plugin. An action with hasValue: true accepts a parameter supplied by the automation builder. Set perDevice: true if the action targets a specific device instance rather than the plugin as a whole.

FieldTypeRequiredDescription
key

string

Yes

Identifier used in automation actions (e.g. set_plug_state).

label

string

Yes

Human-readable label shown in the automation builder.

hasValue

boolean

Yes

Whether this action accepts a value parameter.

valueType

string

No

Type of the value: string, number, or boolean. Required when hasValue is true.

perDevice

boolean

No

If true, the automation builder prompts the user to select a specific device to target.

acceptedValues

array

No

Fixed set of allowed values. When present, the automation builder shows a Select instead of a free-text input. Each item is either a plain string or an object { value: string; label: string } for a labelled option.

© 2026 Hiclaro. All rights reserved.