Think alone. Decide together.
Impeccable Board is a completely free, self-hostable, end-to-end-encrypted whiteboard for small-team decisions. It moves a room through private thinking, deliberate reveal, clustering, voting, and a documented commitment.
- The outcome is the primary object. Every session finishes with a decision, rationale, dissent, and owned actions.
- The server is blind. Board updates, workflow records, presence, and exports are encrypted in the browser.
- Private means private. Ideas created during the first pass stay on that device until their author reveals them.
- No account wall. Facilitator, editor, and viewer capability links open the room directly.
- No feature gates. The whole application is AGPL-3.0 and runs without paid services.
- The canvas has an equivalent structured view. Notes, text, clusters, connectors, editing, selection, cluster movement, and voting are available through native keyboard and screen-reader controls.
Blank rooms remain the default. Facilitators can optionally start with a product tradeoff, retrospective action plan, risk review, or technical decision record recipe. Every prompt, phase instruction, vote limit, and outcome field can be edited before room creation. Recipes are inert, portable versioned JSON: they cannot execute code or load remote assets, and custom recipes can be imported or exported without an account.
Requirements: Node.js 22+
npm install
npm run devOpen http://localhost:5173. The development server proxies API and WebSocket traffic to port 3000.
npm run build
npm startThe production server listens on port 3000 and stores data in ./data/impeccable-board.db unless DATA_PATH is set.
docker compose up --buildPersist /data, terminate TLS at a trusted reverse proxy, and back up the volume as part of normal operations.
Encrypted image and file attachments are optional and need no extra service. Configure their abuse limits with environment variables:
| Variable | Default | Meaning |
|---|---|---|
ATTACHMENT_MAX_FILE_BYTES |
10485760 |
Maximum plaintext file size, plus a fixed 29-byte encrypted-envelope overhead on the wire. |
ATTACHMENT_MAX_ROOM_BYTES |
104857600 |
Maximum ciphertext bytes stored for one room. |
ATTACHMENT_MAX_FILES_PER_ROOM |
50 |
Maximum attachment records per room. |
ATTACHMENT_UPLOADS_PER_MINUTE |
20 |
Upload attempts allowed per source address and room each minute. |
Files are encrypted with the room key before upload and decrypted only in a member's browser. The server stores the opaque envelope, MIME type, ciphertext byte count, and operational timestamps; file names remain inside encrypted session state. Room deletion and expiry cascade to attachment blobs. See docs/encrypted-attachments.md.
Room keys live in URL fragments and are not included in the initial HTTP request. The server authorizes role capabilities but stores only their hashes. See SECURITY.md for guarantees and limitations.
Finalized rooms export Markdown, versioned JSON, .excalidraw, PNG, SVG, a GitHub-issue bundle, and Linear-compatible Markdown. Each action can be linked to the current canvas selection and exported as an individual issue body. Structured exports retain the question, decision, rationale, dissent, vote summary, action owners, due dates, canvas element references, and an attachment manifest. Attachment bytes are intentionally not embedded: JSON and Excalidraw files mark them as encrypted references that still require the room link and key. All integration exports run in the browser and work without tokens or server configuration.
Use the list-tree button in a room header to switch between Excalidraw and the synchronized structured board. The release test procedure is documented in docs/accessibility-test-matrix.md.
Issues and pull requests are welcome. Before proposing a large feature, open an issue explaining the user problem, privacy implications, and the smallest useful version. UI changes should preserve the system documented in DESIGN.md and include desktop and mobile screenshots.
AGPL-3.0-only. Every self-hosted feature is intended to remain free forever.