tabkeeper Svelte Themes

Tabkeeper

Self-hostable browser extension for organizing tabs into workspaces, spaces and collections. Svelte 5 + NestJS.

TabKeeper

Any resemblance to another tab manager is purely coincidental :)

A self-hostable browser extension for organizing tabs and bookmarks into Workspaces → Spaces → Collections → Links. Works offline-first; opt-in sync via a NestJS + PostgreSQL backend.

Screenshots

Login Logged in

Collections and links Active tab capture

What it does

  • Save the current tab, all open tabs, or any link via right-click menu
  • Four-level organization: workspaces, spaces, collections, links
  • Drag-and-drop reordering with keyboard fallbacks
  • Fuzzy search across titles, URLs, labels and notes (fuse.js)
  • Light / dark theme toggle with system preference detection
  • Import / export your entire workspace as JSON, including Toby v3 export files (for users migrating across)
  • Optional sync across browsers when you run the backend
  • Cross-browser via WXT: Chrome, Edge, Brave, Firefox

Stack

Extension: Svelte 5 (runes), TypeScript, Tailwind v4, WXT, CRXJS Backend: NestJS 10, TypeORM, PostgreSQL 16, JWT auth, Handlebars mail Infra: Docker Compose

Repository layout

tabkeeper/
├── chrome-extension/   # Svelte 5 + WXT browser extension
├── backend/            # NestJS API + PostgreSQL migrations
└── docker-compose.yml  # Postgres (and optional api) for local dev

Quick start — Extension only (no login, no sync)

The extension works fully standalone using chrome.storage. However, the current UI requires a user to be logged in. For a no-backend experience you need to either disable the auth gate in src/entrypoints/newtab/App.svelte or follow the full-stack instructions below.

cd chrome-extension
npm install
npm run dev

Then open chrome://extensions/ → enable Developer Mode → click Load unpacked → select chrome-extension/.output/chrome-mv3/.

For Firefox: npm run dev:firefox and load .output/firefox-mv2/.


Quick start — Full stack with sync

This is the path that actually works end-to-end. Follow it in order.

1. Configure environment

cp backend/.env.example backend/.env
cp chrome-extension/.env.example chrome-extension/.env

Edit backend/.env:

  • DATABASE_PASSWORD — any strong password
  • JWT_ACCESS_SECRET — generate with openssl rand -base64 48
  • JWT_REFRESH_SECRET — generate with openssl rand -base64 48

backend/.env defaults to:

DATABASE_PORT=55432   # non-standard, avoids conflicts with a host postgres
PORT=3002             # backend API port

chrome-extension/.env defaults to VITE_API_URL=http://localhost:3002.

2. Copy .env to repo root (docker compose reads it from there)

cp backend/.env .env

This is required because docker-compose.yml references ${DATABASE_USER}, ${DATABASE_PASSWORD}, etc. — which compose loads from a root-level .env, not from backend/.env.

3. Start PostgreSQL in Docker

docker compose up -d db
docker compose ps     # verify "Up" and "healthy"

The db service exposes port 55432 on the host so the backend can connect without conflicting with any existing local Postgres on 5432.

4. Build backend and run migrations

cd backend
npm install
npm run build                                          # required: data source reads from dist/
npx typeorm migration:run -d dist/config/database.config.js

This creates users, workspaces, spaces, collections, links tables and seeds two dev users (see below).

5. Start the backend

npm run start:dev

Backend now listens on http://localhost:3002.

6. Start the extension dev server

In a separate terminal:

cd chrome-extension
npm install
npm run dev

Load the extension from chrome-extension/.output/chrome-mv3/ as in the standalone instructions above.

7. Log in

The migration step seeds two demo users:

email password
[email protected] ChangeMe123!
[email protected] ChangeMe123!

Change these immediately if you deploy anywhere reachable from the internet. Edit backend/src/migrations/9999999999999-SeedUsers.ts or remove the file.


Tests

cd backend
npm test                          # all unit tests
npm test -- auth.service.spec     # just the auth service suite

The auth service ships with 16 unit tests covering login, logout, JWT refresh token rotation, password reset (token hashing, expiry, email enumeration prevention), and password change. Mocks are scoped to the service's direct collaborators — bcrypt runs for real so the tests exercise the actual hash and compare paths.

Common pitfalls

  • ECONNREFUSED 127.0.0.1:55432 — Postgres container is not up. Run docker compose ps and docker compose logs db. Most often the root .env is missing (see step 2) and the container restarts forever because POSTGRES_PASSWORD is empty.
  • relation "users" does not exist — migrations have not run. Repeat step 4. Note that npm run migration:run (the script in package.json) will not work directly because the data source reads from dist/. You must build first and run npx typeorm migration:run -d dist/config/database.config.js.
  • Port 55432 is in use — change it in both docker-compose.yml and backend/.env.

Status

Personal learning project. Not production-hardened. No public deployment, no published extension, no warranty. If you want to self-host it, read the code first.

License

MIT — see LICENSE.

Top categories

Loading Svelte Themes