An open source, self-hosted, simple kanban board. Boards, columns, cards, sharing and live updates, built with Angular + Supabase. Runs on your own infrastructure, with a Home Assistant integration so you can see and move cards from a wall-mounted tablet or a voice command, not just the browser.
| Path | What | Docs |
|---|---|---|
/app |
The Angular web app (boards, columns, cards, sharing) | app/README.md |
/supabase |
Database schema, RLS policies, realtime setup | supabase/README.md |
/homeassistant |
Custom component + Lovelace card | homeassistant/README.md |
- Multiple boards, each with user-configurable columns and drag-and-drop cards.
- Multi-user via Supabase Auth. A board owner shares a board by adding a collaborator's email directly and picking a role (editor or viewer) — access is granted immediately, no invite/accept step.
- Live updates across everyone viewing a board, via Supabase Realtime.
- Installable as a PWA, with a thumbnail preview of each board on the boards list (captured client-side, stored in a private Supabase Storage bucket).
- A Home Assistant integration that polls your board and exposes it as sensors + services, plus a Lovelace card that mirrors the board and lets you move cards by tapping them (see homeassistant/README.md for why tap-to-move instead of drag-and-drop there).
Angular app (Netlify) Home Assistant
│ REST + Realtime, custom_component
│ anon key + RLS │ REST, service_role key
▼ ▼
Supabase (Postgres, Auth, Realtime)
▲
│ HA services + sensor state
│
Lovelace card
The Angular app talks to Supabase directly, protected entirely by Row Level Security (see supabase/schema.sql) — the browser only ever holds the public anon key. The Home Assistant integration is a separate, trusted server-side client: it holds your Supabase service_role key locally in your HA config and proxies reads/writes for the Lovelace card, so that key never reaches a browser. See homeassistant/README.md for the reasoning.
- Supabase: for local development,
cd supabase && npm install && npm startruns a full local Postgres + Auth + Realtime stack in Docker (see supabase/README.md) — no remote project needed. For a real deployment, create a project at supabase.com and runsupabase/schema.sqlin its SQL editor instead. - App:
cd app, copypublic/env.example.jstopublic/env.jswith the Supabase URL + anon key (localsupabase startprints these; a remote project has them under Settings → API),npm install,npm start. See app/README.md. - Home Assistant (optional): copy
homeassistant/custom_components/kanbaninto your HAcustom_components/, restart HA, add the integration via the UI. Then add the Lovelace card. See homeassistant/README.md.
This repo pins its Node and Python versions with mise
(mise.toml at the repo root — mise install picks them up). Package
management is npm throughout.
- The Home Assistant integration polls every 30s rather than subscribing to Supabase Realtime — simpler to build and reason about; a websocket bridge is a natural follow-up.
- The Lovelace card is tap-to-move, not drag-and-drop.
- The HA custom_component is manual-install only; it's structured to be HACS-compatible but hasn't been submitted to HACS yet.
- No SSR — the app is a static SPA. If this ever grows a public marketing page, that page would likely want SSR; the authenticated board views wouldn't.
See CONTRIBUTING.md.
Live demo: https://demo.kanban.moimob.com/
Set DEMO_MODE=true (build-time env var) to build a Supabase-free version: login is skipped,
data is kept in the browser's localStorage (snapshots in IndexedDB), and a banner marks the app as a demo.
On Netlify, create a second site from this repo and set DEMO_MODE=true in its environment
(SUPABASE_* not needed). Locally, put DEMO_MODE: 'true' in app/public/env.js.
Sharing is unavailable in demo mode.
Run the demo e2e suite (cypress/demo) against a DEMO_MODE=true build:
npx serve -s dist/app/browser -l 4200 then npx cypress run --config-file cypress.demo.config.ts.
/embed/<boardId> shows a single board without the sidebar or navigation, for
dashboards and wall tablets:
<iframe src="https://kanban.example.com/embed/<boardId>?theme=dark&readonly=true#email=wall%40example.com&password=..."></iframe>| Query param | Values | Default |
|---|---|---|
theme |
light / dark (not saved) |
stored/system theme |
readonly |
true disables all editing |
false |
toolbar |
false hides the title and search/filter bar |
true |
Credentials are optional and go in the URL fragment (#email=...&password=...,
URL-encoded), which browsers never send to the server. The app signs in, then
strips them from the address bar. The iframe's src still holds the password,
so anyone who can see the dashboard's page source can read it. Use a dedicated
account that only has viewer access to the boards you embed, never your
own. Without credentials the embed uses an existing session, or shows the login page.