The manifest
Every field of niles-integration.toml.
Interface 0.3, and 0.2 and 0.1, which Niles still runs. Niles 0.2.0 runs them and installs them from GitHub: the runtime,
niles plugin check,niles plugin dev, Add-on from GitHub with its approval screen, updates, rollback, turning off and removing, and in the app the generated setup panel, the plugin's log, and its pages and widgets.
Every plugin has a niles-integration.toml at the root of its repo, attached
to each release. Niles reads it before downloading anything else: it decides
whether the plugin can be installed, what the household is asked to approve,
and what the setup panel looks like.
A complete example is in the worked example.
Top-level fields
| Field | Required | What it is |
|---|---|---|
id | yes | [a-z0-9_], 2–32 characters, starting with a letter. Never changes once published. |
name | yes | What the Add-ons page calls it: "Spot prices". |
version | yes | Semver, 0.1.0. The release tag must be the same, with or without a leading v. |
description | yes | One line, at most 120 characters. See below. |
authors | yes | List of "Name <email>" or "Name". |
homepage | no | Usually the repo. Linked from the setup panel. |
min_niles | yes | The oldest Niles version it runs on. Niles 0.2.0 is the first with plugins, so "0.2.0" or later; "0.3.0" or later for interface 0.3. |
interface | yes | The niles:integration version the component targets: "0.3", or "0.2" or "0.1" for a plugin built before accounts and webhooks, or before MQTT. It must match the WIT the component was built with. |
group | no | Where it sits on the Add-ons page. See Groups. |
logo | no | Path of an SVG in the release, normally "logo.svg". See Logo. |
memory_mb | no | Memory cap, 64 by default, at most 256. JavaScript and Python plugins want 128. |
keywords | if it has tools | Words that make its tools relevant to a request. See tools. |
id
The id ends up everywhere, so choose it once:
- device source
x_<id>: devices arex_spotprice:system/electricityinside Niles; - tool names
<id>_<tool>:spotprice_prices; - secrets
plugins.<id>.<key>in Niles's secret store; - menu entries
<id>.<entry>in the house's[menu]settings.
It must not be the id of anything built in (mqtt, elevenlabs, cerebras,
sonos, spotify, lg_tv, unifi, tado, microsoft, google,
nemlig, claude_code, chatgpt, linear, and any added later) or of another
plugin already installed. Prefer the product's name, short: airgradient, easee,
rejseplanen.
description
One line saying what the household gets, not what the API is. The built-in catalogue's blurbs are the model:
- "Switch and dim your Zigbee lights and WLED strips."
- "Set the heating in each room by voice."
- "Turn the shopping list into a filled basket."
So: "Hear what power costs hour by hour, and let rules run things when it is cheapest." Not: "Integration with the Energi Data Service DayAheadPrices API."
Groups
The Add-ons page (Settings → Add-ons) has two tabs, Connected and
Available, each grouped by what an add-on does for the house. group is
one of the built-in group ids:
group | Shown as | For |
|---|---|---|
music | Music & TV | Speakers, music, the television |
presence | Presence & climate | Who is home, heating, air |
calendars | Calendars | Calendars |
shopping | Shopping | Groceries and deliveries |
assistants | Assistants & tools | AI assistants and work tools |
A missing or unknown group puts the plugin in Custom, shown after the
built-in groups. These ids are part of the interface: Niles may add groups
later, and a plugin naming a group its Niles does not know yet lands in Custom
rather than failing, so naming a newer group is safe.
There is no group for what Niles needs to work at all — the broker and the
providers that hear and think. Those are the base system, set up under
Settings → System rather than listed with the add-ons, and a plugin cannot put
itself there: essentials, the group that used to hold them, now lands in
Custom like any other id Niles does not offer.
Wherever it sits, a plugin's row carries a Custom badge, and its setup panel shows where it came from: your repo (linked), authors, version and install date. Add-on from GitHub is at the end of the Available tab under its own heading.
Logo
An SVG on a 24×24 view box, like the built-in marks: one or a few paths, a
brand colour or currentColor (which follows the text colour, so it works in
dark mode). Niles sanitises it at install and serves its own copy, so it
accepts only svg, g, path, circle, ellipse, rect, line,
polyline and polygon, with geometry, fill, stroke and transform
attributes. No script, style, image, use, foreignObject, links or
external references, and at most 32 KiB. Without a logo the card shows a
generic icon.
[permissions]
What the plugin may reach. Everything not listed is refused at run time, and the household approves this list in plain words before install. Each entry is explained in permissions.
[permissions]
http = ["api.example.com", "*.example.net"]
local_network = ["setting:address"]
devices = true
tools = true
events = ["device_state", "device_action", "presence"]
notify = true # or "urgent"
ui = true
mqtt = [ # interface 0.2
{ name = "printer", host = "setting:address", port = 8883, tls = "trust-device" },
]
callback = true # interface 0.3
webhook = true # interface 0.3All are optional; leave out what you do not use. mqtt needs
interface = "0.2" or later; see MQTT for its
fields. callback and webhook need interface = "0.3"; see
accounts and webhooks.
Settings
Each [[settings]] table is one field on the generated setup panel.
| Field | Required | What it is |
|---|---|---|
key | yes | [a-z0-9_]+, what config.get and config.secret take. |
label | yes | The field's label. |
type | yes | string, number, boolean, secret, choice or room. |
required | no | false by default. Until every required setting is filled in, the plugin is "Not set up" and does not start. |
help | no | One or two sentences under the field: where to find the value. |
default | no | Used until the user enters something. Not allowed for secret. |
options | for choice | A list of { value, label }. |
min, max | no | For number. |
The types:
string: one line of text.config.getreturnstext.number:config.getreturnsnumber(anf64).boolean: a switch.config.getreturnsboolean.secret: a password field. The value goes into Niles's encrypted secret store asplugins.<id>.<key>, is never shown again (the panel says only whether it is set), and reaches the plugin only throughconfig.secret.config.getreturns none for it.choice: a drop-down ofoptions.config.getreturns the chosen option'svalueastext.room: a drop-down of the house's rooms.config.getreturns the room id,living_room, astext. Use it for "which room is this device in".
[[settings]]
key = "address"
label = "Address"
type = "string"
required = true
help = "The device's IP address or host name, such as 192.168.1.50. Give it a fixed address in your router."
[[settings]]
key = "room"
label = "Room"
type = "room"
required = true
[[settings]]
key = "api_key"
label = "API key"
type = "secret"
required = true
help = "Under Account → API in the vendor's app."
[[settings]]
key = "interval"
label = "Check every (seconds)"
type = "number"
default = 60
min = 15
max = 3600Saving the panel restarts the plugin: stop, a fresh instance, init. Read
your settings in init and you never have to handle them changing.
Niles keeps the values in its configuration as [plugins.<id>], beside its
own settings, and secrets in its encrypted secret store as
plugins.<id>.<key>. The setup panel writes them there, as
PATCH /config with {"plugins":{"spotprice":{"area":"DK2"}}} and
PUT /secrets/plugins.spotprice.api_key; a script can do the same.
How each type is drawn: string and number as a text box, boolean as a
switch, choice as a menu of its options, room as a menu of the house's
rooms, and secret as a password box that says whether one is saved and never
shows it. A required setting is marked, help is written under its field,
and a default is shown beside it ("Default: West Denmark (DK1).") rather than
filled in, so an emptied field goes back to the default. Until every required
setting has a value the panel reads "Not set up: needs API key", and the plugin
does not start.
[[ui.menu]] and [[ui.widgets]]
Pages and widgets the plugin adds to the app. Need ui = true. Described in
pages and widgets.
[[ui.menu]]
id = "electricity"
label = "Electricity"
icon = "zap"
refresh_every = 300
[[ui.widgets]]
id = "price_now"
title = "Electricity now"
room = "kitchen" # or "setting:<key>" for a room setting's room
refresh_every = 300What Niles checks
At install and in niles plugin check: every required field is present and
well-formed; version matches the release tag; interface is one this Niles
runs and min_niles is not newer than it, and the component was built for
that interface; mqtt brokers have unique names, a setting: that names a
string setting, and trust-device or none only for home-network addresses;
id is free; settings keys are
unique and defaults match their types; keywords exist if the component
returns tools; surface ids are unique and [a-z0-9_]+; icons are Lucide names;
the logo passes sanitising.