iobroker.kanban
v0.3.1
Published
Kanban board for ioBroker with webhooks, multi-user support and email notifications
Readme
ioBroker.kanban
Kanban board adapter for ioBroker with its own web server, live sync, webhooks, multi-user support and e-mail notifications (including calendar invites) via the email adapter.

📖 Full documentation: English · Deutsch
Installation
Install the adapter from the ioBroker admin: Adapters → filter for kanban → install. Then create an instance kanban.0 and open the web UI at http://<host>:8095/.
The adapter is awaiting inclusion in the official ioBroker repository.
Features
- Own web server (default port 8095), the web UI is served directly by the adapter; no
iobroker uploadneeded for UI updates - Multiple boards with freely configurable columns (name, order, display limit, WIP limit, "new"/"done" flags)
- Cards with title, Markdown description, assignees, due date (optionally with time), labels, color, priority, checklist, link, location and calendar invite
- Recurring tasks (daily/weekly/monthly/yearly, n-th weekday, n-th working day incl. public-holiday calculation)
- Drag & drop (mouse + touch), live sync across all open views via WebSocket (polling fallback)
- Multi-user without login: user registry in the admin config, assignable members per board; the header chips act as a saved per-board person filter
- E-mail notifications via
iobroker.emailon assignment, due date and card events (toggleable per event type and per user) - Webhooks inbound (token-secured) and outbound (JSON POST on events)
- Share view: dialog to build filtered, embeddable links
- iframe-friendly (no frame headers) with
?embed=1mode for Lovelace & co. - Multilingual (de, en, fr, nl, it, es, pl, pt, ru, uk, zh-cn), configurable date & time format per instance (moment/Day.js tokens with localised month/weekday names, 12h/24h), theming (light/dark/auto, accent color, custom CSS)
Web UI / URL parameters
http://<host>:8095/ with optional parameters (excerpt, full list in the docs):
| Parameter | Effect |
|---|---|
| board=<id> | preselect a board |
| users=<name,name> | person filter: show only cards assigned to these users (user=<name> = single-user short form) |
| label=<id,id> | label blacklist: hide cards with these labels |
| columns=<id,id> | show only these columns |
| doneLimit=N | in done columns show only N cards (0 = none, omit = all) |
| theme=light\|dark\|auto | force a theme |
| embed=1 | borderless view without the header bar (for iframes) |
| card=<id> | open a card dialog directly (deep link from e-mails) |
| lang=<code> | force a language (de, en, fr, nl, it, es, pl, pt, ru, uk, zh-cn) |
Lovelace embedding
type: iframe
url: http://<host>:8095/?board=family&embed=1&theme=auto&user=user1
aspect_ratio: 75%REST API
For integrations on the local network (the same API the web UI uses). Reading (GET) is open; writing (POST/PATCH/DELETE) requires a token from 0.1.1 (X-Kanban-Token; the web UI sends it automatically), see Security.
| Method | Route | Purpose |
|---|---|---|
| GET | /api/boards | board list |
| POST | /api/boards | create a board {title} |
| GET | /api/boards/:id | board JSON; with ?rev=<n> → {unchanged:true} if current |
| PATCH | /api/boards/:id | {title?, columns?, labels?} |
| DELETE | /api/boards/:id | delete a board |
| POST | /api/boards/:id/cards | create a card |
| PATCH | /api/boards/:id/cards/:cardId | update a card partially |
| POST | /api/boards/:id/cards/:cardId/move | {columnId, order?} |
| DELETE | /api/boards/:id/cards/:cardId | delete a card |
| PATCH | /api/users/:name | set a user colour (applied at runtime, no restart) |
Webhooks & commands
External systems (scripts, agents) modify boards/cards token-secured via POST /webhook/<token>/action. The same command vocabulary applies as for sendTo and the action state:
curl -X POST http://<host>:8095/webhook/<token>/action \
-H 'Content-Type: application/json' \
-d '{"cmd":"addCard","board":"family","title":"Buy milk","assignees":["user1"],"due":"2026-07-15"}'Commands: listBoards, getBoard, addBoard, deleteBoard, addCard, updateCard, moveCard, doneCard, deleteCard. From ioBroker scripts:
sendTo('kanban.0', 'addCard', { board: 'family', title: 'From a script' }, res => log(JSON.stringify(res)));
setState('kanban.0.action', JSON.stringify({ cmd: 'doneCard', board: 'family', cardId: 'c_xyz' }));Outbound webhooks send a JSON POST to configured URLs on events (cardCreated, cardUpdated, cardMoved, cardAssigned, cardDone, cardDeleted, cardDue). Details in the docs.
States (for scripts/visualization)
| State | Content |
|---|---|
| kanban.0.boards.<id>.data | full board as JSON (read-only) |
| kanban.0.boards.<id>.rev / .cardCount / .overdueCount | revision & counters |
| kanban.0.users.<name>.assignedCount / .overdueCount / .overdueList | per user |
| kanban.0.lastEvent | last event as JSON (can trigger scripts) |
| kanban.0.action | command input (write JSON, cleared after processing) |
Security
From 0.1.1: token-secured write API, Markdown preview sanitized with DOMPurify (no stored XSS), a Content Security Policy and safe link schemes only. The web UI works without a login, the token blocks third-party websites/CSRF but is not a substitute for network isolation. For hard isolation, bind the port to the LAN only or put an authenticating reverse proxy in front.
Also from 0.3.0: agent tokens keep their board restriction on the REST API as well, tokens are only read from the header or the request body, the write secret lives in the adapter's file storage instead of a readable state, the token-free action state can be switched off, and irreversible commands are logged with their source. Note that sendTo and the action state are local ioBroker interfaces without a token by design: anyone who can run scripts in ioBroker can use them.
Details: Security & access control.
Requirements
- js-controller ≥ 6.0.11, Node.js ≥ 22
- For e-mail notifications: a configured
iobroker.emailinstance - Optional:
iobroker.feiertagefor region-accurate public-holiday calculation of the working-day recurrences
Changelog
0.3.1 (2026-08-19)
- (bmueller77) Releases are now built and published by CI when a version tag is pushed, signed with provenance through npm trusted publishing. The 0.3.0 package was published by hand and carries no signature, which is what the repository checker flags as E2008 and E3032
- (bmueller77) The workflow follows the ioBroker standard now: separate
check-and-lintandadapter-testsjobs, a trigger forv*tags, a concurrency group per branch, and adeployjob that also creates the GitHub release. Adapter tests run on Node 22 and 24 across Linux, Windows and macOS instead of Linux alone - (bmueller77) Type checking for the adapter sources (
tsconfig.jsonon@tsconfig/node22), withlib/adapter-config.d.tsdeclaring the 29 fields of the instance configuration, so a typo inadapter.config.<field>surfaces instead of silently readingundefined - (bmueller77)
npm run lintis usable again. Prettier flagged every line of every file on a Windows checkout because the repository stores LF and git checks out CRLF; it now accepts the line ending a file arrives with - (bmueller77)
common.newslists only the versions that actually exist on npm, so the changelog shown in the admin matches what can be installed - (bmueller77) Internal: the day difference of the "every X days" recurrence is computed from
getTime()on both dates rather than subtracting the Date objects. Same result, without the implicit conversion
0.3.0 (2026-08-04)
- (bmueller77) Trash: deleting a card no longer removes it immediately, it moves to a per-board Trash column and can be restored for 30 days, after which it is deleted permanently. The trash column is hidden by default and can be shown per device (board settings). It has its own fixed grey styling, is sorted by deletion time and shows the remaining days per card
- (bmueller77) Optional automatic cleanup of old done cards per board, by age (default 90 days) or by count (default 100), moving them into the trash daily and on adapter start
- (bmueller77) Per-column sorting: a toggle in the column header opens a menu with five modes, drag & drop, drag handles, due date (incl. time, cards without a date last), priority and age in column (most recently added on top, e.g. the last card you completed). For the automatic modes a direction toggle sits next to it and reverses the order with one click. Mode and direction are stored per device; returning to the manual modes restores your own order. Requested by @lyc in the ioBroker forum (post #5), tracked as #10
- (bmueller77) Cards can be moved or copied to another board: labels are matched by name, assignees are limited to members of the target board, and if none remain you are asked to pick one. Requested by @lyc in the ioBroker forum (post #5), tracked as #11
- (bmueller77) The same dialog can clone a card in place: three buttons at the top (clone / copy / move) decide what happens, and a clone duplicates the card with all its content directly below the original
- (bmueller77) Recurrence by cron expression: a new recurrence type takes the usual five fields (
0 8 * * 1-5) with lists, ranges, steps and the English short names for month and weekday. The expression works as a pattern, not as a schedule: the adapter uses it to find the next due date when a card is ticked off. Minute and hour set the time of the card, and the editor shows the rule in plain words plus the next three dates while you type. Simple patterns also become a proper recurring calendar invitation - (bmueller77) Recurring cards now produce a series invite (
RRULE) instead of one single event per instance, and the invite is sent once: follow-up cards attach nothing, an updated invite (sameUID, higherSEQUENCE) only follows a change of due date, time, duration or recurrence rule - (bmueller77) Calendar invites get an adjustable duration (
calendarDuration,HH:MM, default one hour) next to the calendar checkbox - (bmueller77) Done cards now show a strikethrough title, a completion timestamp in the configured date/time format, and a copy button that opens the editor with the same content
- (bmueller77) The "Email" tab is now called "Notifications" and is sorted so that the mail settings come first: adapter instance, sender, reminder time, lead time, the card-precise due trigger, then the per-event defaults. The pointer to other services (Telegram, Pushover and the rest through the lastEvent state or an outbound webhook) sat in the middle of the mail settings and now closes the tab under its own heading, linking straight to the docs section that walks through it. Native messenger support stays open as #12, raised by @lyc in the ioBroker forum (posts #5 and #6)
- (bmueller77) New events cardRestored and cardPurged;
cardDeletednow means "moved to trash" (automatic cleanup addsdetail.auto). Per-user e-mail toggles for all three; automatic cleanup runs are bundled into one summary e-mail per user - (bmueller77) The created notification now reaches all members of the board instead of only the assignees of the card, so you also hear about cards someone else is responsible for
- (bmueller77) Every notification event and every card returned by the REST API now carries the computed field
dueAt, the due date including time as an ISO timestamp with local offset (no time = 00:00), so automations no longer need to combinedue+dueTime+ time zone themselves - (bmueller77) New instance option "fire 'card due' at the card's time of day" (off by default): cards with a time of day trigger
cardDueexactly then, flaggeddetail.exact: true, next to the unchanged daily reminder, for minute-precise triggers without polling - (bmueller77) Reworked board settings: one Board tab with a board picker (edit any board without switching), a "show this board" button, create/delete board, member selection and the cleanup section
- (bmueller77) Reworked card editor layout: calendar-invite checkbox and location moved up below the due date; assignees and labels sit side by side (stacked on narrow screens)
- (bmueller77) All irreversible actions now use an in-app confirmation dialog instead of the browser's
confirm() - (bmueller77) The card editor now opens only via the edit icon placed right after the end of the title, not by clicking anywhere on the card, so no editor opens by accident while scrolling or tapping. Cards stay draggable everywhere
- (bmueller77) Checklist items in the card editor can be reordered by drag & drop via a small grip (shown from two items upwards)
- (bmueller77) The hit area of the checklist chevron is noticeably wider and taller (the icon itself keeps its size and position), and whether a checklist is expanded or collapsed is remembered per device and board, just like the column sort modes
- (bmueller77) Columns always share the full window width; the board only scrolls horizontally once less than 280 px per column would be left
- (bmueller77) The landing zones offered while dragging on narrow screens moved from a column at the right edge, up to a full screen width away from the card, to a row along the top in the same order as the columns; with many columns the row wraps instead of running off screen
- (bmueller77) The address bar now carries the current board (
?board=<id>), updated whenever you switch boards, so the URL can be copied and shared as is - (bmueller77) New URL parameter
onlyLabel=(label whitelist) next to the existinglabel=blacklist, selectable in the share dialog: show only cards carrying one of the given labels - (bmueller77) Subtle depth for the board, all lit from the top left: cards, avatars, chips, header controls and buttons cast a shadow to the bottom right, while columns, the card counter and chips of hidden users look recessed. Cards and columns also carry a faint top-to-bottom gradient. Every effect is a CSS variable and can be overridden or switched off in your own CSS
- (bmueller77) The UI now ships in eleven languages: German, English, French, Dutch, Italian, Spanish, Polish, Portuguese, Russian, Ukrainian and Chinese (simplified)
- (bmueller77) All log messages are now in English (they were partly German before), following the usual ioBroker convention. Texts in the web UI and in the admin config stay translated
- (bmueller77) Security: the write token was readable by any website.
Access-Control-Allow-Origin: *sat on every route, including the one that hands the token to the UI in a<meta>tag, so any page open in your browser could scan the network for the adapter, read that page cross-origin, take the token and then change or delete boards. CORS is now limited to/apiand/webhookand to origins listed in the new "Allowed browser origins" setting (empty by default = same origin only). Only browser access was affected; scripts, Node-RED and curl are unchanged - (bmueller77) Security: a board-restricted token could still escape its boards. It was only checked for board-specific webhook calls and could change any board via
/api. On the command route the guard accepted any allowed board named anywhere in the body as proof, even for commands that ignore that field:addBoardwith"board":"<allowed>"created boards elsewhere, and the same trick worked on the user and avatar routes. What counts now is the board the call actually touches: the path board for REST plus a transfer's target, and per command the field that command really evaluates. A writing command that names no board is refused for restricted tokens; reading commands (listBoards,getBoard) stay open - (bmueller77) Security: an empty token row opened the API. A row added in the token table but left blank matched every request that carried no token at all, because a missing token fell back to the empty string. Blank tokens are now rejected outright. In the same vein, an empty "allowed boards" field counted as
*, so clearing it to take rights away in fact granted them for every board. Empty now means no board; enter*explicitly for all - (bmueller77) Tokens are no longer accepted as a URL parameter (
?token=…), only in theX-Kanban-Tokenheader or as_tokenin the body, so they stop showing up in logs, browser history and referrers - (bmueller77) The SPA write secret moved into the adapter's file storage; the state
kanban.0.info.apiSecretstays but is kept empty. An existing value is migrated on first start. Object access no longer implies write access to the API - (bmueller77) The
actionstate can be switched off in the instance settings ("Webhooks (in)"). It executes the full command vocabulary without a token, so installations that do not use it can close that door - (bmueller77) Irreversible commands (
deleteBoard,emptyTrash,purgeCard) are logged with their source, no matter which route they came in through - (bmueller77) Fix: on a tablet, dragging a card into another column was cancelled by the context menu. Dragging by touch means pressing and holding, which Android also reads as a long press. With the finger on an icon (priority, due date and location are SVGs) or on a link, the WebView opened its context menu, sent
pointercanceland the card dropped back where it came from. Measured on a Galaxy Tab S5e: context menu after 251 ms, cancel after 749 ms. Grabbing the same card by its title worked, which is why it looked arbitrary: cards without a due date, priority or location have no icon row at all. The context menu is now suppressed on cards, andtouch-actionis applied to their children as well, since it does not inherit and the finger practically never rests on the card itself - (bmueller77) Fix: dragging a card landed it somewhere else while a filter was active. The board reported the position among the visible cards, but the server reads it as a position in the full column. With a person or label filter, a display limit or a done limit in place, the card therefore jumped after the next reload. The position is now translated to the full column before it is sent
- (bmueller77) Fix: the "views" dialog kept offering the users of the board you were looking at. Switching the board inside the dialog reloaded labels and columns but not the users, so the generated
users=link could name people who belong to no one on the target board, and then silently filtered nothing at all - (bmueller77) Fix: a board refresh could pull you back to the board you just left. A reply that arrived after you had switched boards overwrote the new one, and two overlapping refreshes could write an older revision over a newer one
- (bmueller77) Fix: a card spawned by a recurrence lost its time of day, location and calendar-invite flag. They were silently dropped when the follow-up card was built, so
dueAtof the new card was always 00:00. All three are inherited from the template now - (bmueller77) Fix: a board whose member list no longer matched any existing user became unusable. After renaming user IDs in the admin, the board offered nobody while an assignee is a required field, so no card could be saved. Such a board now offers all users. User rows without an ID are dropped instead of showing up as a member called "null", and removing a member who is the only assignee of cards asks first
- (bmueller77) Fix: ticking a checklist item on a card no longer scrolls the board back to the top; column, board and page scroll positions survive the re-render
- (bmueller77) Fix: a shared
users=link no longer overwrites the personal chip selection permanently - (bmueller77) Fix: live sync reconnects reliably after an adapter restart (single reconnect chain, watchdog, refresh on tab focus and a minute-by-minute safety net)
- (bmueller77) Fix: native date and time pickers follow the theme (
color-scheme), so the calendar icon stays visible in dark mode - (bmueller77) Fix: the port fallback is logged as a warning instead of an error and says what to do
- (bmueller77) Stricter validation, please check your automations. The API no longer repairs invalid input silently: an unknown
columnId, a malformeddueand apriorityoutside0/1/2now return an error instead of being dropped or remapped, andgetBoardon an unknown board answers404instead of200withnull
0.2.1 (2026-07-25)
- (bmueller77) Now runs on Express 5 (updated dependency)
- (bmueller77) Fixed the user avatar upload ("image could not be read"): the Content-Security-Policy blocked the
blob:URL used to read the file; the image is now loaded without ablob:URL, so the CSP stays strict - (bmueller77) Requires Node.js 20+ (Node.js 18 reached end-of-life) and admin 7.8.23+
- (bmueller77) Updated dependencies (
@iobroker/adapter-core,ws) - (bmueller77) Adapter metadata (type, connection type, keywords) and repository housekeeping for the ioBroker repository (adapter checker)
- (bmueller77) Every notification event now carries a ready-to-use deep link to the card; the docs gained copy-paste Telegram/Pushover notification-routing examples
0.2.0 (2026-07-22)
- (bmueller77) Mobile: columns stack & collapse (accordion); full-screen dialogs with a fixed action bar (equal-width buttons), no sideways scrolling
- (bmueller77) Assignable users per board, managed centrally under Settings → Boards; each board needs at least one member
- (bmueller77) Header user chips are now a saved, per-board filter (multi-select; tap to show only those users' cards; all active by default). The old "my cards" button was removed
- (bmueller77) User colours are edited in the web UI (Settings → Users), applied instantly without an adapter restart (previously in the instance config)
- (bmueller77) Colour ring around avatars (cards always, chips when selected), 50% larger avatars, and automatic black/white text on labels & avatars (WCAG luminance) for readability
- (bmueller77) Per-board notification link target: board view (highlight card), card editor, or a fixed custom URL
- (bmueller77) Configurable date format per instance (moment/Day.js tokens incl. localised month/weekday names) plus a 12h/24h time format; empty date format = ioBroker system format
- (bmueller77) At least one assignee is required per card in the UI, required fields are marked with a red
*; the validation message follows the board language - (bmueller77) Per-column display limit ("Max"): show only the first N cards, the rest collapse into a
+X morehint - (bmueller77) Column settings gained an aligned header row (Title · Max · WIP · New · Done) with explanatory tooltips
- (bmueller77) Material Design icons throughout (toolbar, card badges, link types), no more emoji glyphs
- 0.1.3, Fix: the column task count now respects the active person/label filter (previously showed the column total)
- 0.1.2, "Share view":
doneLimitdistinguishes empty=all / 0=none; label filter is now a blacklist (new labels stay visible) - 0.1.1, Security: token-protected write API, sanitized Markdown preview, CSP, safe link schemes
- 0.1.0, Initial release
Older changelogs can be found here
Acknowledgements
Built with the support of Anthropic's Claude, in particular for the translations of the web UI and this documentation (English, French, Dutch, Italian), as well as testing and documentation review.
License
The MIT License (MIT)
Copyright (c) 2026 Björn Müller [email protected]
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
