Upgrading to hledger 2
hledger 2 (the 1.99.x previews, and 2.0 when released) is a major update, whose biggest new feature is lot tracking and capital gains reporting. It reads hledger 1 data, and most hledger 1 users can switch to it with no changes. This page helps you check that, and fix anything that needs it. It covers the changes since hledger 1.52 that can affect existing setups; the release notes list all changes.
hledger 1 (1.52.x) continues to receive occasional fixes, and both versions can be installed side by side.
Upgrade steps
-
Install hledger 2 (see Install), keeping hledger 1 available under another name, eg by renaming its executable to
hledger1.Both versions read the same config file. If you later use hledger 2 config file features, such as multi-line command aliases, hledger 1 will refuse to run, even for
--version(see Config files for the details). Run it ashledger1 -nthen, or give it its own config file with--conf. And if hledger reports an unexpected config file error, check that an old hledger isn't running instead (eg one found earlier in your PATH). -
Make sure your data files are backed up, or committed to version control.
-
Check your data with hledger 2:
$ hledger check(Add any extra checks you normally use, eg
hledger check -s.) -
If you import CSV files, run your usual import command with
--dry-runadded, which shows what would be imported without changing anything. Eg:$ hledger import --dry-run *.rules -
Compare your usual reports with hledger 1's. Eg:
$ diff <(hledger1 accounts) <(hledger accounts) $ diff <(hledger1 bs) <(hledger bs) $ diff <(hledger1 is) <(hledger is)(This works in bash or zsh. Elsewhere, save each report to a file and compare the files.)
-
Fix anything found, using the sections below. And update any scripts or config which use changed commands.
Errors you might see
These are errors in hledger 2, but weren't in hledger 1. You probably won't see these.
Decimal marks.
When a decimal-mark directive (or CSV rule) is in effect,
a number that's clearly using the other decimal mark (eg 1,000.00 when decimal-mark , is declared),
or using the declared mark more than once (eg 1,2,34),
is now an error. Fix the number, or the directive.
CSV rules comments.
In CSV rules files, a line beginning with * is no longer a comment, and is reported as an error.
Use # or ; to begin comment lines.
Lot errors.
hledger 1 accepted cost basis annotations like {$50} but ignored them.
hledger 2 uses them to track lots, and checks them:
-
"This acquire posting's cost basis ($50) differs from its transacted cost ($55)": in a purchase like
10 AAPL {$50} @ $55, the two costs must agree. If the difference is real (eg a gift with a carried-over cost basis), record it in another posting, as in this gift example. -
"no ... lots available for disposal": a sale (a negative amount with a
{...}annotation) takes from a lot that the journal never acquires, often because the journal starts partway through your investment history. Add the missing purchases, or an opening entry which acquires the lots, giving each lot's original date and cost:2026-01-01 opening lots assets:stocks 10 AAPL {2024-03-15, $50} equity:opening/closing balances(A dated annotation like this needs hledger 2; hledger 1 can't read it.)
-
"This multi-commodity transaction is unbalanced", on a sale with a cost basis annotation and a gain posting, where
@is the original cost (a common hledger 1 style), like-5 AAPL {$50} @ $50withrevenues:gain $-100: hledger 2 reads@as the selling price. Write the selling price instead:-5 AAPL {$50} @ $70. -
"A final account name part enclosed in { } must be a valid lot subaccount name." Account names ending in
:{...}are reserved for lot subaccounts, and are rejected unless the braces contain a valid lot name. Rename such accounts.
If you don't want to deal with these now, you can run hledger with --ignore-lots or -I.
This skips lot processing, giving results close to hledger 1's. (-I also skips balance assertions.)
For more help with lot errors, see Start tracking lots.
Balance assertions and account checks.
A balance assertion or account check that fails only in hledger 2 may be caused by one of the
changes in how your files are read below, such as aliases with apply account.
Changes in how your files are read
These change how your journal, CSV rules or config files are interpreted. Comparing reports from both versions, as in the upgrade steps above, will show their effects, if any.
-
Config file default command. A config file can no longer choose the command to run. A bare word at the top of its general section, which hledger 1 used as the default command, is now read as a query argument, which quietly filters every report. Remove it, and give the command on the command line.
-
Inferred gains. If your journal has sales with cost basis annotations but no gain posting, like
-5 AAPL {$50} @ $70, hledger 2 infers the realised gain and posts it to a gain account (revenues:gainby default). This will appear on your income statement, which is usually what you want;-Igives hledger 1's results. -
Aliases and
apply account. Whenaliasdirectives andapply accountaffect the same entries, the aliases now apply first, to account names as written, and the parent account is added afterward (as in Ledger). hledger 1 did it the other way round. So aliases written with the parent account (alias business:checking = ...) no longer match, and aliases written without it (alias checking = ...) now do. This can make balance assertions or account checks fail. Update the aliases if you need to. (--aliasoptions are unaffected; they still apply last.) -
Repeated commodity declarations. When a commodity is declared more than once, the later declaration now adds to the earlier one, and replaces its display style only if it gives one. With several
-ffiles, the last file's display style is now used (hledger 1 used the first's), and hledger warns if they differ. -
Repeated CSV settings. When a CSV setting like
date-formatorseparatoris written more than once, the last one now wins. If you wrote one before anincludeto override the included file's, move it after theinclude. -
CSV data directory. The
sourcerule now looks for bare file names and relative paths in adata/directory next to the main journal, then in~/Downloads. (Paths beginning with./or../are still relative to the rules file.) Thearchiverule now saves files indata/archive/there, instead of in adata/directory next to the rules file.
Changes in commands and their output
These affect how you run hledger and what it shows. If your scripts, aliases or config files depend on these, update them:
-
--tldris now--examples. -
stats -1is nowstats --oneline. -
--verbose-tagsis now an option ofprintandrewriteonly. -
hledger test(which runs the built-in unit tests) is nowhledger --test. -
The
demoandcommandscommands have been removed. (hledger help commandslists the commands.) -
print layout.
printnow aligns amounts by their decimal mark.--layout=hledger1gives the old layout. -
Decimal places. Amounts which hledger infers no longer affect a commodity's display precision, so some reports show fewer decimal places.
-
activity. This now shows a weekly chart counting transactions.
activity --postings -D -0shows daily posting counts, as hledger 1's did. -
any: and all: queries. These now also work in posting reports like
registerandbalance, not just inprint. -
accounts and payees. Query terms which only match transactions, like
date:ordesc:, now exclude declared accounts or payees which aren't used. Andpayees payee:REGEXnow matches declared payees. -
commodities --used. This now shows only commodities used in transactions;
--pricedshows those used in P directives. -
hledger-ui now watches your files and reloads automatically by default.
--no-watchturns this off. -
hledger-web, when listening on a non-local address (set with
--host), is now read-only by default. Add--allow=editto allow editing. -
hledger-web now shows its pages in your browser's preferred language, if a translation is available (see Languages for the current list).
--lang enkeeps it in English.
What's new
Some of the most useful new features in hledger 2:
- Speed: hledger 2 is 2-3x faster than hledger 1.52, and uses up to a third less memory.
- Localisation: report headings and hledger-ui/hledger-web's UI can be shown in other languages, with
--lang(more languages are welcome, see Translating hledger). - Lot tracking and capital gains: a commodity can track each purchase's cost basis and calculate gains when you sell, with several cost basis methods (FIFO, LIFO, HIFO, AVERAGE, ..), and the new holdings report shows your investments and their performance.
- Custom commands: define custom command aliases and scripts in your config file
- Simpler importing: the new get command fetches new transactions and market prices,
and with a conventional
data/,rules/,prices/layout,hledger importneeds no arguments (see Finding the data). - CSV rules: merge combines several CSV records into one transaction, functions transform field values, and CSV or rules files can be included in a journal.
- Commodity aliases: commodity aliases help unify a commodity's symbols/units
- Find anywhere:
::TEXTmatches text in any field, as you'd see it inprintoutput. - Compact transactions list: the new transactions (tx) command lists transactions one per line.
- Charts: reports can show charts, in the terminal or as SVG/HTML, with
--chart. - Amount expressions: a posting amount can be simple arithmetic, like
$50.20 + $16.54. - hledger-ui updates automatically when your files change, by default.
- hledger-web gains balance reports, a holdings page, charts, report controls and keyboard shortcuts, paging and a dark mode.
For all changes, see the release notes.
Turning on lot tracking
Once your data works with hledger 2, you can (if you want) let it track and check your investment lots.
Start tracking lots explains why and how, with advice for long investment histories.
Usually you start by adding a lots tag to each investment commodity's declaration:
commodity AAPL ; lots:
With hledger 1 data, there are two more things to know.
Sales recorded at original cost
In hledger 1 a sale is often recorded at its original cost, using @:
2026-02-01 sell
assets:stocks -5 AAPL @ $50 ; bought at $50
assets:cash $350
revenues:gain $-100
In hledger 2 with lot tracking enabled, @ records the selling price instead, and the gain amount is not part of transaction balancing.
So the entry should be:
2026-02-01 sell
assets:stocks -5 AAPL @ $70 ; sold at $70
assets:cash $350
revenues:gain $-100
You can leave the gain posting, and hledger will check it; or remove it, and it will be inferred.
If you've been tracking lots by hand
If you have been tracking lots in hledger 1 with subaccounts, as Tracking lots with hledger 1 suggested
(eg assets:cc:ada:20200201), those entries keep working in hledger 2 unchanged.
If you want to convert them to hledger 2's lot tracking:
- Add the
lotstag to the commodity, as above. - Post purchases to the parent account (remove the
:20200201subaccount). - In sales, post to the parent account, select the lot by its date,
and write the selling price instead of the original cost, as above.
Eg
assets:cc:ada:20200201 -1000 ADA @ $0.03becomesassets:cc:ada -1000 ADA {2020-02-01} @ $0.04. - If you have balance assertions on lot subaccounts, move them to the parent account, or drop them.
Switching back, or using both
hledger 1 can read most journals written for hledger 2. So can add-ons built with hledger-lib 1.x, like hledger-interest, which read journals as hledger 1 does. To switch back, or to keep the same files working in both versions, watch out for these:
- Cost basis annotations. hledger 1 ignores them:
10 AAA {$50}means just10 AAA. So write the transacted cost too, with the same amount:10 AAA {$50} @ $50. - Gain postings. hledger 1 doesn't infer them, and balances a sale differently, as shown above. So leave out the gain posting, or its amount; hledger 2 will infer it, and hledger 1 will record no gain.
- New syntax. hledger 1 can't read dated annotations like
{2024-03-15, $50}, amount expressions like$50.20 + $16.54,type: Uaccount declarations, or new CSV rules likemerge. Avoid these. - print output. With lot tracking, hledger 2's
printshows lot postings with cost basis annotations, including ones it inferred, often dated ({2026-01-01, $50}), which hledger 1 can't read; and it shows inferred gain postings, which make sales unbalanced in hledger 1. To print entries that hledger 1 and its add-ons can read, usehledger print --ignore-lots -O ledger.--ignore-lotsleaves out the inferred annotations and gain postings, and theledgerformat writes any remaining annotations in Ledger's syntax, which hledger 1 reads (and ignores). - Lot subaccounts. hledger 2's
print --lotsoutput includes names likeassets:stocks:{2026-01-01, $50}, which hledger 1 reads as ordinary subaccounts. Generally, avoid account names ending in{...}. - The
lotstag is an ordinary tag in hledger 1, and has no effect.
After changes, check your files with both versions, eg hledger check and hledger1 check.