Table of contents
Open Table of contents
Introduction
Every transaction I make - every bus ride, every coffee, every gift - goes into one plain text file. No banking app, no aggregator, no spreadsheet. Just a text file and Ledger, a command-line double-entry accounting tool.
If presentations can be code, accounting definitely can.
Note: All names, banks, tickers and amounts in this post are made up. The structure is exactly how I run mine; the numbers are not.
Purpose
- Understand plain text accounting - double-entry in one minute, and why a text file beats an app.
- Structure a journal - the account tree, multi-currency handling (USD, EUR, gold, silver, stocks, crypto) and the file conventions I settled on.
- Query it - the aliases and commands I actually run, including a few that answer suspiciously specific questions.
Prerequisites
- A terminal.
ledgerinstalled:apt install ledger, ornix shell nixpkgs#ledger- I am a NixOS person, after all.- The discipline to spend two minutes a day logging. This is the hard part; the tool is the easy part.
Why a Text File and Not an App
- Want your full financial history on someone else’s server, categorized by their ML model?
- Banking app: yes, by default.
- Ledger: it is a file on your disk.
grepis the ML model.
- Want version control for your money?
- Ledger:
git add finances.ledger && git commit -m "July"
- Ledger:
- Want gold grams, EUR, stocks and crypto in the same report as your main currency?
- Excel: prepare the VLOOKUP of your nightmares.
- Ledger: built in. One flag converts everything.
- Want to know how much one specific doner place has cost you since 2025?
- Ledger: one command, milliseconds.
And it is free, works offline, and the file will still open in 30 years.
Double-Entry in One Minute
Every transaction moves value between at least two accounts, and the postings must sum to zero. All accounts live under five roots. The comment header at the top of my file is the whole theory:
; Assets - Bank Accounts, Wallet, Investments, Loans credited
; Income - Paychecks, Gifts received, Dividends, Interest
; Expense - Groceries, Taxes, Gifts given, Donations
; Liabilities - Mortgage, Credit cards, Student loans, Loans owed
; Equity - Untrackable errors or Opening Balances
One sign convention to internalize: Income is negative. Money flows from Income into Assets, so a salary posting is -3000 USD on the Income side. It feels wrong for a day and then it clicks.
The Journal
A routine day in the file looks like this:
2026-07-10
Assets:Cash:Bank:WeeWeeBank:Checking
Expenses:Groceries:PurPurMarket 7.12 USD
Expenses:Transport:Transit:Bus 0.60 USD
Expenses:Food:Restaurants:Doner 9.80 USD
The conventions I converged on:
- One transaction per day per funding account. The account without an amount is elided - Ledger balances it automatically (here:
-17.52 USDleaves Checking). No payee line for routine days; I name transactions only when they are worth remembering (“Buy gold”, “Pay Day”). - A subaccount per venue and per person.
Expenses:Food:Cafes:<CafeName>,Expenses:Gifts:Monetary:<Person>,Expenses:Transport:Taxi. The tree grows organically - a new place is just a new line - and “how much did I ever spend there” becomes free. - Day one is an
Opening Balancestransaction againstEquity:OpeningBalances, with whatever numbers you remember. Precision comes later; starting matters more.
A payday - income negative in the wild, with the day’s expenses and cashback netted into the same transaction:
2026-07-02 Pay Day
Assets:Cash:Bank:WeeWeeBank:Checking
Income:Salary:Employer -3000.00 USD
Expenses:Groceries:PurPurMarket 17.61 USD
Income:Cashback:Bank:WeeWeeBank -5.00 USD
Debts are assets. Lending moves money from the bank into Assets:Receivable:<Person>, repayment moves it back, and if it is never coming back - reclassify and accept reality:
2026-03-01
Assets:Receivable:Friend 100 USD
Assets:Cash:Bank:WeeWeeBank:Checking
2026-04-01
Assets:Cash:Bank:WeeWeeBank:Checking 100 USD
Assets:Receivable:Friend
2026-06-30 Clearing Receivables
Assets:Receivable:Friend -50 USD
Expenses:Gifts:Monetary:Friend 50 USD
The receivable balance is always the honest answer to “who owes me what”.
More Than One Currency
In Ledger everything is a commodity: USD is one, EUR is one, a gram of gold is one, a share of ACME is one. Buying an asset records the total cost with @@:
2026-01-05 Buy gold and silver
Assets:Cash:Bank:WeeWeeBank:Checking
Assets:Investments:PreciousMetals:Gold 5 GOLD @@ 545 USD
Assets:Investments:PreciousMetals:Silver 20 SILVER @@ 26 USD
Expenses:Fees:MetalPremium 21 USD
Note the premium split into its own expense - the bank’s margin over spot is a fee, not an investment. Stocks and crypto work the same way:
2026-01-06
Assets:Cash:Bank:WeeWeeBank:Checking
Assets:Investments:Stocks:Broker 2 ACME @@ 505 USD
Assets:Crypto:Exchange 0.5 COIN @@ 60 USD
Even exchanging cash is just a commodity purchase:
2026-01-10
Assets:Cash:Pocket:Checking 100 EUR @@ 108 USD
Assets:Cash:Bank:WeeWeeBank:Checking
Market prices are P directives that I update by hand every month or two:
P 2026-07-01 EUR 1.08 USD
P 2026-07-01 GOLD 109 USD
P 2026-07-01 SILVER 1.30 USD
P 2026-07-01 ACME 260 USD
Plain ledger bal Assets then reports each commodity separately; add -X USD and the whole portfolio collapses into one USD number at the latest known prices.
Where Did the Cash Go
Pocket cash never reconciles. Instead of pretending it does, I book the difference honestly:
Expenses:FX:Loss 2.05 USD ; idk where
Those entries accumulate into the real, measured cost of carrying physical cash. Income:FX:Gain exists too, for the rarer opposite case of finding money on your own shelf.
Daily Workflow
Aliases, because I run these constantly:
alias lg='ledger'
alias lgb='ledger b'
alias lgba='ledger b Assets'
alias lgbe='ledger b Expenses'
alias lgbi='ledger b Income'
At the end of the day I append one transaction from the bank app history and one from memory for cash. To lower the friction I pre-create empty date lines for the week ahead:
2026-07-25
2026-07-26
2026-07-27
They parse fine, and an empty date staring at you is a surprisingly effective nag to fill it.
The Queries I Actually Run
# net worth, per commodity
lgba -f finances.ledger
# net worth, one number
lgba -f finances.ledger -X USD
# liquid money only - boolean filters on account names
lgba -f finances.ledger -X USD and not gold and not crypto and not saving
# this month's burn and income
lgbe -f finances.ledger --begin "2026-07-01"
lgbi -f finances.ledger --begin "2026-07-01"
# monthly expense totals across the whole history
lg -f finances.ledger reg ^Expenses -X USD -M --collapse
# lifetime damage in one supermarket chain
lgb -f finances.ledger PurPurMarket
The query language is just account regexes plus and/or/not plus date windows. That is enough to answer almost anything.
What the File Knows About You
After a while, the fun part is asking questions you never planned to ask:
# most frequent expense accounts - where does life actually happen
lg -f finances.ledger reg ^Expenses -F "%(account)\n" | sort | uniq -c | sort -rn | head
# how many distinct restaurants have I tried
lg -f finances.ledger accounts Expenses:Food:Restaurants | wc -l
# savings rate in one line
lg -f finances.ledger bal ^Income ^Expenses -X USD --depth 1
# average cost of a taxi ride vs a bus ride - you already know, but now in numbers
lg -f finances.ledger bal Expenses:Transport:Taxi -X USD
lg -f finances.ledger reg Expenses:Transport:Taxi | wc -l
I will keep my own numbers to myself, but I promise some of them surprised me.
Tips
1. LLM
Once an LLM has the Ledger manual and your file, it writes any query you can describe in words, finds patterns you did not think to look for, and catches inconsistencies. This is the same trick as with Presenterm: plain text formats and LLMs are a natural pair.
2. Guard the Account Names
A typo silently creates a new account - Expenses:Grocery next to Expenses:Groceries will split your data without a single error. Run ledger accounts once in a while and read the list; renames are one sed away.
3. Start Stupid, Split Later
Do not design the perfect taxonomy upfront. Log everything to Expenses:Food and split into subaccounts the day you have a question that needs the split. The file is text; restructuring it is editing, not migration.
4. Edit in Neovim
The vim-ledger plugin adds syntax highlighting, folding, account completion and amount alignment for .ledger files, which makes a growing journal far easier to read and keep consistent.
Conclusion
One text file under git, two minutes a day. In return, any question about my money is one command away - in a format no vendor can take away or discontinue.
References
- Ledger CLI and the full manual
- plaintextaccounting.org - the wider ecosystem
- Alternatives in the same family: hledger, beancount
Appendix: A Sample Journal
Everything from this post in one small, valid file. Save it as finances.ledger and start querying:
; Assets - Bank Accounts, Wallet, Investments, Loans credited
; Income - Paychecks, Gifts received, Dividends, Interest
; Expense - Groceries, Taxes, Gifts given, Donations
; Liabilities - Mortgage, Credit cards, Student loans, Loans owed
; Equity - Untrackable errors or Opening Balances
P 2026-01-01 EUR 1.08 USD
P 2026-01-01 GOLD 105 USD
P 2026-01-01 SILVER 1.20 USD
P 2026-01-01 ACME 250 USD
2026-01-01 Opening Balances
Assets:Cash:Bank:WeeWeeBank:Checking 1500 USD
Assets:Cash:Bank:WeeWeeBank:Saving 2000 USD
Assets:Cash:Pocket:Checking 120 USD
Assets:Cash:Pocket:Checking 50 EUR
Liabilities:StudentLoans:University -4500 USD
Equity:OpeningBalances
2026-01-02 Pay Day
Assets:Cash:Bank:WeeWeeBank:Checking
Income:Salary:Employer -3000.00 USD
Expenses:Groceries:PurPurMarket 17.61 USD
Income:Cashback:Bank:WeeWeeBank -5.00 USD
2026-01-03
Assets:Cash:Bank:WeeWeeBank:Checking
Expenses:Food:Cafes:Coffee 3.20 USD
Expenses:Transport:Transit:Bus 0.60 USD
Expenses:Transport:Transit:Bus 0.60 USD
Expenses:Subscriptions:Music 11.99 USD
2026-01-04
Assets:Cash:Pocket:Checking
Expenses:Food:Restaurants:Doner 9.80 USD
Expenses:FX:Loss 2.05 USD ; idk where
2026-01-05 Buy gold and silver
Assets:Cash:Bank:WeeWeeBank:Checking
Assets:Investments:PreciousMetals:Gold 5 GOLD @@ 545 USD
Assets:Investments:PreciousMetals:Silver 20 SILVER @@ 26 USD
Expenses:Fees:MetalPremium 21 USD
2026-01-06
Assets:Cash:Bank:WeeWeeBank:Checking
Assets:Investments:Stocks:Broker 2 ACME @@ 505 USD
Assets:Crypto:Exchange 0.5 COIN @@ 60 USD
2026-01-07
Assets:Receivable:Friend 100 USD
Assets:Cash:Bank:WeeWeeBank:Checking
2026-01-10
Assets:Cash:Pocket:Checking 100 EUR @@ 108 USD
Assets:Cash:Bank:WeeWeeBank:Checking
2026-01-15
Assets:Cash:Bank:WeeWeeBank:Checking 100 USD
Assets:Receivable:Friend
2026-01-16
2026-01-17
2026-01-18


