Ruan4k downloadsPersonal budget dashboard — reads and writes markdown files in your vault's budget folder. Dashboard, transactions, budgets, savings, accounts, owed money, services, tax and CSV import.
Most budgeting apps ask you to upload your bank statements and trust someone else's server with them. Budget Vault doesn't.
Every account, budget and transaction is a plain markdown file in your own vault, on your own devices. There is no company database, no account to create, and nothing to export when you leave — the files are already yours.
Out of the box the plugin makes zero network requests, so your financial history can't be uploaded, analysed or sold. There is exactly one thing that can change that, and only if you switch it on: if you hold money in more than one currency, you can ask the plugin to fetch exchange rates so those accounts can be added into your totals. It is off by default, the setup wizard asks about it outright, and when it is on it sends a three-letter currency code and nothing else — no balances, no account names, nothing about your vault. The rates are saved as an ordinary note in your budget folder that you can read, edit or delete. The next section names who it asks.
Only when Exchange rates is switched on (Settings → Budget Vault, or the setup wizard's rates step), the plugin asks open.er-api.com — the free rates endpoint run by ExchangeRate-API — for the day's rates, at the daily, weekly or monthly rhythm you pick. The request is https://open.er-api.com/v6/latest/<CODE>, where <CODE> is your home currency's three-letter code (for example ZAR).
Exchange Rates.md in your budget folder. Every converted figure is printed with the date of its rate.The provider asks for this credit wherever its rates are used: Rates By Exchange Rate API. The same link sits beside the setting and in the wizard.
Put your vault in iCloud Drive, Obsidian Sync, Dropbox or Syncthing and your budget follows you to every device — synced by a service you chose, not one we picked.
From Obsidian's community plugins (the usual way):
By hand (to try a build that is not released yet):
manifest.json, main.js and styles.css into <your vault>/.obsidian/plugins/budget-app/A setup wizard runs on first launch. On mobile, repeat the enable step once — Restricted mode is per-device.
Open the vault, not the budget folder. Obsidian loads plugins from
<vault root>/.obsidian/plugins/, so Budget Vault only exists in the vault you installed it into. If you use Open folder as vault onFinances/— the folder holding your budget — the plugin is simply not there: no wallet icon, no command, no error. It looks like a failed install and is not one. Opening that inner folder also creates a second.obsidianinside it, so the mistake sticks until you delete that folder and reopen the outer vault.
One folder you choose, default Finances/Budget:
Finances/Budget/
├── Settings.md
├── Categories/ one file per category
├── Accounts/ one file per account
├── Budgets/ one file per period (YYYY-MM.md)
├── Transactions/
│ └── <Account>/YYYY-MM.md
├── Data/Categorisation Rules.csv
├── Receipts/ one file per till slip (YYYY-MM/), plus Food Items.md
├── Tax/
├── Debts.md
├── Owed Money.md
└── Services.md
Ordinary markdown tables and frontmatter. Open them in any editor, diff them in git, keep them forever.
Note the two levels: Finances/ is an ordinary folder inside your vault, and
Finances/Budget/ is what the plugin reads. Neither of them is the vault —
that is the folder above them, the one holding .obsidian/. See the note under
Install for what happens if you open one of them as a vault instead.
Two folders sit outside the budget folder, at the top of your vault, because what they hold isn't household data to load back in — it's something you asked the plugin to write out: Exports/ (CSV/Markdown transaction exports, and the Budget page's Export — your budget for the last month, quarter, year, a tax year or any two dates, as a PDF, an Excel workbook or CSV) and Reports/ (the one-note financial report, Markdown and/or JSON). Both default to those names but are yours to rename or relocate from the Transactions, Budget and Report pages, and both show you the exact path before anything is written.
Columns are read by position, not by header name, so keep the first twelve in this order — a table missing one silently shifts every later value into the wrong field. A thirteenth, Currency, is optional and always last: a file without it loads exactly as before.
| Column | Meaning |
|---|---|
| Name | What the debt is. Need not be unique. |
| Lender | Who it is owed to. |
| Type | credit card, personal loan, vehicle, home loan, student, store account, overdraft, other. Free text is kept as-is. |
| Balance | What is still owed today. |
| Original | What it started at — drives the "paid off" bar. Leave blank to reuse Balance. |
| Rate | Annual interest rate as a percentage, e.g. 22.25. Compounded monthly. |
| Payment | The contracted monthly instalment. |
| Extra | Anything you pay on top of it every month. |
| Start date | When it opened, YYYY-MM-DD. |
| Category | Budget category whose transactions pay this debt. Blank means untracked. |
| Status | active or paid. |
| Notes | Anything else. |
| Currency | Optional, last. The currency symbol this debt is stated in, e.g. $. Blank means the household's own currency. |
The plugin rewrites the whole table on save, so a file created through New debt always has the right shape. This matters only if you write one by hand.
A book is one budget folder — its own Settings.md, categories, accounts and transactions. Most people need exactly one, and a vault with one book looks and behaves as it always has: no switcher, nothing new to learn. If you keep several — a book per client, or a personal and a business book — add the others under Settings → Budget Vault → Books, and switch between them from the top bar (on a phone, from the menu drawer).
Settings.md and every transaction in it stay exactly where they are, and adding the folder again later brings the book back as it stands. If the book you remove is the one that's open, you are switched to the home book first — and, like any switch, that is refused while anything is unsaved. The home book — the one named in the Budget folder setting — can't be removed.The reasoning is written up in ADR 0009.
Any statement with Date / Description / Amount columns works. Discovery Bank, FNB, Capitec and Nedbank exports have been imported end to end, but nothing is hardcoded per bank — columns are matched by header, then by layout shape for headerless files, and you can map them by hand if neither resolves.
A Discovery Bank Miles export (DiscoveryBankMiles_….csv, which has a Miles column and no amount) is read on its own path: every Mile is valued at one rate you set in Settings (default 0.10, so 10 Miles = R1), the exact Miles count and any value the bank printed for a redemption are kept in each row's Note, and the rows go to an account that is outside the budget, since the card spend that earned them is already in your bank account.
Sign conventions are verified, not guessed: where a statement carries a running balance, the amounts are checked against it, so a bank that lists money out as a positive number can't quietly import every expense as income. If it can't be verified, the review screen says so.
No supported bank? Build your own CSV with Date, Title and Amount columns — negative for money out.
Budget Vault exposes a small headless API at app.plugins.plugins['budget-app'].api, so a sibling plugin can show the Dashboard's own "left to spend" figure instead of working it out from the files a second time and arriving at a different number.
apiVersion — 2 today. Version 2 only adds to version 1, so a version 1 consumer keeps working unchanged; test apiVersion >= 2 before calling the two books functions.currentPeriod() — resolves to the open book's figure for the current period (budgeted, spent, left and the notes the Dashboard prints beside them — and, from version 2, book: { folder, label }, so the figure says whose it is), to { locked: true } while the privacy splash screen hasn't been opened, or to null when no budget is set up.onChange(cb) — calls cb() after a change to the budget has been reloaded, when the privacy lock opens or closes, and after a switch of book. Returns a function that unsubscribes.books() (version 2) — resolves to [{ folder, label, active }]: the home book first, exactly one of them active. A vault with a single book answers with one entry, so there is no "no books" case to handle. While the privacy lock is closed it resolves to { locked: true } instead — a book's label is usually a client's name.setActiveBook(folder) (version 2) — switches the open book on this device, under the same rules as the top-bar switcher. Resolves to { ok: true } or { ok: false, reason }, where reason is 'unknown' (not one of your books), 'dirty' (a page has unsaved edits) or 'locked'; it doesn't throw for any of the three, and a refusal changes nothing. Pass a folder exactly as books() gave it.The API reads. The one thing it can change is which book is open on this device. While the privacy splash screen is closed it reads nothing from the vault and says only that it is locked — a consumer should say the same, not fall back to reading the budget files itself. The whole contract, guarantee by guarantee, is the header of src/api.js.
src/ holds small vanilla-JS modules — no framework, no dependencies. main.js is build output; never edit it by hand.
./build.sh
Then toggle the plugin off and on.
The animation at the top of this page is docs/onboarding/onboarding-motion.html —
open it in a browser to watch it with controls, or in 9:16 for phones. After changing a scene, re-record the
images with scripts/record-onboarding.cjs (its header lists what it needs).
The budget period is the one piece of the app whose behaviour isn't obvious from the code alone — it has two naming shapes, a pay-cycle phase, and its own income arithmetic:
Settings → Budget Vault → Send feedback, or open the form. Nothing from your budget is attached — the button just opens the form in your browser.
Budget Vault is free and always will be. If it's useful to you and you'd like to say thanks, you can send something via PayPal — entirely optional, and nothing in the plugin changes either way.
GNU Affero General Public License v3.0 © Ruan Pienaar
Use it, change it, share it — personally or commercially, free of charge. The condition is reciprocity: if you distribute a modified version, or run one where other people can use it over a network, those people get its source under this same licence.
Releases up to and including 1.11.11 were published under the MIT licence and stay MIT for anyone holding a copy of them. See NOTICE for the full licence history.
Two things in a release are not the author's own work. Each keeps its own terms, and NOTICE carries them in full:
styles.css
as a single Latin-subset variable font, used as supplied. Copyright 2024 The
Geist Project Authors (vercel/geist-font),
licensed under the SIL Open Font License, Version 1.1.
The OFL covers the font only; it does not change the licence of the plugin's
own code.