A relatively simple, automated workflow using current hledger features. This is the kind of setup I use myself as of 2023. The Files layout will give you the gist of it. It is a journal first setup (journal files are primary, CSV files are disposable).
hledger 1.30+ ,
git (or other version control system),
Here are the main files, grouped for clarity. These are unix-style file paths; on Windows, they may be a little different. These filenames are reasonably good for typing, autocompleting, visual grouping, editor configuration, etc. But you might find better ones.
/home/USER/ finance/ home of finance files; wherever you wish .git/ most files are tracked with git or other VCS hooks/ pre-commit runs hledger check -s ordereddates recentassertions bin/ extra scripts/tools for finance work bashrc extra shell config gsheet-csv get a Google sheet as CSV paypaljson get recent Paypal transactions as JSON paypaljson2csv convert Paypal JSON to CSV justfile commands/scripts for common tasks 2023.journal main journal file for 2023 (current year) 2023accounts.journal account declarations, included by main journal 2023prices.journal market price declarations, included by main 2022.journal main journal file for 2022 (etc..) 2022accounts.journal 2022prices.journal forecast.journal future transactions / periodic transaction rules bi-ichecking.csv.rules CSV conversion rules for each bank account paypal.csv.rules wf-pchecking.csv.rules wf-bchecking.csv.rules wf.rules common rules included for all wf accounts common.rules common rules included by all Downloads/ hledger looks here for CSV files by default; these file names are declared in *.csv.rules Checking1.csv a bank account's recent transactions Checking1-1.csv newer copies saved by web browser Checking1-2.csv the newest; hledger will read this one Savings1.csv Transactions-2342.csv
justfile contains common reporting commands and task scripts, which can be listed and invoked by
just1. I alias
bin directory is a place to keep additional scripts and tools; it should be added to your shell's PATH.
The scripts and an example justfile can be found at Scripts and add-ons.
When you want to add a custom script, you have a choice:
- you can define it as a shell alias or function in
- you can define it as an executable file in
- you can define it as a script in
Makefile, Shake file, or other build tool)
Over time, scripts can proliferate and become a maintenance and memory problem. I recommend using
justfile where possible; it is the best combination of ease and power. I am increasingly using it as the starting point for all finance tasks.
The main journal file is
LEDGER_FILE environment variable should be set to this.
It contains these sections, one after another:
- commodity declarations - for error checking and to set display styles/precision - recommended
- tag declarations - if you use tags, for error checking
includedirectives - to include other files. I try to keep files to a minimum, to reduce headaches, but certain things are convenient to keep in separate files, like
- (account aliases - I avoid permanently defined aliases, since they add complexity. But if you need them, define them here so they can affect everything.)
- transactions - from all accounts, in date order. New transactions can simply be appended at the end.
Account declarations are kept in a separate
YEARaccounts.journal, included by the main journal, for more convenient reviewing and updating. These provide error checking and declare account types, account display order, account tags and/or comments. New account declarations can be inserted in the correct place, or appended at the end.
account assets ; type:A account liabilities ; type:L account equity ; type:E account revenues ; type:R account expenses ; type:X account assets:bank ; type:C account assets:cash ; type:C account equity:conversion ; type:V account assets:bank:checking account assets:bank:savings # etc...
As a sole proprietor, I like to track my business and my personal life as two separate entities. Legally we are one entity, and there are frequent transactions between them, so it's convenient to keep them in the same journal. But I use a two letter entity prefix to keep the accounts clearly separated. So my account declarations look like this:
; business 1 account JS:assets ; type:A account JS:liabilities ; type:L account JS:equity ; type:E account JS:revenues ; type:R account JS:expenses ; type:X account JS:assets:bank ; type:C account JS:assets:cash ; type:C account JS:equity:conversion ; type:V account JS:assets:bank:checking account JS:assets:bank:savings # etc... ; personal account sm:assets ; type:A account sm:liabilities ; type:L account sm:equity ; type:E account sm:revenues ; type:R, taxable personal revenues account sm:income ; type:R, already-taxed "salary" from JS, non-taxable income account sm:expenses ; type:X account sm:assets:bank ; type:C account sm:assets:cash ; type:C account sm:equity:conversion ; type:V account sm:assets:bank:checking account sm:assets:bank:savings # etc...
Market price declarations are also kept in a separate, included file,
YEARprices.journal, in date order. New prices can be appended at the end. Eg:
# ... P 2023-06-08 00:00:00 £ $1.25161 P 2023-06-08 00:00:00 ¥ $0.00719 P 2023-06-08 00:00:00 € $1.07676 P 2023-06-09 00:00:00 £ $1.25777 P 2023-06-09 00:00:00 ¥ $0.00717 P 2023-06-09 00:00:00 € $1.07713 P 2023-09-04 00:00:00 € $1.08021 # ...
forecast.journal is a place to note expected future transactions, one-time or recurring.
include it in the main journal, and I put only periodic transaction rules (and a few auto posting rules) in it.
This means it has no effect until I run reports with the
Or you could put ordinary transaction entries here; in that case you may want to not include it, to keep future events out of reports by default.
Each CSV (or TSV, SSV, ...) data source has a CSV rules file declaring how to convert it to meaningful journal entries. Each rules file declares its corresponding data file's name, so that (eg)
hledger import wf-pchecking.csv.rules will automatically look for the latest-numbered
Checking1*.csv file in the
Some rules files are common rules included by the others, eg
~/Downloads is where my mac web browsers save downloaded CSV files, and it's where the
source rule looks for data files by default. If you don't have this folder on your system, you can make it, or use a symbolic link; or you can specify a different folder in your
When downloading bank CSV files, you don't need to care much about which dates you download; hledger's
import system will usually do the right thing. Just make sure to download enough data to cover the period since your last import (eg the last 30 days, last year, or all transactions).
You also don't need to care much about managing downloaded CSV files; your browser will give them unique filenames, and hledger will automatically choose the latest ones. After successful import you can either delete the CSV files, keep them around for a while for troubleshooting, or archive them permanently.
In the justfile I have a
foo-import script for each data source foo, and the
import script runs all of them. So it's
- download one or more CSVs manually in web browser
j import --dry
- load journal in emacs + ledger-mode + flycheck-hledger
- select new transactions,
M-qto align them
- for each new transaction: review, refine and mark it cleared when appropriate.
- open flycheck's error list with
C-c ! l, jump to and resolve each issue in turn. These will be things like typos, uncategorised postings, mis-ordered dates etc.
- I also run the
recentassertionscheck, which periodically forces me to add more recent balance assertions for my main accounts. I do this by adding a "reconcile" transaction (using
yasnippet, by typing "reconcile" and TAB); filling in the balances hledger expects, to make the errors go away
- When the journal is again error-free, I check each account's real-world balance against the "reconcile" transaction's asserted balance (or in a balance report or hledger-ui accounts screen), and resolve any disagreements.
- Finally I commit the changes to journal, rules and scripts. Git's
pre-commithook runs my checks one more time, catching any last-minute errors.
just is Like
make, but better for this purpose. I recommend trying it, even though it doesn't come with your OS; I say this after years of costly battle with make, shell, and other scripting setups.