Zero-based envelope budgeting that never leaves your device.
Try the demo ·
User guide ·
English · Português (BR)
Warning
Moneta is an experimental personal project. It was written 100% by LLMs, as a way for me to learn. It is usable, but it comes with no guarantees: I take no responsibility for lost or stolen data. Use it at your own risk and keep backups somewhere you control.
Moneta is a budgeting app in the spirit of YNAB and Actual Budget. You give every unit of income a job: money lands in Ready to Assign, you move it into category envelopes, and spending draws those envelopes down. When an envelope runs dry, you decide where the money comes from, instead of finding out at the end of the month.
It runs entirely in your browser. There is no server, no account and no tracking: your budget is a SQLite database stored on your device, and it stays there. Install it as an app and it works offline, on your phone or your computer.
Try the demo: it opens a year of sample data, and nothing you do there is saved. The user guide explains how to budget with it, screen by screen.
- Zero-based envelope budget. Income lands in Ready to Assign and you spread it across categories, organized in groups. Groups, categories and accounts can wear an emoji from the whole Unicode set. Overspending carries over per category, and quick-assign fills a month for you: same as last month, the average spent over 3, 6 or 12 months, cover overspending, or clear.
- Accounts and transactions. Budget and tracking accounts, credit cards whose payments don't touch the budget, split transactions, transfers between accounts, and payees you can rename everywhere, merge, and give a default category. Six colored flags you can name mark transactions to find them again in the filters and the reports. Select several transactions to set their category, date or flag, clear or delete them at once, and undo a deletion, a money move or an import right after.
- Bank statements. Import OFX and CSV statements: lines you already entered are matched instead of doubled, a statement imported twice adds nothing, and each payee's usual category comes back by itself. Reconcile an account against the bank's balance to lock what was checked.
- Scheduled transactions. One-off, daily, weekly, monthly or yearly, with a rule for dates that fall on a weekend. They are entered by themselves or with a tap, and each account shows what is coming in the next 30 days.
- Reports. Spending by category and by payee, net worth, cash flow, spending trends, assets and debts by account, and Age of Money, on an overview whose cards you can reorder and hide.
- Your data, on your device. Several budgets side by side, backups in a single
.monetafile (encrypted if you want), CSV and JSON exports, and automatic encrypted backups to your own Google Drive. - Made for every screen. A phone layout with a bottom bar and sheets, a desktop layout with a sidebar and dialogs, light and dark themes, and ten accent colors.
The whole app is in English and Brazilian Portuguese.
Each budget is one SQLite file in your browser's private storage for the site (the Origin Private File System). Nothing leaves your device unless you turn on automatic backups, and then only encrypted.
Because the budget lives in the browser's storage, clearing the site's data deletes it. Keep a backup somewhere you control.
Settings → Backup saves every budget into one .moneta file in your downloads, and
restores the budgets you pick from one. Moneta reminds you when your last backup is more than
two weeks old.
- A
.monetafile is a ZIP:moneta.jsondescribes it, andbudgets/holds each budget's SQLite file. Older.sqlitebackups still restore. - Restoring a budget that is already on the device replaces it, and keeps what it held as a saved copy.
- Before an update changes a budget's format, Moneta saves a copy of the old file (the last three), so an upgrade is never a one-way door.
Turn on Encrypt backups to protect the files themselves. You pick a password and get a recovery key to keep apart from your backups.
- Backing up never asks for anything: Moneta keeps the encryption key on the device. Restoring asks for the password or the recovery key.
- An encrypted
.monetaholds only the encryption settings inmoneta.json, and the whole backup, encrypted with AES-256-GCM, inpayload.bin. The key comes from the password through PBKDF2-SHA256, or from the recovery key through HKDF. - Lose both the password and the recovery key and nobody can open those backups, Moneta included.
Connect your Google Drive once, and Moneta saves an encrypted backup of every budget to a Moneta folder there by itself.
- It backs up two minutes after changes stop, when you switch away with a change waiting, and at start when the last backup is a day old. It runs only while Moneta is open.
- Each device keeps one file per day, its newest and the five days before it, and deletes the older ones.
- Cloud backups are always encrypted, so it asks you to turn encryption on first. Restoring from Drive on another device asks for the password or the recovery key.
- Moneta can see only the files it created in your Drive.
You need Node.js 24 or newer and pnpm 12 or newer.
pnpm install
pnpm build # static build into ./build
pnpm preview # serve that build at http://localhost:4173build/ is a plain static site: no server code, and no COOP/COEP headers needed. To host it:
- Use a host that serves
index.htmlfor unknown paths. - Serve it from the root of a domain: the service worker that makes it work offline is
registered at
/. - Give Moneta an origin of its own. Any other app on the same origin can read its storage (OPFS, IndexedDB, localStorage), budgets included.
The Content-Security-Policy ships inside index.html as a <meta> tag, so the page is
restricted on any host. A meta tag doesn't reach everything, though: workers follow the
headers of their own scripts, and only a header can forbid framing. If your host lets you set
headers, send:
- a Content-Security-Policy for the workers (
/_app/immutable/workers/*and/sw.js); Content-Security-Policy: frame-ancestors 'none'for everything else;X-Content-Type-Options: nosniffandReferrer-Policy: no-referrer;Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=().
netlify.toml has them all, along with the index.html fallback rule. The page's own policy
can't be repeated there, since its script hashes change with every build.
Automatic backups to Google Drive need one small serverless function,
netlify/functions/oauth-token.mts. Google gives a browser app only one-hour tokens unless
the token request carries the OAuth client secret, and that secret must not ship in the app.
The function adds it to the two token requests Moneta makes (signing in, and renewing the
hour-long token) and stores nothing: the long-lived token stays on the device, and backups go
from the browser straight to Drive. Without VITE_GOOGLE_CLIENT_ID in the build the option
doesn't appear, so any static host still works.
- In the Google Cloud console, create a project, enable the Google Drive API and set up
the OAuth consent screen with the
https://www.googleapis.com/auth/drive.filescope. Publish it (In production): while it is in Testing, sign-ins expire after seven days.drive.fileis a non-sensitive scope, so Google doesn't need to review the app. - Create an OAuth client of type Web application, with your site as an authorized
JavaScript origin and
https://<your site>/oauth/callbackas a redirect URI. - In the Netlify site's environment variables, set
VITE_GOOGLE_CLIENT_ID(the client id: public, read by the build and the function) andGOOGLE_CLIENT_SECRET(read only by the function), then deploy again.
To try it locally, put both in .env (see .env.example), run npx netlify dev, and add its
address (http://localhost:8888) to the client's origins and redirect URIs.
pnpm install
pnpm dev # dev server at http://localhost:5173
pnpm dev --open # …and open it in the browser| Command | What it does |
|---|---|
pnpm dev |
Dev server at http://localhost:5173 |
pnpm build |
Static production build into ./build |
pnpm guide |
Write the user guide into ./build/guide |
pnpm guide:screenshots |
Retake the guide's screenshots from the demo |
pnpm preview |
Serve ./build like a static host would |
pnpm test |
All unit tests once (Vitest, Node) |
pnpm test:unit |
The same tests in watch mode |
pnpm test:e2e |
Build, then run Playwright in Chromium |
pnpm lint |
Prettier check + ESLint |
pnpm check |
Type-check with svelte-check |
pnpm format |
Fix formatting with Prettier |
pnpm i18n |
Compile the Paraglide messages |
pnpm bench |
Time the budget recompute on a large budget |
pnpm icons |
Regenerate the PWA icons from static/icon.svg |
Unit and integration tests run in Node with Vitest; the database tests use a real in-memory
SQLite, the same WASM build the app ships. End-to-end tests in e2e/ run the production build
in Chromium:
pnpm exec playwright install chromium # first time only
pnpm test:e2epnpm test:e2e builds first, unless a server is already answering on port 4173. To run the tests
again and again, serve a build in another terminal
(VITE_GOOGLE_CLIENT_ID=e2e-client-id pnpm build && pnpm preview), then run one file at a time
with pnpm test:e2e e2e/budget.e2e.ts. Build again after changing the app.
On a fresh Linux or WSL machine Chromium may fail to start because system libraries are
missing (for example libnspr4.so). Install them once from a regular terminal:
sudo pnpm exec playwright install-deps chromiumsrc/core/domain/ pure TypeScript: money, months, budget engine, quick-assign
src/core/db/ SQLite side (runs in a Web Worker): schema, migrations, repositories, RPC dispatcher
src/core/client/ main-thread side: RPC client, live queries, tab lock, budget registry and session
src/core/i18n/ message catalogs (en, pt-BR), error messages, labels and formats
src/features/ feature modules (colocated screen logic + Svelte components):
budget/ budget grid, category & group sheets, order, progress, view
accounts/ account list, register, account creation dialogs, reconciliation,
statement import (import/: OFX and CSV parsers, review)
transactions/ transaction entry dialog, form validation
flags/ flag picker, flag names, flag filters
schedules/ schedules screen, schedule form, rule summaries
reports/ report cards and pages, overview layout, date ranges
settings/ backup & restore, storage, theme, budget files
onboarding/ first-run steps, starter categories
welcome/ landing page, PWA install dialog
backup/ backups, CSV and JSON exports, backup reminder, cloud backups (cloud/)
demo/ demo dataset, seed data
src/components/ shared Svelte components (ui/ holds shadcn-svelte primitives, app/ holds shell)
src/routes/ SvelteKit pages
guide/ the user guide: Markdown pages (en, pt-BR) and the script that builds them
e2e/ Playwright tests
netlify/ the optional token function for backups to Google Drive
UI text lives in src/core/i18n/messages/en.json and pt-BR.json, and
Paraglide compiles it into
src/core/i18n/paraglide/ (generated, not committed). pnpm dev, pnpm build and pnpm check
compile it for you; pnpm i18n does it on its own. The first compile downloads Paraglide's
message-format plugins from jsDelivr, so it needs a network connection once.
The guide at /guide/ is written in Markdown, in guide/content/en/ and
guide/content/pt-BR/, with the same pages and section ids in both. pnpm build turns it into
plain HTML (no JavaScript) after the app's own build, so the service worker never precaches it:
it is fetched from the host only when someone opens it, and an offline app can't show it.
pnpm dev serves it from the Markdown at http://localhost:5173/guide/, so an edit shows on
reload. Its screenshots in guide/img/ come from the demo, in both languages and themes:
pnpm guide:screenshots takes them again after a change to one of those screens.
Moneta was a Roman goddess, an epithet of Juno as protector of the city's funds. Her name comes from the Latin verb monere, "to remind, to warn, to advise", and Romans honored her as the advisor who would see they never lacked money as long as they stayed just. Money was coined in her temple, which is how her name became the word for coinage itself: money and mint in English, moeda in Portuguese and moneda in Spanish all trace back to her.
A budgeting app that reminds you where your money is meant to go seemed a fitting namesake.
Bug reports, translations and pull requests are welcome. Start with CONTRIBUTING.md: it covers the setup, the handful of rules that keep the money math honest, and what CI expects before a PR can land.
MIT.
