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.
| Login | Logged in |
|---|---|
| Collections and links | Active tab capture |
|---|---|
fuse.js)Extension: Svelte 5 (runes), TypeScript, Tailwind v4, WXT, CRXJS Backend: NestJS 10, TypeORM, PostgreSQL 16, JWT auth, Handlebars mail Infra: Docker Compose
tabkeeper/
├── chrome-extension/ # Svelte 5 + WXT browser extension
├── backend/ # NestJS API + PostgreSQL migrations
└── docker-compose.yml # Postgres (and optional api) for local dev
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/.
This is the path that actually works end-to-end. Follow it in order.
cp backend/.env.example backend/.env
cp chrome-extension/.env.example chrome-extension/.env
Edit backend/.env:
DATABASE_PASSWORD — any strong passwordJWT_ACCESS_SECRET — generate with openssl rand -base64 48JWT_REFRESH_SECRET — generate with openssl rand -base64 48backend/.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.
.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.
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.
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).
npm run start:dev
Backend now listens on http://localhost:3002.
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.
The migration step seeds two demo users:
| 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.
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.
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.55432 is in use — change it in both docker-compose.yml and
backend/.env.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.
MIT — see LICENSE.