* feat: add ability to move boards between workspaces Implements the "Move to workspace" feature (#344) allowing users to relocate a board and all its contents (lists, cards, labels, checklists, comments, activity) to a different workspace. Key design decisions: - Card member assignments are cleared on move (they reference workspace-scoped members that may not exist in the target workspace) - Comments and activity history are preserved (they reference global user IDs, not workspace members) - Slug conflicts in the target workspace are auto-resolved by appending a UID suffix - Permission model: requires board:edit in source workspace and board:create in target workspace - Templates and archived boards cannot be moved Co-Authored-By: Claude <noreply@anthropic.com> * refactor: consolidate board queries in move mutation Address review feedback: - Consolidate 3 separate board queries into a single findFirst() that fetches all needed fields (id, name, slug, type, isArchived, workspaceId, createdBy) - Fix slug fallback to use board.name instead of publicId for human-readable URLs Co-Authored-By: Claude <noreply@anthropic.com> * fix: filter guest workspaces from move board destination list Guests typically lack board:create permission in the target workspace, so showing them as destinations leads to a confusing server rejection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: extract getBoardForMove repo function Moves the inline board query from the move mutation into the repo layer, consistent with how every other board mutation fetches data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add unit tests for board.move mutation 10 test cases covering auth, validation, permissions, slug conflict resolution, and the happy path. Follows webhook.test.ts patterns. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: mark locale files as linguist-generated GitHub will now auto-collapse compiled translation files (messages.json, messages.ts, messages.po) in PR diffs and exclude them from language stats. This makes PRs that touch i18n strings much easier to review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: remove locale file changes from PR Reverts locale file diffs and .gitattributes to match main, per review feedback. The locale changes were unrelated translation updates that inflated the PR diff. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: remove locale file changes from PR Per @hjball's review: locale compilation/translations are handled automatically on merge to main, so this PR shouldn't carry them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: align locale files with upstream/main Previous removal commit used local main, which had drifted from upstream. Re-syncing to upstream/main so the PR carries no locale diff. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(board-move): tighten deletedAt handling per review Three changes addressing @hjball's review comments, all about the schema treating deletedAt as optional metadata while the move-board flow needs it as a load-bearing invariant. 1. getBoardForMove now filters isNull(deletedAt). Moving a tombstoned board has no defensible semantics. Replaces the implicit "the board exists in the table" check with an explicit "the board is not soft-deleted" check. 2. Move-flow's clearing of cardToWorkspaceMembers now spans every card under every list ever associated with this board, including soft-deleted ones. If we leave member assignments on a deleted card and that card is later restored, the assignments would resurrect rogue references to workspace members from the OLD workspace. Removed the isNull filters on both lists and cards in that loop. 3. Move-flow now refuses to move into a soft-deleted target workspace. workspaceRepo.getByPublicId did not previously project deletedAt; extended its column selection so the call-site guard in board.move can check it. (A wider fix to make the repo treat deleted-as-not-found across all 14+ callers is left for a separate PR — narrow scope here.) Plus one regression test: throws NOT_FOUND when target workspace is soft-deleted. All 11 board-move tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Kan
The open-source project management alternative to Trello.
Roadmap · Website · Docs · Discord
Features 💫
- 👁️ Board Visibility: Control who can view and edit your boards
- 🤝 Workspace Members: Invite members and collaborate with your team
- 🚀 Trello Imports: Easily import your Trello boards
- 🔍 Labels & Filters: Organise and find cards quickly
- 💬 Comments: Discuss and collaborate with your team
- 📝 Activity Log: Track all card changes with detailed activity history
- 🎨 Templates : Save time with reusable custom board templates
- ⚡️ Integrations (coming soon) : Connect your favourite tools
See our roadmap for upcoming features.
Screenshot 👁️
Made With 🛠️
Self Hosting 🐳
One-click Deployments
The easiest way to deploy Kan is through Railway. We've partnered with Railway to maintain an official template that supports the development of the project.
Docker Compose
Alternatively, you can self-host Kan with Docker Compose. This will set up everything for you including your postgres database and automatically run migrations.
-
Create a
.envfile with your environment variables (see Environment Variables section below) -
Use the provided
docker-compose.ymlfile or create your own with the following configuration:
services:
migrate:
image: ghcr.io/kanbn/kan-migrate:latest
container_name: kan-migrate
networks:
- kan-network
environment:
- POSTGRES_URL=${POSTGRES_URL}
depends_on:
postgres:
condition: service_healthy
restart: "no"
web:
image: ghcr.io/kanbn/kan:latest
container_name: kan-web
ports:
- "${WEB_PORT:-3000}:3000"
networks:
- kan-network
env_file:
- .env
environment:
- NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL}
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
- POSTGRES_URL=${POSTGRES_URL}
- NEXT_PUBLIC_ALLOW_CREDENTIALS=true
depends_on:
migrate:
condition: service_completed_successfully
restart: unless-stopped
postgres:
image: postgres:15
container_name: kan-db
environment:
- POSTGRES_DB=kan_db
- POSTGRES_USER=kan
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
ports:
- 5432:5432
volumes:
- kan_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U kan -d kan_db"]
interval: 5s
timeout: 5s
retries: 10
restart: unless-stopped
networks:
- kan-network
networks:
kan-network:
volumes:
kan_postgres_data:
- Start the containers in detached mode:
docker compose up -d
The migrate service will automatically run database migrations before the web service starts. The application will be available at http://localhost:3000 (or the port specified in WEB_PORT).
Managing containers:
- To stop the containers:
docker compose down - To view logs:
docker compose logs -f - To view logs for a specific service:
docker compose logs -f webordocker compose logs -f migrate - To restart the containers:
docker compose restart - To rebuild after code changes:
docker compose up -d --build
For the complete Docker Compose configuration with all optional features, see docker-compose.yml in the repository.
Local Development 🧑💻
- Clone the repository (or fork)
git clone https://github.com/kanbn/kan.git
- Install dependencies
pnpm install
- Copy
.env.exampleto.envand configure your environment variables - Migrate database
pnpm db:migrate
- Start the development server
pnpm dev
Environment Variables 🔐
| Variable | Description | Required | Example |
|---|---|---|---|
POSTGRES_URL |
PostgreSQL connection URL | To use external database | postgres://user:pass@localhost:5432/db |
REDIS_URL |
Redis connection URL | For rate limiting (optional) | redis://localhost:6379 or redis://redis:6379 (Docker) |
EMAIL_FROM |
Sender email address | For Email | "Kan <hello@mail.kan.bn>" |
SMTP_HOST |
SMTP server hostname | For Email | smtp.resend.com |
SMTP_PORT |
SMTP server port | For Email | 465 |
SMTP_USER |
SMTP username/email | No | resend |
SMTP_PASSWORD |
SMTP password/token | No | re_xxxx |
SMTP_SECURE |
Use secure SMTP connection (defaults to true if not set) | For Email | true |
SMTP_REJECT_UNAUTHORIZED |
Reject invalid certificates (defaults to true if not set) | For Email | false |
NEXT_PUBLIC_DISABLE_EMAIL |
To disable all email features | For Email | true |
NEXT_PUBLIC_BASE_URL |
Base URL of your installation | Yes | http://localhost:3000 |
NEXT_API_BODY_SIZE_LIMIT |
Maximum API request body size (defaults to 1mb) | No | 50mb |
BETTER_AUTH_ALLOWED_DOMAINS |
Comma-separated list of allowed domains for OIDC logins | For OIDC/Social login | example.com,subsidiary.com |
BETTER_AUTH_SECRET |
Auth encryption secret | Yes | Random 32+ char string |
BETTER_AUTH_TRUSTED_ORIGINS |
Allowed callback origins | No | http://localhost:3000,http://localhost:3001 |
GOOGLE_CLIENT_ID |
Google OAuth client ID | For Google login | xxx.apps.googleusercontent.com |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret | For Google login | xxx |
DISCORD_CLIENT_ID |
Discord OAuth client ID | For Discord login | xxx |
DISCORD_CLIENT_SECRET |
Discord OAuth client secret | For Discord login | xxx |
GITHUB_CLIENT_ID |
GitHub OAuth client ID | For GitHub login | xxx |
GITHUB_CLIENT_SECRET |
GitHub OAuth client secret | For GitHub login | xxx |
OIDC_CLIENT_ID |
Generic OIDC client ID | For OIDC login | xxx |
OIDC_CLIENT_SECRET |
Generic OIDC client secret | For OIDC login | xxx |
OIDC_DISCOVERY_URL |
OIDC discovery URL | For OIDC login | https://auth.example.com/.well-known/openid-configuration |
TRELLO_APP_API_KEY |
Trello app API key | For Trello import | xxx |
TRELLO_APP_API_SECRET |
Trello app API secret | For Trello import | xxx |
S3_REGION |
S3 storage region | For file uploads | WEUR |
S3_ENDPOINT |
S3 endpoint URL | For file uploads | https://xxx.r2.cloudflarestorage.com |
S3_ACCESS_KEY_ID |
S3 access key | For file uploads (optional with IRSA) | xxx |
S3_SECRET_ACCESS_KEY |
S3 secret key | For file uploads (optional with IRSA) | xxx |
S3_FORCE_PATH_STYLE |
Use path-style URLs for S3 | For file uploads | true |
S3_AVATAR_UPLOAD_LIMIT |
Maximum avatar file size in bytes | For file uploads | 2097152 (2MB) |
NEXT_PUBLIC_STORAGE_URL |
Storage service URL | For file uploads | https://storage.kanbn.com |
NEXT_PUBLIC_STORAGE_DOMAIN |
Storage domain name | For file uploads | kanbn.com |
NEXT_PUBLIC_USE_VIRTUAL_HOSTED_URLS |
Use virtual-hosted style URLs (bucket.domain.com) | For file uploads (optional) | true |
NEXT_PUBLIC_AVATAR_BUCKET_NAME |
S3 bucket name for avatars | For file uploads | avatars |
NEXT_PUBLIC_ATTACHMENTS_BUCKET_NAME |
S3 bucket name for attachments | For file uploads | attachments |
NEXT_PUBLIC_ALLOW_CREDENTIALS |
Allow email & password login | For authentication | true |
NEXT_PUBLIC_DISABLE_SIGN_UP |
Disable sign up | For authentication | false |
NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY |
Hide “Powered by kan.bn” on public boards (self-host) | For white labelling | true |
KAN_ADMIN_API_KEY |
Admin API key for stats and admin endpoints | For admin/monitoring | your-secret-admin-key |
LOG_LEVEL |
Log verbosity level (debug, info, warn, error) | No (defaults to debug in dev, info in prod) | info |
See .env.example for a complete list of supported environment variables.
MCP Server (AI Control) 🤖
Kan ships with a Model Context Protocol (MCP) server that lets any MCP-compatible AI client — GitHub Copilot, Claude Desktop, Cursor, Codex, and others — read and control your Kan instance using natural language.
Prerequisites
- Node.js 18+
- A running Kan instance (self-hosted or cloud)
- A Kan API key (Settings → API Keys → Create key)
Installation
You do not need to clone this repository. The recommended way is to use npx, which runs the server on-demand and always uses the latest version — no global install required:
npx -y @kan/mcp
Alternatively, install it globally:
npm install -g @kan/mcp
kan-mcp
Configuration
The server is configured via two environment variables:
| Variable | Description | Example |
|---|---|---|
KAN_BASE_URL |
Base URL of your Kan instance | https://your-kan.example.com |
KAN_API_TOKEN |
API key from your Kan user settings | kan_xxxxxxxxxxxx |
GitHub Copilot (VS Code)
Add the following to your VS Code mcp.json (open it via MCP: Open User MCP Configuration from the Command Palette):
{
"servers": {
"kan": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@kan/mcp"],
"env": {
"KAN_BASE_URL": "https://your-kan-instance.com",
"KAN_API_TOKEN": "kan_your_api_key_here"
}
}
}
}
Then use Copilot in Agent mode to interact with Kan.
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"kan": {
"command": "npx",
"args": ["-y", "@kan/mcp"],
"env": {
"KAN_BASE_URL": "https://your-kan-instance.com",
"KAN_API_TOKEN": "kan_your_api_key_here"
}
}
}
}
Cursor / Codex / other clients
Use the same command + args + env pattern above — all MCP stdio clients follow the same format.
Example prompts
Once connected, you can ask your AI assistant things like:
Browsing
- "List all my workspaces"
- "Show me all boards in the Marketing workspace"
- "What cards are in the Backlog list of the Q3 Planning board?"
- "Get the full details of card X including comments and checklists"
Managing cards
- "Create a card called 'Fix login bug' in the To Do list of the Dev board"
- "Move the 'API redesign' card to the In Progress list"
- "Set a due date of next Friday on the 'Write docs' card"
- "Add a comment to the 'Deploy to prod' card saying the deployment is blocked"
- "Duplicate the 'Sprint template' card into the new Sprint 4 list"
- "Mark the 'Setup CI' checklist item as complete"
Organisation
- "Add the 'urgent' label to all cards assigned to me in the Backend board"
- "Create a 'Release checklist' checklist on the v2.0 card with items: smoke test, update changelog, tag release"
- "What tasks are assigned to @alice in the Mechanics Rework board?"
Workspace management
- "Create a new workspace called 'Client Projects'"
- "Invite bob@example.com to the Marketing workspace as a member"
- "Create a new board called 'Sprint 5' in the Dev workspace with lists: Backlog, In Progress, Done"
- "Search for anything related to 'authentication' across the Dev workspace"
Available tools
The MCP server exposes 46 tools across 7 resource types:
| Resource | Tools |
|---|---|
| Workspaces | list, find by name, get, create, update, delete, search, check slug |
| Boards | list, find by name, get, get by slug, create, update, delete |
| Lists | create, update, delete |
| Cards | create, get, update, delete, duplicate, get activities |
| Card interactions | add/update/delete comment, toggle label, toggle member |
| Checklists | create, update, delete, create item, update item, delete item |
| Labels | get, create, update, delete |
| Members | invite, remove, update role, manage invite links |
Contributing 🤝
We welcome contributions! Please read our contribution guidelines before submitting a pull request.
Contributors 👥
Sponsors ❤️
Proudly sponsored by TestMu AI (formerly LambdaTest) - an AI-native testing cloud platform built for modern engineering teams. Covering everything from autonomous test creation and fast execution to testing AI agents like chatbots and voice assistants. If you're serious about testing, go check them out.
License 📝
Kan is licensed under the AGPLv3 license.
Contact 📧
For support or to get in touch, please email henry@kan.bn or join our Discord server.