A self-hosted net worth dashboard. Bank accounts, investments and crypto in one place, converted to a single currency.
Try the live demo → No sign-up. The demo runs on generated fake data inside your browser and never touches a real account. The data follows today's date, so the latest transactions are always from today or yesterday.
| Transactions | Spending | Portfolio | Phone |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
- Net Worth: total over time (bank + portfolio + crypto), in EUR, USD or GBP
- Transactions: full register with inline categories and a "worth it?" check
- Subscriptions: recurring charges with detected billing frequency and true monthly cost; one-off charges are listed separately and left out of the estimate
- Spending and Income vs Expenses: monthly breakdowns
- Portfolio and Crypto: holdings, allocation and live-priced value
This is the frontend. The data pipeline (bank sync, FX rates, AI categorization, pricing, setup wizard) is in alopex. Start there to self-host.
Number-first design in the spirit of Trade Republic and Revolut:
- Each screen opens on one exact figure, set in Fragment Mono; words are Geist.
- Scrub the net-worth chart to read any past day.
- Snow ground, hairline rows, 4px and 8px corners, no pills.
- Cold ink is the only control colour (white in dark mode); Ember, the fox's fur, is the one accent.
- Phones get a bottom tab bar.
- Light and dark themes follow the system setting (no toggle). Account numbers are masked until tapped, charts carry text alternatives, and translucency falls back to solid when the OS asks for reduced transparency.
TanStack Start (React), TypeScript, Tailwind, shadcn/ui, Recharts, Supabase, Vitest, deployed on Vercel. The app only uses the publishable key, so every read and write goes through Row Level Security (auth.uid() = user_id). No service-role key is used anywhere.
Start from the backend: alopex → Getting started. Its setup.sh creates the Supabase project, can run the sync on GitHub Actions, and prints the three values the dashboard needs. About 30 minutes the first time, nothing to install if you use Codespaces.
Then put the dashboard online:
Vercel copies this repo to your GitHub, asks for VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY and VITE_VAPID_PUBLIC_KEY (paste the values setup printed) and gives you an HTTPS address. Open the address under Domains on the Vercel project page (not a per-deployment address) and sign in. On that first sign-in the dashboard tells your database its own address, so bank linking sends you back to it: there is nothing to copy back and no second ./setup.sh run (the backend's setup waits for it and prints your link). Free on Vercel's Hobby plan; phone and push notifications work because it is HTTPS.
Prefer your own server? docker compose in the backend repo runs both apps instead.
Needs Bun and a .env (written by the backend's setup.sh, or cp .env.example .env and fill in your Supabase URL and publishable key).
bun install
bun run devSet your own .env. Without it a production build refuses to start, and bun run dev runs only the demo (/demo): the app never connects to anyone else's Supabase project.
Open Import data (a link on the Accounts page, also offered on a new empty dashboard). Pick net worth by month or spending by month, then choose a CSV: in Excel, File → Save As → CSV. The file is read in your browser and nothing is sent until you press Import.
- Amounts are read as EUR. Spending rows in another currency are skipped.
- Months can be
2024-03,03/2024,Mar 2024,marzo 2024or an Excel date. Day-first dates (03/04/2024is 3 April) are assumed. - Importing again overwrites months that already exist. Undo removes the rows of that import but does not bring back values it replaced.
- Examples:
docs/examples/net-worth.csvanddocs/examples/spending.csv.
Use the Deploy button above, or import the repo in Vercel yourself (framework: Other, defaults for build). Vercel sets VERCEL=1, which switches the Nitro preset to vercel in vite.config.ts. Add VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY (and VITE_VAPID_PUBLIC_KEY for push) as environment variables, then add the Vercel domain under Supabase → Authentication → URL Configuration so login redirects work: set it as Site URL and add https://<your-domain>/reset-password to the Redirect URLs, or the "Forgot password?" email links back to the wrong address. Every merge to main deploys; every PR gets a preview URL. The Vercel button copies the code into a new repo that is not a fork, so GitHub's "Sync fork" does not exist for it: see Keeping your dashboard up to date.
Your Vercel copy never receives upstream fixes by itself. It ships a workflow, update-from-upstream, that does it as a pull request:
- Runs every Monday and on demand: Actions tab → update-from-upstream → Run workflow.
- Fetches the latest
mainof this repo and opens a PR titled "Update Alopex dashboard to " with the upstream commit list. If one is still open it updates that PR instead of opening another. - Never pushes to your default branch. Review the PR and merge it; Vercel redeploys.
- Copies over the files upstream has, leaving alone
.env*,vercel.json,.vercel/, anything your.gitignorematches and.github/workflows/(GitHub does not let a workflow change workflows). Files only in your copy are kept; edits you made to files upstream also has are overwritten, so keep customisations in new files. - First time only: Settings → Actions → General → enable Allow GitHub Actions to create and approve pull requests, or the run stops after pushing the branch
update-from-upstreamand you open the PR from it by hand.
The backend repo (alopex) is a real fork: use Sync fork on GitHub. Run ./setup.sh again only when the release notes say a database change is needed.
bun run dev · bun run build · bun run test · bun run lint · bun run format
If Alopex is useful to you, a ⭐ on GitHub helps others find it. The app shows a small "Made with Alopex" credit at the bottom of each page.




