Pin it. Move it. Done.
Open-source, self-hostable Kanban for the rest of your company — marketing, ops, customer
success, people, finance, design, agencies, and founders. The teams that think in pipelines and
stages, not sprints and story points. One board per team, magic-link login, real-time, and your
data in your own Supabase. Cards carry a human ID (BRD-12), a description, links, labels, an
assignee, a due date, and a priority.
Stack: Next.js 15 (App Router, React 19) · Supabase (Auth + Postgres + Realtime + RLS) · Cloudflare Workers via OpenNext · dnd-kit · Tailwind v4. No paid tier needed at small-team scale.
☁️ Use the hosted version — tack.ali-ali.workers.dev Sign in with a magic link and start moving cards in seconds. We run the infrastructure, updates, and backups. Free to start. This is the fastest path if you don't want to host anything.
🛠 Self-host it — free, forever, MIT-licensed Run the exact same code on your own Supabase + Cloudflare. Your data never leaves your infrastructure. The whole setup takes about five minutes — follow the self-hosting guide below.
Same code, same features either way. Start on the cloud and move to self-hosted later — your data exports cleanly.
Tack is for the parts of a company that don't live in an engineering sprint board — marketing, operations, customer success, people/HR, finance, design, legal, agencies, and founders. These teams think in pipelines and stages: campaigns, intake queues, hiring funnels, content calendars, client work. One board is one team — no nested projects, no permission matrices.
Linear and Jira own the engineering sprint, and Tack won't try to. A small or eng-adjacent team can
absolutely use it for bug intake, support triage, or a lightweight roadmap — it just won't ever grow
story points, velocity charts, or git integration. See ROADMAP.md for where it's
headed (a non-engineering core, then a calm, opt-in AI layer) and what it deliberately leaves out.
- One board per team — a board is a team's space. No nested projects, no permission matrices.
- Cards with title, description, links (
[{label, url}]), labels, assignee, due date, and priority. Drag between/within columns, or use the ◀ ▶ buttons (touch-friendly). - Human card IDs — every card gets a stable
PREFIX-NUMBERID (e.g.BRD-12), shown on the card, in the modal, and in the URL. Copy it into Slack and it pastes as text. - Real-time — changes propagate to everyone on the board via Supabase Realtime, within ~1s.
- Row-level security — every query goes through Postgres RLS keyed on
board_members. A non-member literally can't read another team's board, even via a raw API call. - Magic-link login — email OTP, no passwords. Google OAuth also works via the same callback.
- Keyboard-first —
⌘/Ctrl+Kcommand palette (jump to board/card, create, toggle theme),Cto create a card,/to filter,?for the shortcut cheatsheet. - Shareable filters — filter by assignee, label, priority, or due date; the filter lives in the URL, so the view is shareable and survives reload.
- Deep-linkable cards —
/boards/:id?card=:cardIdopens straight to a card. - Board templates — seed a new board as Engineering, Marketing, or Personal (or the default Backlog / To Do / In Progress / Done).
- Soft WIP limits — set a per-column limit; exceeding it shows a warning, never blocks writes.
- Data export — export a board's data from the board menu.
- Light & dark themes — follows the system preference, toggleable.
You need a Supabase project (free tier is fine) and a Cloudflare account.
- Create a project at supabase.com.
- Open SQL Editor → New query, paste all of
supabase/schema.sql, and run it. This creates the tables, the new-user profile trigger, the membership helper functions, the row-level-security policies, the per-board card-number trigger, and thecreate_board/add_board_memberRPCs, and turns on Realtime. Upgrading an existing install instead of a fresh one? Run each file insupabase/migrations/in filename order —schema.sqlis kept in sync with them for fresh installs. - Authentication → Providers → Email: keep Email on. For a magic-link-only flow you can
turn the password requirement off. (To add Google, enable it as a provider — the existing
/auth/callbackroute handles it.) - Authentication → URL Configuration: add your site URL and
https://YOUR-DOMAIN/auth/callbackto the redirect allow-list. For local dev also addhttp://localhost:3000/auth/callback. - Project Settings → API: copy the Project URL and the publishable (or legacy
anon) key. - Before real users sign in: set up custom SMTP. Supabase's built-in email service is meant
for development only — it's heavily rate-limited, and once the limit is hit the magic-link
endpoint rejects sign-ins with
429 Too Many Requests(misconfigured SMTP surfaces as a raw500). Either way it's easy to mistake for "login is broken". In Authentication → Emails → SMTP Settings, plug in any transactional email provider (Resend has a free tier and a one-click Supabase integration), then raise the email rate limit under Authentication → Rate Limits. If you use Resend, don't ship with its sandbox sender (onboarding@resend.dev) — it only delivers to the Resend account owner's own inbox and errors for everyone else. Verify a domain in Resend and send from an address on that domain.
cp .env.example .env
# fill in NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY
npm install
npm run dev # http://localhost:3000Sign in, then create your first board: click New board in the sidebar. It seeds the default columns and makes you the owner.
See .env.example for all variables. There are only three, and all are public —
the two Supabase values plus NEXT_PUBLIC_SITE_URL (used for SEO canonical tags, the sitemap, and
link-preview images). Nothing secret runs server-side: RLS is the security boundary. Do not add a
service-role key to this app.
This uses the OpenNext adapter on Cloudflare Workers (the
current Cloudflare recommendation — next-on-pages is deprecated). It needs the nodejs_compat
flag, already set in wrangler.jsonc.
npx wrangler login
npm run deploy # opennextjs-cloudflare build && deploy
⚠️ The env vars must exist at build time, not as runtime secrets.NEXT_PUBLIC_*values are inlined into the bundle bynext build— they are not read at runtime.wrangler secret putdoes nothing for them, and a deploy with them missing builds fine but throws a 500 on every request (the Supabase client getsundefined).Set them so they're present when
next buildruns:
- CI / Cloudflare dashboard build → add
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY, andNEXT_PUBLIC_SITE_URLunder Workers & Pages → your project → Settings → Build → Variables and secrets, then trigger a rebuild.- Deploying from your machine → having them in
.envis enough;next buildpicks them up.
After the first deploy, point a custom domain at the Worker and add https://that-domain/auth/callback
to the Supabase redirect allow-list.
- A person only ever sees boards they're a member of — enforced in Postgres by RLS keyed on the
board_memberstable, not in the app. Even a direct API call can't read another team's board. - The board owner adds teammates by email under the members button. If the teammate already has an account they join instantly; otherwise the invite is held and applied automatically the first time they sign in. Owners can see and revoke pending invites in the same dialog.
- Profiles are readable only by yourself and people you share a board with (enforced by a
SECURITY DEFINERhelper), so the user directory isn't exposed to everyone who signs up.
src/
middleware.ts # session refresh + auth redirects (matcher excludes static + /auth/callback)
app/
page.tsx # marketing landing page (redirects signed-in users to /boards)
login/, auth/callback/ # magic-link sign-in + OAuth/OTP exchange
boards/ # board list, [boardId] board view
sitemap.ts, robots.ts, manifest.ts, opengraph-image.png … # SEO / link previews
components/ # Board, Column, Card, CardModal, Sidebar, CommandPalette, etc.
lib/
actions.ts # all server actions ("use server")
types.ts, theme.ts
supabase/{client,server,middleware}.ts
supabase/
schema.sql # source of truth for fresh installs
migrations/ # numbered, run in order to upgrade an existing install
tests/database/rls.test.sql # RLS regression tests
BUILD.md is the original build spec, and ROADMAP.md tracks what's shipped and what's
deliberately left out — both are useful background if you're extending Tack.
Contributions are welcome — this is meant to be spun up and forked.
- Fork and clone, then follow Local development to get running against your own Supabase project.
- Branch off
main. Keep changes focused; Tack's whole pitch is that it's small, so a feature needs to earn its place (see the non-goals inROADMAP.mdbefore adding tables, libraries, or surface area). - Never weaken RLS to make a feature work. The database is the security boundary. Any schema
change ships as a new numbered file in
supabase/migrations/, withschema.sqlkept in sync, and a matching case insupabase/tests/database/rls.test.sql. - Run
npm run lintand confirmnpm run buildis clean before opening a PR.
Issues and PRs: github.com/AAALI/Tack.
- New cards and column changes apply optimistically and reconcile via a short refetch; the refetch is skipped mid-drag so reordering never janks.
- Card links are stored as JSON on the card (
[{label, url}]) — no extra table, no extra policy. - Card ordering uses an integer
position. If you outgrow that under heavy concurrent reordering, switch to a fractional index. Not needed at team scale.
MIT.