Personal finance that stays on your own devices.
Budget, bank sync, net worth and investments — on iPhone, Mac and your own server.
Download · Features · Screens · Get started · Connect a bank · Contributing
- 🔒 Your data never leaves your devices. No Florin account, no Florin server, no analytics, no tracking SDK. Merchant logos on iPhone come straight from each merchant's own website, never from a logo service — and can be turned off.
- 🏦 Real bank sync, under your own credentials. PSD2 access to 2 000+ European banks through Enable Banking — your own free registration, not a shared key.
- 🎯 Figures that match the bank. Loans follow a real amortisation schedule; savings rates count complete months only; the net worth curve is anchored on balances actually recorded, not on balances deduced backwards. When a number cannot be computed honestly, Florin says so instead of showing zero.
- 🗂️ A budgeting workflow, not a chart gallery. A monthly plan in category groups and a review queue that learns from how you file your own history.
- 📈 Everything in one place. Current accounts, savings, loans and a stock portfolio, in one net worth — and one dotted line for the year ahead.
- 🧾 Open source, AGPL-3.0. Read it, run it, fork it.
| Platform | Get it | Notes |
|---|---|---|
| iPhone | App Store — in review | Meanwhile: TestFlight or the sideloadable .ipa |
| Mac | Florin-*-arm64.dmg |
Signed, notarised, updates itself |
| Self-hosted | Florin-*-server.tar.gz |
docker compose up -d — see below |
Nothing to sign up for, anywhere. Want to look around first? The iPhone app has a demo on its first screen, with invented accounts you can erase from Settings.
| iPhone | Mac | Web | |
|---|---|---|---|
| What's left to spend this month, and the daily pace it allows | ✅ | ✅ | ✅ |
| Month-end projection that narrows as the month fills | ✅ | ✅ | ✅ |
| Monthly plan in category groups, carried over from last month | ✅ | ✅ | ✅ |
| Mark a category a fixed charge: rent and subscriptions fall whole in the projection instead of being spread over the month, and stay out of the daily pace | ✅ | ✅ | ✅ |
| Review queue that learns from your own filing | ✅ | ✅ | ✅ |
| Suggested category offered wherever a row is filed, ranked by your own history | ✅ | — | — |
| Create, rename and remove categories; a removal reassigns what it held | ✅ | ✅ | ✅ |
| Recurring charges found on their own, with their cadence and yearly cost | ✅ | ✅ | ✅ |
| Rename a merchant once — the bank's label, shown your way, everywhere | ✅ | — | — |
| An instalment plan tracked to its last payment | ✅ | — | — |
| A purchase in instalments named at the till, even when PayPal or Revolut collected it | ✅ | — | — |
| iPhone | Mac | Web | |
|---|---|---|---|
| PSD2 bank sync through your own Enable Banking app | ✅ | ✅ | ✅ |
| A sync log saying what each bank actually returned, account by account | ✅ | ✅ | ✅ |
| CSV / OFX import that reads real French, German and English exports | ✅ | ✅ | ✅ |
| Manual entry, and transfers between your own accounts | ✅ | ✅ | ✅ |
| Pair two existing rows into a transfer after the fact | ✅ | — | — |
| Photograph a receipt and keep what it says, line by line — read on the phone, never uploaded | ✅ | — | — |
| Search your own receipts by article, not just by merchant | ✅ | — | — |
| Apple Pay payments listed as upcoming the moment you pay, then replaced by the bank's row | ✅ | — | — |
| A loan instalment filed in one tap, with its mirror row on the loan account | ✅ | — | — |
| An entry on a bank-synced account is upcoming by default, swallowed when the bank's own row lands | ✅ | — | — |
| Link a refund to the expense it pays back — the expense's category comes with it | ✅ | — | — |
| YNAB-style spreadsheet import | — | — | ✅ |
| iPhone | Mac | Web | |
|---|---|---|---|
| Loans on a real amortisation schedule | ✅ | ✅ | ✅ |
| Holdings with cost basis, gain, and opt-in live quotes | ✅ | ✅ | ✅ |
| Search a listed asset by name; a line is priced the moment it is created | ✅ | — | — |
| Net worth over time, scrubbable day by day | ✅ | ✅ | ✅ |
| The year ahead in dotted line, at the pace of the complete months behind | ✅ | — | — |
| Allocation ring and rolling savings rate over 3, 6 and 12 complete months | ✅ | ✅ | ✅ |
| Balance corrections booked as visible adjustments, never as silent edits | ✅ | ✅ | ✅ |
| iPhone | Mac | Web | |
|---|---|---|---|
| Hide every amount in one gesture | ✅ | ✅ | ✅ |
| Face ID lock / PIN lock | ✅ | ✅ | — |
| Home-screen widget / menu-bar widget | ✅ | ✅ | — |
| One morning summary; a payment banner only if you set up the Apple Pay automation | ✅ | — | — |
| A trash: a deleted row is recoverable, not gone | ✅ | — | — |
| A demo with invented accounts, erased in one tap | ✅ | — | — |
| Siri shortcuts, in all nine languages | ✅ | — | — |
| Languages | 9 | 3 | 3 |
| iPhone | Mac | Web | |
|---|---|---|---|
| Five themes, light or dark — the two settings compose rather than replace each other | ✅ | — | — |
| One of the five is a second grammar of screen: type and rules, no cards and no emoji | ✅ | — | — |
| Thirty accent colours, as eight flat inks or as eight dilutions of one | ✅ | — | — |
| Day and night switched from the home screen, without opening Settings | ✅ | — | — |
| A colour per account, picked by hand or taken from its name, the same everywhere it appears | ✅ | — | — |
Each theme holds one family of hues — Obsidian runs blue to mint, Slate teal to blue, Sepia brown to sage, Paper is a ramp of greys. Red and green stay out of it: they mean lost and gained.
iPhone: English, French, German, Dutch, Italian, Spanish, Portuguese, Catalan, Turkish. Mac and web: English, French, Dutch.
The same five on every platform, plus Settings — on iPhone behind the avatar in the top-left, so the tab bar keeps all five.
| Tab | What it answers |
|---|---|
| Overview | What am I worth, what is left this month, what happened today. Accounts sit at the top as a wallet of cards, each in its own colour, fanned open with a tap. Under them the net worth curve: tap the figure to swap net for gross, scrub it to read any day, and switch to Prév. to fold the past to three months and see the year ahead dotted. |
| Accounts | What I own and what I owe, grouped by kind, with the allocation ring. Holdings, loans and their schedules open from here. |
| Plan | The month in category groups: planned against spent, carried over from last month in one tap. |
| Activity | Every row, searchable and filterable, with the review queue folded into a panel and counted on the tab itself. A row opens to its detail: category, merchant name, receipt, transfer pairing, instalment plan. |
| Analysis | Spending per day over the month, movers against last month, recurring charges, savings rate. |
Open the app and pick how to start: connect a bank, enter accounts by hand, import a statement, restore a backup — or Try the demo. Everything lives in a SQLite ledger on the phone, included in its iCloud backup. It can also act as a client of your own Florin server (Settings → Use my Florin server).
Sideload, TestFlight, or build it yourself
- Sideload. Each release carries
Florin-<version>-unsigned.ipa, re-signed on the way in with your own Apple ID by AltStore, SideStore or Sideloadly. A free Apple ID re-signs for seven days at a time.- Bank sync needs an Associated Domains entitlement, which a free Apple ID cannot declare: host your own
apps/site/.well-known/apple-app-site-associationand pointBankingFlow.redirectHostandproject.ymlat it. - Notifications and background refresh are gated the same way.
- Bank sync needs an Associated Domains entitlement, which a free Apple ID cannot declare: host your own
- TestFlight. Properly signed, no seven-day expiry, bank sync works. The steps and the review note are in
apps/ios/TESTFLIGHT.md. - Build it with Xcode 26+ and XcodeGen:
Pick your team in Signing & Capabilities. Deployment floor: iOS 17.4.
cd apps/ios && xcodegen generate && open Florin.xcodeproj
- Install your own builds over the air.
apps/ios/OTA.mdsets up anitms-servicesmanifest on a host of your own, so a build installs from any network without a cable.scripts/install-device.shdoes the same over USB.
Download the .dmg, drag Florin to Applications, launch — onboarding covers language, categories and your first account. Updates arrive on their own (checked at launch and every 6 hours). Data lives in ~/Library/Application Support/@florin/desktop/florin.db.
Build the Mac app from source
Requires Node 22 — better-sqlite3 has no prebuilt binary for newer majors.
pnpm install
pnpm --filter @florin/desktop run pack # → apps/desktop/dist/Florin-<version>-<arch>.dmgLocal builds are unsigned: right-click → Open, or xattr -dr com.apple.quarantine /Applications/Florin.app. Releases are cut by pushing a Florin-v* tag; CI builds both architectures, signs and notarises. Forking? Point publish.owner/publish.repo in apps/desktop/electron-builder.yml at your repo, or your users will auto-update onto upstream builds.
A single-admin Next.js + Postgres stack, installable as a PWA. Needs Docker, plus Node 22 and pnpm for the setup scripts.
Install steps
git clone https://github.com/adrbn/florin.git && cd florin
cp .env.example .env
openssl rand -base64 32 # → DB_PASSWORD
openssl rand -base64 32 # → NEXTAUTH_SECRET
cd apps/web && pnpm install
pnpm tsx scripts/hash-password.ts "your-strong-password" # → ADMIN_PASSWORD_HASHPaste the hash into .env, escaping every $ as \$, then:
cd .. && docker compose up -d
cd apps/web && pnpm drizzle-kit migrate && pnpm tsx src/db/seed.tsOpen http://localhost:3000. Put a reverse proxy with TLS in front (Caddy, Traefik, Tailscale Serve) before exposing it anywhere.
Florin has no shared bank connection. You register your own free Enable Banking application once; the private key never leaves your device.
| iPhone | Mac | Web | |
|---|---|---|---|
| 1. Key | Settings → Bank connection → Create a key | Settings → Bank Sync → Generate a key | openssl genrsa -out enablebanking-private.pem 2048 |
| 2. App | Create a Production app, paste the certificate | Create an app, paste the public key | Create an app, upload the public key |
| Redirect URI | shown in the app | https://127.0.0.1:3847/api/banking/callback |
https://<your-domain>/api/banking/callback |
| 3. App ID | Paste it back in Settings | Paste it back in Settings | ENABLE_BANKING_* in .env |
Pick Production, not Sandbox: an application is activated by linking real accounts, and the API returns only the accounts you linked. Some banks expose less than others — PayPal returns no accounts at all over PSD2 — which is why the sync log records what each one actually sent back.
No bank? Manual entry and CSV/OFX import cover everything else.
France/EUR-first defaults, every one a setting — environment variables on the web, Settings → App on the Mac.
| Setting | Default | What it does |
|---|---|---|
APP_CURRENCY |
EUR |
Display currency and number formatting |
APP_GOAL_TARGET |
100000 |
Long-term wealth target on the goal card |
APP_GOAL_RETURN_PCT |
7 |
Assumed net annual return for the projection |
APP_PEA_CEILING |
150000 |
Contribution cap for a tax wrapper (France's PEA); 0 hides it |
APP_DCA_MONTHLY |
(blank) | Planned monthly investment; blank = inferred from history |
PRICE_PROVIDER |
none |
yahoo opts into live quotes; off by default, no outbound calls |
| iPhone | Mac | Web | |
|---|---|---|---|
| Import | Settings → Import a statement | Drop a CSV/OFX on an account | Drop a CSV/OFX on an account |
| Backup | iCloud backup, plus Export a copy (plain SQLite) | Copy florin.db, or JSON export |
pg_dump (below) |
| Move to a new device | Restore a copy, also offered at first launch | Copy the file across | Restore the dump |
Commands
# Web — a timestamped Postgres dump
docker exec florin-db pg_dump -U florin -d florin --no-owner --no-privileges \
| gzip -9 > "backups/florin-$(date -u +%Y%m%dT%H%M%SZ).sql.gz"
# Web — import a YNAB-style spreadsheet (idempotent)
cd apps/web && node --env-file=.env --import tsx scripts/import-legacy-xlsx.ts /path/to/finances.xlsx
# Mirror one ledger onto the other (both directions, both truncate the target)
python3 scripts/pg-to-sqlite-dump.py # server → Mac
python3 scripts/sqlite-to-pg-dump.py # Mac → serverA restored iPhone copy does not carry the bank connection: a PSD2 session restored onto another phone is a dead session, and the signing key is marked ThisDeviceOnly. Reconnect the bank on the new phone.
apps/
ios/ SwiftUI app with its own SQLite ledger (XcodeGen)
Florin/Local/ the ledger: queries, writes, parsers, repairs
Florin/Screens/ five tabs, their sheets, Settings
scripts/ USB install, over-the-air publish
desktop/ Electron + Next.js + SQLite, menu-bar widget
main/ Electron process: tray, IPC, scheduler, updater
tray-ui/ the menu-bar popup, raw DOM, no React
web/ Next.js 15 + Postgres, Docker
site/ Static pages and the Associated Domain file for bank redirects
packages/
core/ Shared UI, i18n, bank client, categorisation
db-pg/ Postgres schema and queries ┐ deliberate twins —
db-sqlite/ SQLite schema and queries ┘ change them together
scripts/ Mirror one ledger onto another (SQLite ⇄ Postgres)
Three ledgers, one shape: Postgres for the server, SQLite for the Mac, and a third SQLite schema written in Swift for the iPhone. A column added to one belongs in all three.
Issues and pull requests are welcome. CONTRIBUTING.md covers the setup for each platform and the two rules that matter most:
- the Postgres and SQLite packages change together;
- a computed figure is tested against something real.
Security issues go through SECURITY.md, privately. If Florin is useful to you, you can buy me a coffee.
Florin is open source under the AGPL-3.0: self-host it, fork it, modify it, redistribute it. Any hosted or distributed derivative must publish its source — a Florin someone runs for you should be a Florin you can read.
The name and logo are not part of that grant. A modified version ships under its own name and icon; see TRADEMARK.md and the section 7 terms in NOTICE.md. Florin collects no data at all — see PRIVACY.md.




