plain text accounting,
made easy
(Woah, too much to read ? Here's the quick start.)

hledger is cross-platform accounting software for both power users and folks new to accounting. It's good for tracking money, time, investments, cryptocurrencies, inventory and more. It is a reimplementation of Ledger CLI (I liked Ledger so much, I rebuilt it in Haskell) with a particular focus on ease of use and robustness.

Here are some things it provides out of the box:

hledger is a Plain Text Accounting system. Some strengths of the PTA approach:

  • Runs on your local computer, keeping your financial data private and under your control

  • Simple model of operation: put a log of transactions in, get reports out

  • Simple, expressive, human-readable, future-proof plain text format

  • Can be version controlled, eg with Git, to safeguard your data, track changes, or collaborate

  • Edit with your favourite text editor, or a data entry UI, or import from other formats

  • Easy to script, automate, and integrate into custom workflows

  • Lightweight, fast, non-distracting to use

  • Great for learning more of double-entry bookkeeping and accounting.

Here are some ways in which hledger strives to provide robustness:

  • Robust installation: multiple options are provided for binary and source installation. Building from source is reliable and consistent across platforms.

  • Robust execution: runtime crashes are minimised by Haskell's memory management and strong compile-time type checking. The software is also heavily tested by automated test suites and CI. Failures caused by user input are reported clearly and promptly.

  • Robust features: built-in commands and options combine well with one another, and are expected to do something sensible in all cases, with all kinds of input.

  • Robust calculation: results are expected to always perfectly match what you would calculate on paper, up to 255 decimal places.

  • Robust parsing: dated items, such as balance assertions and balance assignments, are processed in date order. Assertions/assignments with the same date are processed in parse order. Multiple assertions/assignments within a single transaction work as you would expect.

  • Robust reporting: reports are deterministic and not affected by the order of input files or data items except where that is part of their spec.

  • Robust documentation: all functionality is documented precisely, with a mnemonic permalink. User manuals for your hledger version are available online, and built in for offline viewing. General and command-specific command line help is provided. We favour documentation-driven development.

hledger is free software, with no purchase price or monthly fees. It is licensed under GNU GPLv3, providing the strongest guarantee that you will always have the right to run, inspect, modify, or share it. It is actively maintained, with regular releases, a large chat room and other support resources.

hledger is modelled after the pioneering Ledger CLI, and will read many Ledger files without change. Ledger users will find the data formats and commands familiar. Though not yet as fast as Ledger, it is quite fast, parsing and analysing (correctly) ~2000 txns/s on a 2013 macbook. Reports normally take a fraction of a second, and hledger-ui updates instantly as you edit.


You can start with hledger very simply, and get more sophisticated as you learn more about double-entry accounting. Here are some common ways of using it:

Web or terminal UI: Use hledger-web or use hledger-ui to enter transactions and see reports.

Command line: Use hledger add's interactive prompts to enter transactions, run hledger commands to see reports.

Text editor:

Record transactions in a plain text file, perhaps assisted by an editor mode:

; $HOME/.hledger.journal (or $LEDGER_FILE)

2020-01-01 opening balances
    assets:checking         $1234

2020-03-15 client payment
    assets:checking         $2000

2020-03-20 Sprouts
    expenses:food:groceries  $100
    assets:cash               $40

Run hledger commands to report balances, income and expenses, and more:

$ hledger bs
Balance Sheet 2020-03-20

             || 2020-03-20 
 Assets      ||            
 assets      ||      $3134 
   cash      ||        $40 
   checking  ||      $3094 
             ||      $3134 
 Liabilities ||            
 Net:        ||      $3134 

$ hledger is -M
Income Statement 2020-01-01-2020-03-20

                         || Jan  Feb    Mar 
 Revenues                ||                 
 income:consulting       ||   0    0  $2000 
                         ||   0    0  $2000 
 Expenses                ||                 
 expenses:food:groceries ||   0    0   $100 
                         ||   0    0   $100 
 Net:                    ||   0    0  $1900 

Automated import: Download CSV files from financial institutions, perhaps using an API like Plaid or Tiller, use hledger's import command to convert and import the new transactions, and use any UI to see reports.

More details

Next, you could:

hledger is brought to you by Simon Michael and 120+ contributors. I've been building and relying on it continuously since 2007; I hope you too will find it helpful in mastering your time and money! When your wealth allows, perhaps you'll feel inspired to become a sponsor and help us do more.

hledger is a rewrite/reboot of the pioneering Ledger. (Why?) Read more about the differences.

hledger strives to be usable, practical and to provide real-world value. Intuitive features, dependable bug-free operation and complete, accurate documentation are top goals.

hledger is first a command-line tool. Your data lives in a plain text journal file which you can edit any way you wish; hledger reads that file and produces reports of various kinds, without changing your data. (It can help you add new transactions, but does not change existing ones.)

hledger also provides a terminal interface that lets you review account balances and transactions quickly and without fuss. (screencast)

And, a zero-setup web app for a more point-and-click experience (demo). Run it on your local machine, or on a server, or set it up with a few clicks on Sandstorm.

hledger is written in Haskell, a modern, highly-regarded programming language which contributes to hledger's robustness, performance and long-term maintainability. Most functionality is exposed as Haskell libraries, making it easy to write your own hledger-compatible scripts, addons and applications.    hledger CI on hackage

What are some (current) limitations of PTA and hledger ?

  • The "GUIs" are minimalist; there is no rich GUI at the level of Quicken or GNUCash.

  • As a beginner you might feel there's too much freedom, too much to read, yet not enough clear guidance. Some common needs are not yet satisfactorily documented. (Tip: a request in chat often produces a quick result.)

  • hledger doesn't yet calculate capital gains automatically, as Ledger and Beancount can; you must do that semi-manually.

  • hledger is not yet as fast as Ledger.

What is planned for hledger ?

More support for investing, more support for correctness and accounting/business rules, more input/output formats, more speed, more GUI, charts, better getting started experience. See also ROADMAP.

Getting help

Chat: #hledger on Freenode IRC (or via Matrix: #freenode_#hledger:matrix.org)
To talk, register your nick:
on IRC, type /msg NickServ register newpassword youremailaddress
on Matrix, use @freenode_NickServ:matrix.org and @appservice-irc:matrix.org
Mail list: list.hledger.org, [email protected] ([email protected])
Twitter: #hledger, #plaintextaccounting
Reddit: /r/plaintextaccounting
Hacker News: stories, comments
Issues: bugs.hledger.org (bugs), issues.hledger.org (all), open issues overview, website issues
Other: [email protected]
See also: plaintextaccounting.org
#plaintextaccounting chat (or #freenode_#plaintextaccounting:matrix.org)
Ledger CLI docs
Beancount docs
@LedgerTips (2014-2018)


Building and supporting good software and documentation requires a lot of time and life energy. Maybe these thousands of person-hours have helped you or your organisation ?

Support us financially at any level, to be a part of this project and advance our core mission: helping more people achieve financial literacy and empowerment. Thank you!

  • Sponsor Simon (project leader / broom pusher):
    github liberapay paypal
  • Sponsor the hledger project with your organisation:

  • Sponsor the hledger project as an individual:
  • Sponsor specific tasks with bounties:
    all bounties bountysource bounties
    (For small amounts, feel free to just post a pledge on an issue. If you use Bountysource, contribute to a specific issue (not the hledger team), and then announce it on the issue page. Thanks!)


Welcome to the hledger FAQ!

This FAQ has not yet received a lot of maintenance and is a bit limited/verbose/disorganised. Sorry! If you'd like to help we'd sure appreciate it. Click the "Edit/PR this page" link at the bottom, or chat with us.

Why would I need to keep accounts ?

For clarity, control, planning, accountability, compliance, tax reporting, tax audits. It clarifies activity, priorities, obligations, opportunities.

What's double-entry accounting ?

Accounting means keeping track of the flow of valuable commodities, such as money or time. Double-entry bookkeeping is a method for keeping accounting records reliably. For every movement of value (a transaction), both the source and destination are recorded. Simple arithmetic invariants help prevent errors.

We already use Tally/other proprietary system

Every tool has strengths and weaknesses. hledger is lightweight, flexible and relatively easy to glue into other systems; it might be worth exploring as a complementary tool.

How do I convey the information in this to my accountant for tax work / to auditors for financial statements ?

Depending on their needs, you send them a few standard reports (balance sheet, income statement, itemized account registers or a full transaction journal)

  • as plain text (optionally spruced up with your own templates)
  • or as HTML
  • or as PDF
  • or as CSV they can import into Excel and elsewhere

I have to enter data in a text editor ??

No. A good text editor can be a very efficient way to work on your data, but there are other ways:

  • use a terminal-based data entry tool like hledger add or hledger-iadd
  • use a web-based data entry tool like hledger-web
  • use a phone-based data entry app like MoLe
  • import CSV data, avoiding manual data entry.

What account names do I use? Why aren't there any default list of commonly used accounts the way other software provide ?

Any standard set of account names you're familiar with. Feel free to copy list from any other software. A default list is a good idea, but right now we don't really provide one because

  • hledger aims to be useful for many needs and in many languages, so a single list won't do
  • we are not that large and organised yet
  • no-one has stepped up and worked on it.

What can hledger do for me ?

hledger is a suite of reporting tools which can provide clarity and insight into your personal or business finances, time logs, or other dated quantitative data, with relatively little effort on your part.

You need only provide a list of transactions, as a plain text file in a simple human-readable format. (Or a time log, or a CSV file with conversion rules.) From this hledger can generate a variety of useful reports and interactive views:

  • list your transactions, payees, currencies/commodities, accounts, statistics
  • show the hierarchy of accounts and subaccounts
  • show the transactions affecting any account, and calculate its running balance
  • make a balance sheet, showing your asset and liability account balances
  • make a cashflow report, showing changes in your cash assets
  • make an income statement, showing your revenues and expenses
  • show a bar chart of transaction activity by period
  • show purchase costs/selling prices
  • show market values in any currency at any valuation date
  • calculate the rate of return of a savings account or investment
  • make reports from timeclock or timedot time logs
  • make reports from any CSV file

It can slice, dice, and present your data in different ways:

  • filter out just the items or time period you're interested in
  • show multiple periods side by side
  • summarise accounts to give the big picture
  • rewrite or pivot account names to give different views
  • output reports as plain text, HTML, or CSV
  • run as a live-updating terminal UI, for fast interactive exploration
  • run as a web app, allowing remote/multi-user browsing and data entry
  • run as a JSON web API, for integrating with custom apps

If you add a few directives to the file, hledger can:

  • include multiple data sets
  • generate recurring transactions by rule
  • add extra postings (splits) to transactions by rule
  • show a forecast of future activity, eg to help with cashflow planning
  • make a budget report, showing your budget goals and status by account and period

Also, it can:

  • generate interest transactions by rule
  • help you enter new transactions with prompts or a terminal UI
  • help you convert and import new transactions from external sources, eg banks
  • be used as a library in a quick Haskell script or compiled program

How could that help me ?

  • More clarity, transparency and accountability, for yourself or others
  • Know what you owe, or who owes you
  • Know where the money went; steer your spending
  • Know how you spent your time; easy client invoicing
  • More foresight and ability to plan; avoid overdrafts, late fees, cashflow crunches
  • Know all the numbers you need for tax reporting; know how much to save for estimated taxes
  • Less stress, fear or overwhelm
  • More satisfaction, empowerment, and prosperity!

Isn't manual data entry a pain ?

  • Not if you spend a few minutes every day.
  • Not if the benefits are worth it to you.
  • Not if you use a comfortable editor and copy/paste a lot.
  • Not if you use tools to help (editor modes, hledger add, hledger-iadd, hledger-web..)
  • Not if you use rules to generate your recurring transactions.

Isn't importing from banks a pain ?

Not once you have set up a manual or automated routine for it. The possibilities vary by bank and country, but here are two simple workflows that are almost always possible:

Manual CSV import:

  1. Manually download CSV from your bank's website.
  2. hledger import BANK.csv
  3. Review/clean up the new journal entries.

Automated CSV import:

  1. Review/clean up the new journal entries. (CSV was downloaded and imported overnight by a cron job.)

Ask us for help setting this up. See also How could I import/migrate from....

Isn't plain text ugly and hard to use ?

No way, it's great, honest. We love it. You'll love it. It's fast. It's cheap. It's non-distracting. It keeps you focussed on the content. It's copy-pasteable. It's accessible to screen readers. It's resizable. You can pick the font and colours. You do not need "Plaintext Reader, Trial Version" to read it. you do not need "Plaintext Studio Pro" to write it. You can use your favorite editor and skills you already have. You can search in it! You can version control it. It works well over remote/slow connections. It's future-proof. It will be just as usable in 15 or 50 years. You can still read it even without the right software or (if you print it) a working computer.

Accounting data is valuable; we want to know that it will be accessible for ever - even without software. We want to know when it changes, and revision-control it. We want to search and manipulate it efficiently. So, we store it as human-readable plain text. --http://plaintextaccounting.org

Isn't this too weird for my family, business partners, tax accountant to use ?

Maybe. You can ask them to enter data via hledger-web, or import from their mobile expenses app or a shared spreadsheet. You can show them the hledger-web UI, or HTML reports, or give them CSV to open in a spreadsheet.

Why did you start hledger ? How does it relate to Ledger ?

I (Simon Michael) discovered John Wiegley's Ledger in 2006, and was very happy to find this efficient command-line reporting tool with a transparent data format.

Initially, I used it to generate time reports for my job. Before long I wanted that to work differently - splitting sessions at day boundaries, reporting in hours, etc. John had got busy elsewhere and the Ledger project now stalled, with unfixed bugs, wrong documentation and a confusing release situation persisting for a long time. I did what I could to help build momentum, reporting bugs, supporting newcomers, and contributing a new domain and website. But, I didn't want to spend time learning C++.

I was learning Haskell, which I did want to spend time in. I felt Ledger could be implemented well and, in the long run, more efficiently in that language, which has some compelling advantages such as lower maintenance costs. I urgently needed a reliable accounting tool that I enjoyed using. I also wanted to see what I could do to reduce roadbumps and confusion for newcomers.

I couldn't expect John to start over - at that time he was not the Haskell fan he is now! So in 2007 I began experimenting. I built a toy parser in a few different languages, and it was easiest in Haskell. I kept tinkering. Goals included:

  • to get better at Haskell by building something useful to me,
  • to learn how well Haskell could work for real-world applications,
  • and eventually: to provide a new implementation focussing more on ease of use, absence of user-visible bugs, and high-quality documentation and web presence. Also to experiment with new user interfaces, APIs, etc.

Before too long I had a tool that was useful to me. With Ledger still installed, and by maintaining high compatibility, I now had two tools with different strengths, each providing a comparison for the other in case of confusion or suspected bugs, which was itself quite valuable.

The Ledger project later revived and has attracted new active contributors. I have remained active in that community, sharing discoveries and design discussions, and we have seen many ideas travelling in both directions. hledger shared #ledger's IRC channel until 2014, when I added #hledger to allow us more space.

I think having independent but compatible implementations has been quite helpful for troubleshooting, exploring the design space, and growing the "Ledger-likes" community. My other projects in that direction include the ledger-cli.org site, LedgerTips, IRC support on #ledger, and now plaintextaccounting.org.

How is hledger different from Ledger ?

File format differences

hledger's journal file format is very similar to Ledger's. Some syntactic forms can be interpreted in slightly different ways, eg hledger comments vs Ledger comments, or balance assertions.

A small number of Ledger's syntactic forms are ignored ({ } prices) or rejected (value expressions). If you avoid these, it's quite easy to keep a journal file that works with both hledger and Ledger.

Or, you can keep the hledger- and Ledger-specific bits in separate files, both including a common file. Eg:

$ ls *.journal
common.journal   # included by hledger.journal and ledger.journal
$ hledger -f hledger.journal CMD
$ ledger -f ledger.journal CMD

hledger's timeclock format is also very similar to Ledger's. hledger also provides a new timedot format, allowing a different style of time logging.

Feature differences

Compared to Ledger, hledger builds quickly and has a complete and accurate manual, an easier report query syntax, multi-column balance reports, much better depth limiting, an interactive data entry assistant, and optional web and terminal interfaces. hledger provides a different system for converting CSV data, with rules files and new-transaction detection which simplify the task of importing new data from banks.

Compared to hledger, Ledger has some additional power-user features such as the built in value expressions language, and basic lots/capital gains reporting. Also, Ledger generates reports up to 10x faster, and using less memory, when files get large.

We currently support Ledger's main features:

  • Ledger's journal format, mostly
  • csv format
  • timeclock format
  • regular journal transactions
  • multiple commodities
  • fixed transaction prices
  • varying market prices
  • virtual postings
  • some basic output formatting
  • the print, register & balance commands
  • report filtering, using flags and query arguments
  • automated postings
  • periodic transactions
  • budget reports
  • -X/--exchange

We add some new commands, such as:

  • activity
  • add
  • balancesheet
  • cashflow
  • check-dates
  • check-dupes
  • close
  • descriptions
  • diff
  • files
  • import
  • incomestatement
  • irr
  • interest
  • notes
  • prices
  • rewrite
  • ui
  • web

We do not yet support:

  • revaluation transactions (--revalued)
  • reporting lots (--lots)
  • reporting capital gain/loss (--gain)
  • value expressions

Functional differences

Command line interface

  • hledger does not require a space between command-line flags and their values, eg -fFILE works as well as -f FILE

  • hledger's -b, -e, -D, -W, -M, -Q, -Y and -p options combine nicely. You can also specify start and/or end dates with a query argument, eg date:START- or date:START-END.

  • hledger's query language is a little less powerful than Ledger's, simpler, and easier to remember. It uses google-like prefixes, eg desc:, payee:, amt:, not:. Multiple patterns are combined using fixed AND/OR rules. We don't yet support full boolean expressions, so some more advanced queries require two invocations of hledger in a pipe, eg: hledger print QUERY1 | hledger -f- reg QUERY2

  • hledger uses --ignore-assertions/-I to disable balance assertions. Ledger uses --permissive for that, and uses -I as the short form of --prices.

  • hledger cleans up some semantic confusion with status matching (#564):

    • hledger uses -P as the short form of --pending. Ledger uses it for grouping by payee.
    • hledger renames Ledger's "uncleared" status (ie, when the status field is empty) to "unmarked", and the --uncleared/-U flag to --unmarked/-U
    • each of hledger's --unmarked/-U, --pending/-P, --cleared/-C flags match only that single status. To match more than one status, the flags can be combined. So the hledger equivalent of ledger print -U (ie: match all but cleared transactions) is hledger print -UP.
  • hledger print shows both the primary date and the secondary date if any, always. ledger print shows both by default, but with --aux-date it hides the primary date.

  • hledger's and Ledger's -H/--historical flags are unrelated:

    hledger's -H makes register and balance-like commands include balances from before the report start date, instead of starting at zero:

    hledger register --help:
    -H --historical           show historical running total/balance
                              (includes postings before report start date)
    hledger balance --help:
    -H --historical           show historical ending balance in each period
                              (includes postings before report start date)

    Ledger's -H changes the valuation date used by -V/-X:

    ledger --help:
    --historical (-H)
                              Value commodities at the time of their acquisition.
  • hledger's -x/--explicit flag (makes print show all amounts) and Ledger's --explicit flag (does something else) are unrelated.

  • hledger period expressions (up to 1.17) don't understand until, use to instead.

journal format

  • hledger supports international number formats, auto-detecting the decimal mark (comma or period), digit group mark (period, comma, or space) and digit group sizes to use for each commodity. Or, these can be declared explicitly with commodity directives.

  • hledger applies balance assignments and checks balance assertions in date order (and then by parse order, for postings on the same date). This ensures correct, deterministic behaviour, independent of the ordering of journal entries and files. Ledger checks assertions in the order they are parsed (ignoring dates), which is fragile.

    Also, hledger correctly handles multiple balance assignments/assertions in a single transaction.

  • hledger's default commodity directive (D) sets the commodity to be used for subsequent commodityless amounts, and also sets that commodity's display settings if such an amount is the first seen. Ledger uses D only for commodity display settings and for the entry command.

  • hledger up to 1.17.1 does not accept Ledger's virtual posting cost syntax ((@), (@@)). hledger 1.17.99+ accepts it, and ignores the parentheses.

  • hledger up to 1.17.1 does not accept Ledger's lot price or lot date syntax except in very limited circumstances ({= } at the end of the posting line). hledger 1.17.99+ accepts, but ignores, Ledger-style lot prices ({PRICE}, {{PRICE}}, {=PRICE}, {{=PRICE}}) and/or lot dates ([DATE]), after the posting amount and before the balance assertion if any. (#1084) Relatedly, hledger will not calculate capital gains when balancing a transaction selling a lot at a different price from its cost basis, as Ledger does. Eg:

    ; Ledger expects the 5 EUR capital gain income here because selling a 10 EUR lot at 15 EUR.
    ; hledger does not. Must leave that amount implicit to allow both to parse this.
    2019-03-01 Sell
      Assets:Shares           -1 ETF {10 EUR} @ 15 EUR
      Assets:Cash             15 EUR
      Income:Capital Gains   ;-5 EUR
  • hledger does not support Ledger's --lots or --gain reports.

  • hledger auto postings allow only minimal customisation of the amount (just multiplying the matched amount by a constant), not a full embedded expression language like Ledger.

timeclock & timedot formats

  • hledger's journal, timeclock and timedot formats are separate; you can't mix them all in one file as in Ledger. (Though you can specify all files on the command line, or have a parent journal file include them all.) This simplifies the implementation and helps ensure useful parse error messages.

  • hledger always shows time balances (from the timeclock/timedot formats) in hours

timeclock format

  • hledger always splits multi-day time sessions at midnight, showing accurate per-day amounts. Ledger does this only with the --day-break flag.

What is ledger4 ?

ledger4 was John's 2012 start at rewriting parts of Ledger 3, eg the parser, in Haskell. We included this in hledger for a while, hoping to attract contributions to improve this "bridge" between the projects, and improve our support for reading Ledger's files. After some time it was removed again.

How could I import/migrate from...

Some quick/rough migration recipes:

Mint.com ?

  1. download examples/csv/mint.csv.rules, and adjust the account1 & account2 rules
  2. touch ~/.hledger.journal
  3. log in to Mint, go to TRANSACTIONS, scroll to the bottom of the page, click on the "Export all N transactions" link, save it as mint.csv on your computer
  4. cd ~/Downloads (or wherever you saved it)
  5. hledger import mint.csv

Now hledger stats and hledger bal should show lots of data. That's your past data migrated.

Then, if you want to leave Mint, you'll need to replace their automatic import from banks with your own import process.

Or if you want to keep using Mint for that, because you like how they aggregate and clean the data: just periodically re-export from Mint, repeating steps 3-5 above.

Why does this entry give a "no amount" error even though I wrote an amount ?

  a 1

Because there's only a single space between a and 1, so this is parsed as an account named "a 1", with no amount. There must be at least two spaces between account name and amount.

Why do some directives not affect other files ? Why can't I put account aliases in an included file ?

This is documented at journal format: directives. (Also mentioned at hledger: Input files.) These docs could be improved.

Directives which affect parsing of data vary in their scope, ie the area of input data they affect. Eg, should they affect:

  • entries after the directive, in this file only ?
    • Eg: alias, apply account, comment, Y
  • entries before and after the directive, in this file only ?
  • entries and included files after the directive, until this file's end ?
  • all entries after the directive, in this and all included or subsequent files, including parent files ?
    • Eg: the number notation specified by D or commodity
  • all entries in all files ?
    • Eg: the default commodity specified by D, and account

The differences are partly due to historical accident, and partly by design. We would like to preserve these properties:

  • Reordering files does not change their meaning.
  • Adding a file does not change the meaning of the other files.

This is why some directives are designed to last only until the end of the current file. This can be annoying, but it seems worthwhile to ensure reports are robust, and not changed by simply moving include directives or -f options around.

For alias directives, when you have multiple files, the workaround is to put them inline in a top-level file, before including the other files that the aliases should affect. See #1007.

See also: #510, #217

Why am I seeing some amounts without an account name in reports ?

Some of hledger's older commands (balance, print, register) show a multi-commodity amount with each commodity on its own line, by default (like Ledger).

Here are some examples. In the following journal entry, the implicit balancing amount drawn from the b account will be a multicommodity amount (a euro and a dollar):

    a         EUR 1
    a         USD 1

the print command shows the b posting's amount on two lines, bottom-aligned:

$ hledger -f t.j print
    a         USD 1
    a         EUR 1
             EUR -1  ; <-
    b        USD -1  ; <- a euro and a dollar is drawn from b

the balance command shows that both a and b have a multi-commodity balance (again, bottom-aligned):

$ hledger -f t.j balance
               EUR 1     ; <-
               USD 1  a  ; <- a's balance is a euro and a dollar
              EUR -1     ; <-
              USD -1  b  ; <- b's balance is a negative euro and dollar

while the register command shows (top-aligned, this time!) a multi-commodity running total after the second posting, and a multi-commodity amount in the third posting:

$ hledger -f t.j register --width 50
2015/01/01       a             EUR 1         EUR 1
                 a             USD 1         EUR 1  ; <- the running total is now a euro and a dollar        
                                             USD 1  ;                                                        
                 b            EUR -1                ; <- the amount posted to b is a negative euro and dollar
                              USD -1             0  ;

Newer reports like multi-column balance reports show multi-commodity amounts on one line instead, comma-separated. Although wider, this seems clearer and we should probably use it more:

$ hledger -f t.j balance --yearly
Balance changes in 2015:

   ||           2015 
 a ||   EUR 1, USD 1 
 b || EUR -1, USD -1 
   ||              0 

You will also see amounts without a corresponding account name if you remove too many account name segments with --drop (a bug, which we'd like to see fixed):

$ hledger -f t.j balance --drop 1
               EUR 1  
               USD 1  
              EUR -1  
              USD -1  

With hledger-ui in iTerm2/3, why does Shift-Up/Shift-Down move the cursor instead of adjusting the period ?

One way to fix: in iTerm2 do Preferences -> Profiles -> your current profile -> Keys -> Load Preset -> xterm Defaults (not Terminal.app Compatibility). And perhaps open a new tab with this profile.

How do I display a decimal separator different from the one in the input file ?

It's not yet easy to do this with hledger:

There's just one special case where it works, by a quirk of the implementation: if in the journal you use space as thousands separator, comma as decimal separator, and no commodity directive, hledger will print numbers with period as decimal separator:

; journal
    (a)       $1 234,56
$ hledger print
    (a)       $1 234.56

Here's a more general workaround, post-processing the output with sed. Adjust if needed:

; journal
    (a)       $1.234,56
$ hledger print
    (a)       $1.234,56

$ hledger print | sed 's/\./~/g; s/,/./g; s/~/,/g'
    (a)       $1,234.56

How do I control the number of decimal places displayed ?

Use a commodity directive

to set the commodity's display style. Eg:

commodity $1000.00
commodity EUR 1.000,
commodity 1000.00000000 BTC

Why are revenues, liabilities, equity negative ?

It's characterisic of plain text accounting tools that balances of revenue, liability and equity accounts normally appear as negative numbers. (And if they have a contra-balance, as with a temporarily overpaid credit card, this would appear as a positive number.)

This is because we use negative and positive sign as an alternative to traditional Credit/Debit notation. (Negative amounts are credits, positives are debits.)

Think of each transaction as a movement of money from one place to another. The "from" amounts are negative (money removed from somewhere) and the "to" amounts are positive (money added to somewhere):

2021-01-01 receive salary
    revenues:salary    $-1000
    assets:checking     $1000

To ensure that no money is lost or created out of thin air, we simply require that a transaction's amounts add up to zero.

See also Ledger's discussion of this.

If you're new to plain text accounting, you'll get used to reading these negative numbers pretty quickly. But when you want to see revenues/liabilities/equity as positive numbers, you can use the higher level reports like balancesheet, cashflow and incomestatement. Or, use --invert to flip all signs.


hledger-related videos:

See also:


The current hledger release is 1.21 (release notes). Below are lots of ways to install:

Binary packages


Linux, Mac, Windows
docker pull dastapov/hledger
Linux, Mac, WSL
brew install hledger
Linux, Mac
nix-env -f https://github.com/NixOS/nixpkgs/archive/915ef210.tar.gz -iA hledger hledger-ui hledger-web
Binaries may not yet be fully cached for your platform, try with --dry-run to estimate how much building will be required. On Linux, note #1030, #1033.
Linux, Mac, *BSD, ...
Install Wine and use the Windows binary below


CI binaries


CI binaries


CI binaries
sudo layman -a haskell && sudo emerge hledger hledger-ui hledger-web
pacman -S hledger hledger-ui hledger-web
Void Linux x86_64
xbps-install -S hledger hledger-ui hledger-web
sudo apt install hledger hledger-ui hledger-web
sudo dnf install hledger
sudo apt install hledger hledger-ui hledger-web


freebsd ports
pkg install hs-hledger hs-hledger-ui hs-hledger-web


make -C /usr/ports/openbsd-wip/productivity/hledger install

Raspberry Pi

Contributed binaries
Contributed binaries



C libraries

On some platforms, certain C library packages must be installed, or you'll see an error (eg: "cannot find -ltinfo") when you try to run prebuilt hledger binaries or build hledger from source. So if you're on one of the following platforms, please run the command shown (and please send improvements for this list):

Debian, Ubuntu:
sudo apt install libtinfo-dev libtinfo5
Fedora, RHEL:
sudo dnf install gmp-devel ncurses-devel

UTF-8 locale

On unix systems, when building or running hledger (and GHC haskell programs in general), the LANG environment variable must be set to a UTF-8-aware locale, or you'll see errors (eg: "invalid byte sequence" or "mkTextEncoding: invalid argument") when processing non-ascii text. Check that LANG's value mentions UTF-8, and if not, change it:

$ echo $LANG
$ export LANG=C.UTF-8    # or en_US.UTF-8, fr_FR.utf8, etc.
$ echo $LANG

In some cases the locale may need to be installed with your system package manager first. See hledger: Troubleshooting for more help.

Building from source

Release source

The hledger-install script requires only bash and builds the current release of the hledger tools and some add-ons, in a reliable way:

curl -sO https://raw.githubusercontent.com/simonmichael/hledger/master/hledger-install/hledger-install.sh
less hledger-install.sh # <- good practice: inspect downloaded scripts before running
bash hledger-install.sh

This uses the stack or cabal build tools (installing stack in ~/.local/bin if needed), and installs the hledger tools in ~/.local/bin or ~/.cabal/bin.

Or, if you prefer to run stack yourself:

stack update
stack install --resolver=lts-17 hledger-lib-1.21 hledger-1.21 hledger-ui-1.21 hledger-web-1.21 --silent

This installs the main hledger tools in ~/.local/bin. Your stack --version should be not too ancient; use a recent release (2.5.1+) for best results. You can usually upgrade stack quickly with stack upgrade. Windows users: the 64-bit version of stack is preferable; and you should omit hledger-ui from this command, unless you are in WSL.

Or, if you prefer to run cabal yourself:

cabal update
cabal install alex happy
cabal install hledger-1.21 hledger-ui-1.21 hledger-web-1.21

This installs the main hledger tools in ~/.cabal/bin. Your cabal --version should be not too ancient; use a recent release (eg 3.2+) for best results. Windows users: omit hledger-ui from this command, unless you are in WSL.

Or, nix users can use nix-env to build hledger from source (but we try to provide a nix command that installs already-cached binaries.)

Build tips

Building the development version

Latest source

If you want the very latest improvements, our master branch on github is suitable for daily use. Get the source with git:

git clone https://github.com/simonmichael/hledger
cd hledger

and build and install executables to ~/.local/bin with stack:

stack update
stack install

or to ~/.cabal/bin with cabal:

cabal update
cabal install alex happy
cabal install all:exes

hledger development builds show a ".99" suffix in their --version output, so eg "1.17.99" means the in-development version of 1.18.

Building the development version with Docker

You can also build the development version in a Docker container which will take care of pulling all the necessary tools and dependencies:

git clone https://github.com/simonmichael/hledger
cd hledger/docker

This will build the image tagged hledger with just the latest binaries inside. If you want to keep all the build artifacts and use the resulting image for hledger development, run ./build-dev.sh instead.

Check your PATH

After building/installing, you may see a message about where the executables were installed. Eg:

  • with stack: $HOME/.local/bin (on Windows, %APPDATA%\local\bin)
  • with cabal: $HOME/.cabal/bin (on Windows, %APPDATA%\cabal\bin)
  • with nix: $HOME/.nix-profile/bin

Make sure that this install directory is included in your shell's $PATH (preferably near the start, to preempt any old hledger binaries you might have lying around), so that you can run the hledger tools easily. How to configure this depends on your platform and shell. If you are using bash, this will show it:

echo $PATH

and here's a way to add the stack and cabal install dirs permanently:

echo "export PATH=~/.local/bin:~/.cabal/bin:$PATH" >> ~/.bashrc
source ~/.bashrc

Here's how to set environment variables on Windows.

Test your installation

After a successful installation, you should be able to run the hledger tools and see the expected versions (the ones you just installed, and not any older versions that may exist somewhere else in your PATH). Eg:

$ hledger --version
hledger 1.21
$ hledger-ui --version
hledger-ui 1.21
$ hledger web --version
hledger-web 1.21

And you could see the unit tests pass (just for fun):

$ hledger test
All 215 tests passed (0.12s)

Or if you have checked out the hledger source, also the functional tests:

$ make functest
Excluding 2 test files

Test Cases Total
Passed 694 694
Failed 0 0
Total 694 694
functest PASSED

Nicely done! Now check the Quick Start for next steps, or come to the #hledger chat where we'll gladly share tips or receive your feedback.

Release notes

Major releases and user-visible changes, collected from the changelogs ( hledger-lib, hledger, hledger-ui, hledger-web ). Changes in hledger-install.sh are shown here.

2021-03-10 hledger-1.21

More speed; more cli-accessible docs; value change report; improvements to balance reports, valuation and more (announcement)

project-wide changes 1.21

  • roi has a new cookbook doc, and example files have been updated. (Dmitry Astapov)

  • Example CSV rules for the Daedalus wallet have been added.

  • The default stackage resolver/GHC version has been bumped to lts-17.4/ghc-8.10.4.

  • tools/generatejournal now includes more commodities and prices in generated journals. (Stephen Morgan)

  • Our functional tests now also run on BSD. (#1434, Felix Van der Jeugt)

  • Addon scripts in bin/ have been updated for latest hledger API (Stephen Morgan).

  • Addon scripts are now compiled as part of our CI tests, and always with the same version of hledger source they were shipped with. We now require script users to check out the hledger source tree and run the scripts (or, bin/compile.sh) from there. This keeps users and tests in sync, making things more reliable for everyone. (#1453)

  • Last but not least, hledger's bash completions (provided in ./shell-completions/) have been thoroughly updated (#1404, #1410, Vladimir Zhelezov).

hledger cli 1.21


  • hledger is now generally about 10% more memory- and time-efficient, and significantly more so in certain cases, eg journals with many total transaction prices. (Stephen Morgan)

  • The --help/-h and --version flags are no longer position-sensitive; if there is a command argument, they now always refer to the command (where applicable).

  • The new --info flag opens the hledger info manual, if "info" is in $PATH. hledger COMMAND --info will open COMMAND's info node.

  • The --man flag opens the hledger man page, if "man" is in $PATH. hledger COMMAND --man will scroll the page to CMD's section, if "less" is in $PATH. (We force the use of "less" in this case, overriding any $PAGER or $MAN_PAGER setting.)

  • Some command aliases, considered deprecated, have been removed: txns, equity, and the single-letter command aliases a, b, p, and r. This was discussed at https://github.com/simonmichael/hledger/pull/1423 and on the hledger mail list. It might annoy some folks; please read the issue and do follow up there if needed.

  • Notable documentation updates: the separate file format manuals have been merged into the hledger manual, the topic hierarchy has been simplified, the balance command docs and "commands" section have been rewritten.


  • Costing and valuation are now independent, and can be combined. --value=cost and --value=cost,COMM are still supported (equivalent to --cost and --cost --value=then,COMM respectively), but deprecated. (Stephen Morgan)

  • -V is now always equivalent to --value=end. (Stephen Morgan)

  • --value=end now includes market price directives as well as transactions when choosing a valuation date for single-period reports. (#1405, Stephen Morgan)

  • --value=end now picks a consistent valuation date for single- and and multi-period reports. (#1424, Stephen Morgan)

  • --value=then is now supported with all reports, not just register. (Stephen Morgan)

  • The too-vague --infer-value flag has been renamed to --infer-market-price. Tip: typing --infer-market or even --infer is sufficient. The old spelling still works, but is now deprecated.


  • add: Infix matches are now scored higher. If the search pattern occurs in full within the other description, that match gets a +0.5 score boost.

  • add: --debug now shows transaction matching results, useful when troubleshooting.

  • balance: To accomodate new report types, the --change|--cumulative|--historical|--budget flags have been split into two groups: report type (--sum|--budget|...) and accumulation type (--change|--cumulative|--historical). --sum and --change are the defaults, and your balance commands should still work as before. (Stephen Morgan et al, #1353)

  • balance: The --valuechange report type has been added, showing the changes in period-end values. (Stephen Morgan, #1353)

  • balance: With --budget, the first and last subperiods are enlarged to whole intervals for calculating the budget goals also. (Stephen Morgan)

  • balance: In multi-period balance reports, specifying a report period now also forces leading/trailing empty columns to be displayed, without having to add -E. This is consistent with balancesheet etc. (#1396, Stephen Morgan)

  • balancesheet, cashflow: declaring just a Cash account no longer hides other Asset accounts.

  • check: Various improvements:

    • check name arguments may be given as case-insensitive prefixes
    • accounts and commodities may also be specified as arguments
    • ordereddates now checks each file separately (#1493)
    • ordereddates no longer supports the --unique flag or query arguments
    • payees is a new check requiring payee declarations
    • uniqueleafnames now gives a fancy error message like the others
    • the old checkdates/checkdupes commands have been dropped
  • help: The help command now shows only the hledger (CLI) manual, its --info/--man/--pager flags have been renamed to -i/-m/-p, and --cat has been dropped.

  • help: With a TOPIC argument (any heading or heading prefix, case insensitive), it will open the manual positioned at this topic if possible. (Similar to the new --man and --info flags described above.)

  • payees: Add --used/--declared flags, like the accounts command.

  • print: Now always shows amounts with all decimal places, unconstrained by commodity display style. This ensures more parseable and sensible-looking output in more cases, and behaves more like Ledger's print. (There may be a cosmetic issue with trailing zeroes.) (#931, #1465)

  • print: With --match, infix matches are now scored higher, as with the add command.

  • print: --match now provides debug output useful for troubleshooting.

    If you forget to give --match an argument, it can confusingly consume a following flag. Eg if you write:

    hledger print --match -x somebank   # should be: hledger print --match=somebank -x

    it gets quietly parsed as:

    hledger print --match="-x"

    Now you can at least use --debug to figure it out:

    hledger print --match -x somebank --debug
    finding best match for description: "-x"
    similar transactions:
  • roi: Now supports the valuation options (#1417, #1483), and uses commodity display styles. Also the manual has been simplified, with some content moved to the Cookbook. (Dmitry Astapov):

journal format

  • The commodity directive now properly sets the display style of the no-symbol commodity. (#1461)

csv format

  • More kinds of malformed signed numbers are now ignored, in particular just a sign without a number, which simplifies sign flipping with amount-in/amount-out.

hledger-ui 1.21

  • Register screen: also show transactions below the depth limit, as in 1.19, keeping the register balance in agreement with the balance shown on the accounts screen. This regressed in 1.20. (#1468)

  • Transaction screen: all decimal places are now shown. On the accounts screen and register screen we round amounts according to commodity display styles, but when you drill down to a transaction you probably want to see the unrounded amounts. (Like print, #cf #931.)

  • New flags --man and --info open the man page or info manual. (See hledger)

hledger-web 1.21

  • Register: a date range can be selected by dragging over a region on the chart. (Arnout Engelen, #1471)

  • Add form: the description field's autocompletions now also offer declared and used payee names.

  • New flags --man and --info open the man page or info manual. (See hledger)

credits 1.21

This release was brought to you by Simon Michael, Vladimir Zhelezov, Stephen Morgan, Dmitry Astapov, Arnout Engelen, Damien Cassou, aragaer, Doug Goldstein, Caleb Maclennan, and Felix Van der Jeugt.

2021-01-29 hledger-1.20.4

  • aregister: ignore a depth limit, as in 1.19 (#1468). In 1.20-1.20.3, aregister had stopped showing transactions in subaccounts below a depth limit. Now it properly shows all subaccount transactions, ensuring that the register's final total matches a balance report with similar arguments.

2021-01-29 hledger-ui-1.20.4

  • ui: register: show all txns in/under an account at the depth limit (#1468). In 1.20-1.20.3, the register screen had stopped showing transactions in accounts below a depth limit. Now it properly shows all subaccount transactions, even when there is a depth limit, ensuring that the register's final total matches the balance shown on the account screen.

2021-01-29 hledger-web-1.20.4

  • Use hledger 1.20.4.

2021-01-14 hledger 1.20.3, hledger-ui 1.20.3, hledger-web 1.20.3

  • When searching for price chains during valuation/currency conversion:

    • It no longer hangs when there are price loops. (And in case of future bugs, it will give up rather than search forever.) (#1439)
    • It now really finds the shortest path. (#1443)
    • Useful progress info is displayed with --debug=1 or --debug=2.
  • balance, incomestatement: End-valued multi-period balance change reports (eg: bal -MV) have been reverted to show value-of-change, as in previous hledger versions, rather than change-of-value, for now. (#1353, #1428) (Stephen Morgan)

  • balance: End-valued balance change reports now choose the same final valuation date and show consistent results whether single-period or multi-period. (#1424) (Stephen Morgan)

  • balance: the --drop option now works with csv and html output. (#1456) (Ilya Konovalov)

  • check: the commodities check, and -s/--strict mode, now ignore the "AUTO" internal pseudo-commodity. (#1419) (Ilya Konovalov)

  • register: Then-valued multi-period register reports (eg: register -M --value=then) now calculate the correct values. (#1449) (Stephen Morgan)

  • roi: now shows a better error message when required prices are missing. (#1446) (Dmitry Astapov)

  • The no-symbol commodity's input number format can now be set by a commodity directive, like other commodities. (#1461)

2020-12-28 hledger 1.20.2

  • help: Fix loss of capitalisation in part of the hledger-ui manual.

  • help: Fix the node structure in info manuals.

  • Drop unused parsec dependency.

2020-12-28 hledger-ui 1.20.2

  • Fix loss of capitalisation in part of the manual.

  • Fix the info manual's node structure.

2020-12-28 hledger-web 1.20.2

  • Fix the info manual's node structure.

2020-12-15 hledger 1.20.1

  • bal, bs, cf, is: In amount-sorted balance reports, equal-balance accounts are now reliably sorted by name. (Simon Michael, Stephen Morgan)

  • help: Fix the topic hierarchy in Info manuals.

2020-12-15 hledger-ui 1.20.1

  • Fix the F key (toggle future/forecast transactions), which in 1.20 would only work twice. (#1411)

  • Fix loss of forecasted transactions when the journal was reloaded while they were hidden. (#1204)

2020-12-06 hledger-web-1.20.1

  • don't hang when reloading the journal, eg after adding a transaction or editing the file. (#1409)

2020-12-05 hledger-1.20

Strict mode; check command; rendering, speed, and valuation fixes

project-wide changes 1.20

  • examples: clean up & add more budgeting examples; stripe csv

  • a hie.yaml file has been added, so hledger source loads easily in IDEs supporting haskell-language-server

  • The functional tests in tests/ have been moved into the respective packages, eg hledger/test/ and hledger-ui/test/.

  • Shake cabalfiles: now gives an error when it fails

  • make bench: add some large tabular reports; run just the slowest commands by default; run after make (func)test

hledger cli 1.20


  • strict mode: with -s/--strict, hledger requires that all accounts and commodities are declared with directives.

  • Reverted a stripAnsi change in 1.19.1 that caused a 3x slowdown of amount rendering in terminal reports. (#1350)

  • Amount and table rendering has been improved, so that stripAnsi is no longer needed. This speeds up amount rendering in the terminal, speeding up some reports by 10% or more since 1.19. (Stephen Morgan)

  • Amount eliding no longer displays corrupted ANSI codes (#1352, Stephen Morgan)

  • Eliding of multicommodity amounts now makes better use of available space, avoiding unnecessary eliding (showing as many amounts as possible within 32 characters). (Stephen Morgan)

  • Command line help for --no-elide now mentions that it also disables eliding of multicommodity amounts.

  • Query terms containing quotes (eg to match account names containing quotes) now work properly. (#1368, Stephen Morgan)

  • cli, journal: Date range parsing is more robust, fixing failing/incorrect cases such as: (Stephen Morgan)

    • a hyphenated range with just years (2017-2018)
    • a hyphenated date with no day in a hyphenated range (2017-07-2018)
    • a dotted date with no day in a dotted range (2017.07..2018.02)
  • Debug output is prettier (eg, in colour), using pretty-simple instead of pretty-show.

  • csv, timedot, timeclock files now respect command line --alias options, like journal files. (#859)

  • Market price lookup for value reports is now more robust, fixing several bugs (and debug output is more informative). There has been a slight change in functionality: when chaining prices, we now prefer chains of all "forward" prices, even if longer, with chains involving reverse prices being the last resort. (#1402)


  • add: number style (eg thousands separators) no longer disturbs the value that is offered as default. (#1378)

  • bal: --invert now affects -S/--sort-amount, reversing the order. (#1283, #1379) (Stephen Morgan)

  • bal: --budget reports no longer insert an extra space inside the brackets. (Stephen Morgan)

  • bal: --budget reports now support CSV output (#1155)

  • bal, is, bs --change: Valued multiperiod balance change reports now show changes of value, rather than the value of changes. (#1353, Stephen Morgan)

  • bal: clearer debug output, following debug levels policy

  • check: A new command which consolidating the various check-* commands. It runs the default, strict, or specified checks and produces no output and a zero exit code if all is well.

  • check-dates: this command is deprecated and will be removed in next release; use "hledger check ordereddates" instead.

  • check-dupes: this command is deprecated and will be removed in next release; use "hledger check uniqueleafnames" instead.

  • import: The journal's commodity styles (declared or inferred) are now applied to imported amounts, overriding their original number format.

  • roi: TWR now handles same-day pnl changes and cashflows, calculation failure messages have been improved, and the documentation includes more detail and examples. (#1398) (Dmitry Astapov)

journal format

  • The journal's commodity styles are now applied to forecasted transactions. (#1371)

  • journal, csv: commodity style is now inferred from the first amount, as documented, not the last. This was "working wrongly" since hledger 1.12..

  • A zero market price no longer causes "Ratio has zero denominator" error in valued reports. (#1373)

csv format

  • The new decimal-mark rule allows reliable number parsing when CSV numbers contain digit group marks (eg thousands separators).

  • The CSV reader's verbose "assignment" debug output is now at level 9.

hledger-ui 1.20

  • When entering a query with /, malformed queries/regular expressions no longer cause the program to exit. (Stephen Morgan)

  • Eliding of multicommodity amounts now makes better use of available space. (Stephen Morgan)

  • E now parses the HLEDGER_UI_EDITOR or EDITOR environment variable correctly on Windows (ignoring the file extension), so if you have that set it should be better at opening your editor at the correct line.

  • E now supports positioning when HLEDGER_UI_EDITOR or EDITOR is VS Code ("code") (#1359)

  • hledger-ui now has a (human-powered) test suite.

hledger-web 1.20

  • hledger-web's test suite is re-enabled, now included in the main executable. hledger-web --test [-- HSPECARGS] runs it.

  • Fix --forecast, broken in hledger-web since 1.18 (#1390)

  • Fix unescaped slashes in hledger-web description on hackage (TANIGUCHI Kohei)

  • The hledger-web version string is now provided at /version, as JSON (#1152)

  • The session file (hledger-web_client_session_key.aes) is now written in $XDG_DATA_DIR rather than the current directory. Eg on non-Windows systems this is ~/.cache/ by default (cf https://hackage.haskell.org/package/directory/docs/System-Directory.html#t:XdgDirectory). (#1344) (Félix Sipma)

credits 1.20

This release was brought to you by Simon Michael, Stephen Morgan, Dmitry Astapov, TANIGUCHI Kohei, legrostdg.

2020/09/07 hledger 1.19.1

hledger cli 1.19.1

  • Fix alignment of coloured numbers (#1345, #1349, Stephen Morgan)

  • Fix a regression in account type autodetection for accounts with capitalised names. (#1341)

  • Allow megaparsec 9

hledger-ui 1.19.1

  • Allow megaparsec 9

hledger-web 1.19.1

  • Allow megaparsec 9

  • Drop redundant semigroups dependency (Felix Yan)

2020/09/01 hledger-1.19

New aregister and codes commands, more powerful CSV conditional rules, new sql output format, consistently default to flat mode, better colour control, cashflow report customisable like the others, more number/date/regexp parsing/validation, more speed.

hledger cli 1.19


  • aregister: a new command showing a transaction-oriented register for a single account. This is like hledger-ui, hledger-web, or your bank statement, and unlike the register command which shows individual postings possibly spanning multiple accounts. You might prefer aregister when reconciling real-world asset/liability accounts, and register when reviewing detailed revenues/expenses. (#1294)

  • codes: a new command for listing transaction codes

  • print: a new sql output format has been added (Dmitry Astapov)

  • A --color/--colour command line option, support for the NO_COLOR environment variable, and smarter autodetection of colour terminals have been added. (#1296)

  • In queries, you can now use q or Q to specify a year quarter, like 2020q1 or Q4. (#1247, Henning Thieleman, Stephen Morgan)

  • When specifying report intervals, you can use fortnightly as a synonym for biweekly. (Stephen Morgan)


  • Reports involving multiple commodities now show at most two commodities per amount by default, making multicolumn reports less wide and more readable. Use the --no-elide flag to prevent this.

  • Flat (AKA list) mode is now the consistent default used by all balance reports and other commands showing accounts. (Stephen Morgan)

  • All commands supporting tree/list mode now accept -t and -l as short forms of the --tree and --flat flags. (#1286)

  • account,bal,bs,cf,is: --drop now also works in tree mode (Stephen Morgan)

  • bal,bs,cf,is: tabular balance reports now elide (compress) boring parent accounts, like the non-tabular reports. (Stephen Morgan)

  • bal,bs,cf,is: monthly column headings are no longer be displayed as just the month abbreviations, if multiple years are being displayed.

  • bal --budget: with --cumulative or --historical, column headings now correctly show the period end dates rather than date spans.

  • bs,cf,is: --no-total now hides subtotals as well as the grand total (Stephen Morgan)

  • bs,cf,is: -%/--percent no longer implies --no-total. (Stephen Morgan)

  • roi: errors are now shown without a call stack

  • tags: the new --parsed flag causes all tags or values to be shown, including duplicates, in the order they were parsed. Blank/empty values are omitted by default and can be shown with -E/--empty.

  • Debug output is now organised better by debug level. The levels are:

    1. normal command output only (no warnings)
    2. useful warnings & most common troubleshooting info (valuation, eg)
    3. common troubleshooting info, more detail
    4. report options selection
    5. report generation
    6. report generation, more detail
    7. input file reading
    8. input file reading, more detail
    9. command line parsing
    10. any other rarely needed or more in-depth info


  • Added a missing lower bound for aeson, making cabal installs more reliable. (#1268)

  • When parsing dates, we now require the year to have at least four digits. So eg Feb 1 in the year 10 would need to be written 0010-02-01, not 10/02/01. would need to be written 0200/1/1. This change was made for consistency and to avoid ambiguities; let us know if it causes you trouble.

  • Command line options taking a numeric argument are now validated more carefully to avoid any issues with unexpected negatives or Int overflow. (Stephen Morgan)

  • Numbers with more than 255 decimal places, which we do not support, now give an error instead of silently misparsing. (#1326)

  • Digit groups in numbers are now limited to at most 255 digits each. (#1326)

  • Account aliases (on command line or in journal) containing a bad regular expression now give a more detailed error message.

  • In the argument of amt: queries, whitespace around the operator, sign, or number no longer causes a parse error. (#1312)

  • A tab character could get parsed as part of a commodity symbol, with confusing results. This no longer happens. (#1301, Dmitry Astapov)

  • add: fixed an error in the command line help (arguments are inputs, not a query)

journal format

  • account directives can specify a new account type, Cash, for accounts which should be displayed in the cashflow report. Cash accounts are also Asset accounts.

  • Documentation of account types has been improved.

csv format

  • Conditional rule patterns can now be grouped with the & (AND) operator, allowing more powerful matching. (Michael Sanders)

  • "If tables", a compact bulk format for conditional rules, have been added. (Dmitry Astapov)

  • csv conversion with a lot of conditional rules is now faster (Dmitry Astapov)

  • Invalid csv rules files now give clearer parse error messages. (Dmitry Astapov)

  • Inferring the appropriate default field separator based on file extension (, for .csv, ; for .ssv, \t for .tsv) now works as documented.

hledger-ui 1.19

  • A --color/--colour command line option, support for the NO_COLOR environment variable, and smarter autodetection of colour terminals have been added. (#1296)

  • -t and -l have been added as short forms of --tree and --flat command line flags.

  • Flat (AKA list) mode is now the default for the accounts screen.

  • t now toggles tree/list mode, while T sets the "today" period (#1286)

  • register screen: multicommodity amounts containing more than two commodities are now elided, unless the --no-elide flag is used.

  • register screen: a transaction dated outside the report period now is not shown even if it has postings dated inside the report period.

  • ESC now restores exactly the app's state at startup, which includes clearing any report period limit. (#1286)

  • DEL/BS no longer changes the tree/list mode.

  • q now exits the help dialog, if active; press q again to exit the app. (#1286)

  • The help dialog's layout is improved.

hledger-web 1.19

  • Added a missing lower bound for aeson, making cabal installs more reliable. (#1268)

  • Queries containing a malformed regular expression (eg the single character ?) now show a tidy error message instead "internal server error". (Stephen Morgan, Simon Michael) (#1245)

  • In account registers, a transaction dated outside the report period now is not shown even if it has postings dated inside the report period.

credits 1.19

This release was brought to you by Simon Michael, Stephen Morgan, Dmitry Astapov, Michael Sanders, Henning Thielemann, Martin Michlmayr, Colin Woodbury.

2020/06/21 hledger 1.18.1

hledger cli 1.18.1

  • value reports now work as in 1.17 again; inferring market prices from transactions is now an option, requiring the --infer-value flag. (#1239, #1253)

  • print: amounts in csv output now have commodity symbol, digit group separators and prices removed (Dmitry Astapov)

  • begin more systematic level usage in --debug output

  • journal: document recursive wildcards

hledger-ui 1.18.1

  • Fix F key having no effect (#1255) (Dmitry Astapov)

2020/06/07 hledger 1.18

Fixed JSON output; market prices inferred from transactions; more Ledger file compatibility; more flexible journal entries from CSV; misc. fixes and improvements.

project-wide changes 1.18

  • new example scripts:

    • hledger-combine-balances.hs, hledger-balance-as-budget.hs (Dmitry Astapov)
    • hledger-check-tag-files.hs, hledger-check-tag-files2.hs
  • more CSV rule examples: coinbase, waveapp

  • new CI (continuous integration) system using Github Actions. Thanks to Travis and Appveyor for their service to date. Improvements:

    • one CI service instead of several
    • more closely integrated with code repo
    • tests run on the three main platforms (linux, mac, windows)
    • harmless commits are ignored automatically ([ci skip] no longer needed for doc commits)
    • scheduled and on-demand testing (push to master, push to ci-* branches, pull request, weekly)
    • now tested: all GHC versions, doctests, haddock building
    • new shortcut url: https://ci.hledger.org

hledger cli 1.18

  • The --forecast flag now takes an optional argument (--forecast=PERIODICEXPR), allowing periodic transactions to start/end on any date and to overlap recorded transactions. (#835, #1236) (Dmitry Astapov)

  • An upper case file extension no longer confuses file format detection. (#1225)

  • In the commands list, redundant source scripts are now hidden properly when a corresponding .com/.exe file exists. (#1225)

  • We now show .. instead of - to indicate date ranges, eg in report titles, to stand out more from hyphenated dates. (Stephen Morgan)

  • Period expressions (eg in -p, date:, and periodic rules) now accept to, until, -, or .. as synonyms. (Stephen Morgan)

  • When parsing amounts, whitespace between sign and number is now allowed.

  • A clearer error message is shown on encountering a malformed regular expression.


  • commands allowing different output formats now list their supported formats accurately in --help (#689)

  • commands allowing JSON output now actually produce JSON (#689)

  • bal, bs: show .. (not ,,) in report titles, like other reports

journal format

  • We now also infer market prices from transactions, like Ledger. See https://hledger.org/hledger.html#market-prices (#1239).

    Upgrade note: this means value reports (-V, -X etc.) can give different output compared to hledger 1.17. If needed, you can prevent this by adding a P directive declaring the old price, on or after the date of the transaction causing the issue.

  • The include directive now accepts a file format prefix, like the -f/--file option. This works with glob patterns too, applying the prefix to each path. This can be useful when included files don't have the standard file extension, eg:

    include timedot:2020*.md
  • We now accept (and ignore) Ledger-style lot dates ([DATE]) and four lot price forms ({PRICE}, {{PRICE}}, {=PRICE}, {{=PRICE}}), anywhere after the posting amount but before any balance assertion.

  • We now accept Ledger-style parenthesised "virtual posting costs" ((@), (@@)). In hledger these are equivalent to the unparenthesised form.

  • The unbalanced transaction error message is clearer, especially when postings all have the same sign, and is split into multiple lines for readability.

csv format

  • You can now generate up to 99 postings in a transaction. (Vladimir Sorokin)

  • You can now generate postings with an explicit 0 amount. (#1112)

  • For each posting, when both numbered and unnumbered amount assignments are active (eg: both amount and amount1), we ignore the unnumbered ones. This makes it easier to override old amount rules.

  • Fix a 1.17.1 regression involving amount-in/amount-out. (#1226)

  • Assigning too many non-zero or zero values to a posting amount now gives a clearer error. (#1226)

hledger-ui 1.18

  • builds with hledger 1.18

hledger-web 1.18

  • The filter query is now preserved when clicking a different account in the sidebar. (Henning Thielemann)

  • Hyperlinks are now more robust when there are multiple journal files, eg links from register to journal now work properly. (#1041) (Henning Thielemann)

add form

  • Fixed a 2016 regression causing too many rows to be added by keypresses in the last amount field or CTRL-plus (#422, #1059).

  • Always start with four rows when opened.

  • Drop unneeded C-minus/C-plus keys & related help text.

credits 1.18

This release was brought to you by Simon Michael, Stephen Morgan, Dmitry Astapov, Henning Thielemann, Andriy Mykhaylyk, Pavan Rikhi, Vladimir Sorokin.

2020/03/01 hledger 1.17

CSV single-field matching; easier SSV/TSV conversion; fixed/enhanced close command; undo in add command; more JSON output; org headline support in timedot format; GHC 8.10 support.

project-wide changes 1.17

  • hledger-install tweaks

  • Simpler, clearer structure in the manuals and hledger.org sidebar.

  • A new Quick Start page

  • A new Common Tasks section in the hledger manual

  • A new Invoicing how-to

  • A basic example of rule parsing for the output of csb2format. (Evilham) csb2format deals with the CSB43/AEB43 format, which all banks operating in Spain must support.

hledger cli 1.17

  • hledger's default date format is now ISO-8601 (YYYY-MM-DD). (Brian Wignall, Jakob Schöttl, Simon Michael)

  • Drop the file format auto-detection feature.

    For a long time hledger has auto-detected the file format when it's not known, eg when reading from a file with unusual extension (like .dat or .txt), or from standard input (-f-), or when using the include directive (which currently ignores file extensions). This was done by trying all readers until one succeeded. Recent changes to timedot format have made this unreliable. So now, hledger will no longer guess; when there's no file extension or reader prefix available, it always assumes journal format. To specify one of the other formats, you must use its standard file extension (.timeclock, .timedot, .csv, .ssv, .tsv), or a reader prefix (-f csv:foo.txt, -f timedot:-). Experimental, feedback welcome.

  • More robust quoting of arguments for addons (#457). (Jacek Generowicz) Command lines like hledger ui 'amt:>200' failed, because the process of dispatching from hledger to hledger-ui lost the quotes around amt:>20 and the > character was interpreted as a shell redirection operator.

  • --output-format now rejects invalid formats

  • Numbers in JSON output now provide a floating point Number representation as well as our native Decimal object representation, since the latter can sometimes contain 255-digit integers. The floating point numbers can have up to 10 decimal digits (and an unbounded number of integer digits.) Experimental, suggestions needed. (#1195)

  • Fix finding latest date in queryEndDate Or queries and simplify date comparison code. (Stephen Morgan)

  • Fix extra $ symbol (Mateus Furquim)


  • add: you can use < to undo and redo previous inputs (Gaith Hallak)

  • bs, cf, is, bal, print, reg: support json output

  • bs, cf, is: fix excess subreport columns in csv output

  • bs, cf, is, bal: fix an issue with border intersections in --pretty-tables output. (Eric Mertens)

  • close: fix a rounding bug that could generate unbalanced transactions. (#1164)

  • close: hide cost prices by default, show them with --show-costs. close no longer preserves costs (transaction prices) unless you ask it to, since that can generate huge entries when there are many foreign currency/investment transactions. (#1165)

  • close: equity amounts are omitted by default, for simpler entries; -x/--explicit shows them (usually causing more postings). (#1165)

  • close: --interleaved generates equity postings alongside each closed account, making troubleshooting easier.

  • close: "equity:opening/closing balances" is now the default closing and opening account.

  • close: --close-desc/--open-desc customise the closing/opening transaction descriptions. (#1165)

  • close: some --open*/--close* flags have been simplified for memorability:

    --closing -> --close
    --opening -> --open
    --close-to -> --close-acct
    --open-from -> --open-acct

    The old flags are accepted as hidden aliases, and deprecated. (#1165)

  • print, register: a new valuation type, --value=then, shows the market value at each posting's date.

  • print: -V/-X/--value now imply -x/--explicit, as -B/--cost does. This avoids a bug where print -V of a transaction with an implicit commodity conversion would convert only some of its postings to value.

journal format

  • The include directive no longer tries all readers. It now picks just one, based on the included file's extension, defaulting to journal. (It doesn't yet handle a reader prefix.)

  • The default commodity (D) directive now limits display precision too, and is fully equivalent to commodity directives for setting a commodity's display style. (#1187)

csv format

  • Conditional blocks can now match single fields. \o/

  • The experimental --separator command line option has been dropped, replaced by a new separator directive in CSV rule files. (Aleksandar Dimitrov)

  • The .tsv and .ssv file extensions are now recognised, and will set the default separator to TAB and semicolon respectively. (#1179)

  • Manually assigning the "expenses:unknown" account name now works. (#1192)

  • CSV rule keywords are now case insensitive. (Aleksandar Dimitrov)

timeclock format

  • Misc. fixes making parsing more robust. (Jakob Schöttl)

timedot format

  • Org mode headlines (lines beginning with one or more * followed by a space) can be used as date lines or timelog items (the stars are ignored). Also all org headlines before the first date line are ignored. This means org users can manage their timelog as an org outline (eg using org-mode/orgstruct-mode in Emacs), for organisation, faster navigation, controlling visibility etc. Experimental.
  • You can now write a description after a date, which will be used in all of that day's transactions. Experimental.

hledger-ui 1.17

  • Don't enable --auto by default.

  • Don't enable --forecast by default; drop the --future flag. (#1193)

    Previously, periodic transactions occurring today were always shown, in both "present" and "future" modes. To fix this, generation of periodic transactions and display of future transactions (all kinds) have been combined as "forecast mode", which can be enabled with --forecast and/or toggled with the F key. The --future flag is now a hidden alias for --forecast, and deprecated.

hledger-web 1.17

  • Fonts have been improved on certain platforms. (David Zhang)

  • IPv6 is supported (Amarandus) (#1145)

  • The --host option can now take a local hostname (Amarandus) (#1145)

  • New --socket option to run hledger-web over an AF_UNIX socket file. (Carl Richard Theodor Schneider) This allows running multiple instances of hledger-web on the same system without having to manually choose a port for each instance, which is helpful for running individual instances for multiple users. In this scenario, the socket path is predictable, as it can be derived from the username.

  • The edit and upload forms now normalise line endings, avoiding parse errors (#1194). Summary of current behaviour:

    • hledger add and import commands will append with (at least some) unix line endings, possibly causing the file to have mixed line endings

    • hledger-web edit and upload forms will write the file with the current system's native line endings, ie changing all line endings if the file previously used foreign line endings.

  • Numbers in JSON output now provide a floating point Number representation as well as our native Decimal object representation, since the latter can sometimes contain 255-digit integers. The floating point numbers can have up to 10 decimal digits (and an unbounded number of integer digits.) Experimental, suggestions needed. (#1195)

credits 1.17

This release was brought to you by Simon Michael, Aleksandar Dimitrov, Brian Wignall, Stephen Morgan, Jacek Generowicz, Gaith Hallak, Eric Mertens, Jakob Schöttl, Carl Richard Theodor Schneider, David Zhang, Amarandus, Evilham, Mateus Furquim and Rui Chen.

2019/12/01 hledger 1.16

GHC 8.8 support, much more powerful CSV conversion rules, percentage balance reports, misc improvements. (mail)

project-wide changes 1.16

  • add support for GHC 8.8, base-compat 0.11 (#1090)

  • drop support for GHC 7.10

  • add descriptions to most issue tracker labels

  • matrix.hledger.org now redirects to a more readable/useful url

hledger cli 1.16

  • The --anon flag now also anonymises transaction codes and account names declared with account directives. (Mykola Orliuk) (#901)

  • The benchmark suite has been disabled.


  • balance/bs/cf/is: balance commands now support the -%/--percent flag to show amounts as percentages of the column's total. (Michael Kainer)

    If there are multiple commodities involved in a report hledger bails with an error message. This can be avoided by using -B/--cost. Also note that if one uses -% with the balance command the chances are high that all numbers are 0. This is due to the fact that by default balance sums up to zero. If one wants to use -% in a meaningful way with balance one has to add a query.

    In order to keep the implementation as simple as possible --tree has no influence over how the percentages are calculated, i.e., the percentages always represent the fraction of the columns total. If one wants to know the percentages relative to a parent account, one has to use a query to narrow down the accounts.

  • balance: --budget no longer errors when there is neither budget nor transactions in the report period (Dmitry Astapov)

  • balance: --budget has improved debug output (shows budget txns) (Dmitry Astapov)

  • check-dates: now sets the exit status code (Amitai Burstein)

  • close: no longer strips zeroes after the decimal mark, and preserves parseable output (#1137)

  • close: the --close-to, --open-from options allow closing/opening account names to be chosen

  • import: create the journal if missing, like the add command Streamlines import/migration instructions.

  • import: --catchup marks all transactions imported, without importing

  • import: more informative output: mention the input files, also show a message when nothing was imported

  • prices: show price amounts with proper display style; always show full precision

  • roi: don't give an error with empty input data (Dmitry Astapov)

  • tests: unit tests are now run by tasty, and show coloured output by default (#1090). Test running options have changed, see the command help. Some unit tests have been collapsed, so the reported test count has dropped a little.

journal format

  • Fixed: wrong dates generated by certain periodic transaction rules, eg "~ every 12 months from 2019/04". (Dmitry Astapov) (#1085)

csv format

CSV conversion is now more powerful (#1095, Dmitry Astapov, Simon Michael):

  • A variable number of postings can be generated, from zero to nine. (#627, #1095)

  • In conditional blocks, skip can be used to skip one or more records after a pattern match, or the new end rule can be used to skip all remaining records. (#1076)

  • The new balance-type CSV rule controls which kind of balance assertions are generated (=, ==, =, ==)

  • Postings with balance assignments can be generated. (#1000)

  • Both the amount-in/amount-out fields having a non-empty value is now accepted, as long as one of them is zero. (#570)

  • Line feeds/carriage returns in (quoted) CSV values are now converted to spaces during conversion. (#416, #841)

  • Field assignments can now unset a field (eg a posting can be suppressed by assigning no value to its account).

  • CSV records with varying lengths are now allowed; short records will be padded with empty fields as needed. This allows us to handle eg exported Google spreadsheets, where trailing empty fields are omitted.

  • Journals generated from CSV are now finalised and checked like ordinary journals (#1000). So invalid transactions generated from CSV will be rejected, amount styles will be standardised etc.

  • Fixed: we no longer add an extra (third) space between description and comment.

  • Fixed: whitespace on the line after an if block no longer causes misparsing. (#1120)

  • Fixed: an empty field assignment no longer consumes the next line. (#1001)

  • Fixed: interpolation of field names containing punctuation now works.

  • Docs have been rewritten and clarified.

Migration notes:

  • When printing from CSV, there is now one less space between transaction descriptions and comments, which may generate noisy diffs if you are comparing old and new reports. diff -w (--ignore-all-space) will filter these out.

  • CSV rules now give you more freedom to generate any journal entries you want, including malformed or unbalanced ones. The csv reader now checks the journal after conversion, so it will report any problems with the generated entries.

  • Balance assertions generated from CSV are not checked, currently. This is appropriate when you are downloading partial CSV data to be merged into your main journal. If you do need to check balance assertions right away, you can pipe through hledger again:

    $ hledger -f a.csv print | hledger -f- print

hledger-ui 1.16

  • the B and V keys toggle cost or value display (like the -B and -V command line flags)

  • uses hledger 1.16.1

hledger-web 1.16

  • The --cors option allows simple cross-origin requests to hledger-web (Alejandro García Montoro)

  • Weeks in the add form's date picker now start on Mondays (#1109) (Timofey Zakrevskiy)

  • No longer depends on json (#1190) or mtl-compat.

  • The test suite has been disabled for now.

credits 1.16

Release contributors: Simon Michael, Dmitry Astapov, Mykola Orliuk, Brian Wignall, Alejandro García Montoro, Timofey ZAKREVSKIY, Amitai Burstein, Michael Kainer.

2019/09/01 hledger 1.15

new website, faster and more flexible valuation, more accurate close command, tags --values, new descriptions/payees/notes/diff commands, misc. fixes. (mail)

project-wide changes 1.15

  • new unified website: hledger.org now has its own git repo, has absorbed the github wiki, and is generated with Sphinx.

  • hledger-api is now mothballed. Its functionality is included in hledger-web.

  • hledger-install.sh: bump to lts-14.4, hledger 1.15, drop hledger-api, now also works on FreeBSD 12.

  • Wine has been added to the list of install options.

  • Dmitry Astapov's hledger docker image is now based on the "haskell" image.

  • Andreas Pauley's hledger-makeitso has been renamed to hledger-flow.

  • bin/ addon scripts: hledger-swap-dates added; hledger-check, hledger-smooth updated. (#1072)

  • shell-completion scripts: updated

  • github: FUNDING.yml / sponsor button configured

  • tools: generatejournal updates: vary amount, make reports with fewer zeroes, start from a fixed year to keep tests stable, also generate P records. (#999)

  • tools: make, shake, CI: misc. updates

  • doc: add a README for the functional tests, linked from contrib guide

hledger cli 1.15

  • There is a new valuation option --value=TYPE[,COMM], with backwards-compatible -B/--cost, -V/--market, -X/--exchange=COMM variants. These provide control over valuation date (#329), and inference of indirect market prices (similar to Ledger's -X) (#131). Experimental.

  • Market valuation (-V/-X/--value) is now much faster (#999):

    |                                           || hledger-1.14 | hledger-1.15 |
    | -f examples/10000x1000x10.journal bal -Y  ||         2.43 |         2.44 |
    | -f examples/10000x1000x10.journal bal -YV ||        44.91 |         6.48 |
    | -f examples/10000x1000x10.journal reg -Y  ||         4.60 |         4.15 |
    | -f examples/10000x1000x10.journal reg -YV ||        61.09 |         7.21 |
  • How date options like -M and -p interact has been updated and clarified. (Jakob Schöttl) (#1008, #1009, #1011)

  • Restore --aux-date and --effective as --date2 aliases (#1034). These Ledger-ish spellings were dropped over the years, to improve --help's layout. Now we support them again, as semi-hidden flags (--help doesn't list them, but they are mentioned in --date2's help).


  • add, web: on Windows, trying to add transactions to a file path containing trailing periods (eg hledger add -f Documents.\.hledger.journal) now gives an error, since this could cause data loss otherwise (#1056). This affects the add command and hledger-web's add form.

  • bal: --budget: don't always convert to cost.

  • bal: --budget: don't show a percentage when budgeted and actual amounts are in different commodities.

  • bal/bs/bse: -H/--historical or --cumulative now disables -T/--row-total (#329). Multiperiod balance reports which show end balances (eg, bal -MH or bs -M) no longer show a Totals column, since summing end balances generally doesn't make sense.

  • bs: show end date(s) in title, not transactions date span (#1078) Compound balance reports showing ending balances (eg balancesheet), now show the ending date (single column) or range of ending dates (multi column) in their title. ,, (double comma) is used rather than - (hyphen) to suggest a sequence of discrete dates rather than a continuous span.

  • close: preserve transaction prices (costs) accurately (#1035). The generated closing/opening transactions were collapsing/misreporting the costs in balances involving multiple costs. Now, each separately-priced amount gets its own posting. (And only the last of these (for each commodity) gets a balance assertion.) Also the equity posting's amount is now always shown explicitly, which in multicommodity situations means that multiple equity postings are shown. The upshot is that a balance -B report will be unchanged after the closing & opening transactions generated by the close command.

  • descriptions, payees, notes commands added (Caleb Maclennan)

  • diff: Gabriel Ebner's hledger-diff is now a built in command, and https://github.com/gebner/hledger-diff is deprecated.

  • help: don't require a journal file

  • print: now also canonicalises the display style of balance assertion amounts (#1042)

  • reg: show negative amounts in red, like balance and Ledger

  • reg: fix --average, broken since 1.12 (#1003)

  • stats: show count of market prices (P directives), and the commodities covered

  • tags: add --values flag to list tag values.

  • tags: now runs much faster when there many tags

journal format

  • Transactions and postings generated/modified by periodic transaction rules and/or transaction modifier rules are now marked with generated-transaction, generated-posting, and modified tags, for easier troubleshooting and filtering.

csv format

  • When interpolating CSV values, outer whitespace is now stripped. This removes a potential snag in amount field assignments (#1051), and hopefully is harmless and acceptable otherwise.

  • We no longer add inter-field spaces in CSV error messages, which was misleading and not valid RFC-4180 CSV format.

  • CSV parse errors are human-readable again (broken since 1.11) (#1038)

  • CSV rules now allow the amount to be left unassigned if there is an assignment to "balance", which generates a balance assignment. (#1000)

hledger-ui 1.15

  • uses hledger 1.15

hledger-web 1.15

  • --serve-api disables the usual server-side web UI (leaving only the API routes)

  • register page: account names are hyperlinked

  • ?sidebar= now hides the sidebar, same as ?sidebar=0

  • fix "_create_locale could not be located" error on windows 7 (#1039)

  • uses hledger 1.15

credits 1.15

Release contributors: Simon Michael, Caleb Maclennan, Jakob Schöttl, Henning Thielemann, Dmitry Astapov, Ben Creasy, zieone, Boyd Kelly, Gabriel Ebner, Hans-Peter Deifel, Andreas Pauley.

2019/03/01 hledger 1.14

inclusive balance assertions, commodities command, --invert option, JSON get/add support in hledger-web (mail)

project-wide changes 1.14

  • hledger.org website: now uses https, home page updates, download page improved package list with status badges. Also the github wiki pages are now rendered as part of hledger.org, like the main site pages (with pandoc markdown and tables of contents). Building the site now requires that a copy of the wiki is checked out under wiki/.

  • bash completion support: removed duplicate options, added new options, stopped listing -h as a command, added some completion for external addon commands.

  • release automation improvements

  • makefile cleanups; make site-liverender helps with local site preview

hledger cli 1.14

  • journal: subaccount-including balance assertions have been added, with syntax =* and ==* (experimental) (#290)

  • new commodities command lists commodity symbols

  • new --invert option flips sign of amounts in reports

hledger-ui 1.14

  • use hledger 1.14

hledger-web 1.14

  • serve the same JSON-providing routes as in hledger-api:


    And allow adding a new transaction by PUT'ing JSON (similar to the output of /transactions) to /add. This requires the add capability (which is enabled by default). Here's how to test with curl:

    $ curl -s -X PUT -H 'Content-Type: application/json' --data-binary @in.json; echo


  • fix unbalanced transaction prevention in the add form

  • fix transaction-showing tooltips (#927)

  • manual updates: document --capabilities/--capabilities-header and editing/uploading/downloading.

  • use hledger 1.14

hledger-api 1.14

  • use hledger 1.14

hledger-lib 1.14

  • added:
    transaction, [v]post*, balassert* constructors, for tests etc.

  • renamed:
    porigin -> poriginal

  • refactored:
    transaction balancing & balance assertion checking (#438)

credits 1.14

Release contributors: Simon Michael, Jakob Schöttl, Jakub Zárybnický.

2019/02/01 hledger 1.13

Unified command CLI help/manuals, bash completions, docker support, improved budget report, --transpose, new account types syntax, usability & bug fixes. (mail)

project-wide changes 1.13

  • packaging: A docker image providing the main hledger tools is now linked on the download page. This is another way to get up-to-date hledger tools without building them yourself (and, a way to run hledger-ui on windows ?) (Dmitry Astapov, Simon Michael)

  • doc: fixed pandoc typography conversion in web manuals. Eg -- was being rendered as en-dash. (#954).


  • developer docs have moved from the wiki into CONTRIBUTING.md (#920)

  • new streamlined changelog update process. Shake targets:

    ./Shake changelogs
    ./Shake CHANGES.md
    ./Shake CHANGES.md-dry
    ./Shake PKG/CHANGES.md
    ./Shake PKG/CHANGES.md-dry

    update the project-wide and/or package changelogs, inserting new commits (touching the respective directory, since the tag version or commit hash which is the first word in the changelog's previous top heading) at the top, formatted as changelog entries.

  • ./Shake PKG - builds a package plus its embedded docs. ./Shake build - builds all the packages and their embedded docs. ("stack build PKG" does not notice changes in embedded doc files.)

  • make ghci-shake - loads Shake.hs in ghci

  • make tags - includes doc source files, hpack/cabal files, Shake.hs

  • make site-livereload - opens a reloading browser view on the website html (requires livereloadx)

  • added a Dockerfile and helper scripts (Dmitry Astapov)

  • doc files and hpack/cabal files are included in TAGS again

hledger cli 1.13

  • cli: reorganised commands list. Addons now have a + prefix.

  • cli: the command line help and manual section for all hledger's commands are now consistent, and generated from the same source.

  • cli: comprehensive bash completion support is now provided (in shell-completion/). See how-to in the Cookbook. (Jakob Schöttl)

  • balance --budget: budget amounts now aggregate hierarchically, like account balances. Unbudgeted accounts can be shown with -E/--empty (along with zero-balance accounts), and the --show-budgeted flag has been dropped. (Dmitry Astapov)

  • balance: new --transpose flag switches the rows and columns of tabular balance reports (in txt and csv output formats). (Dmitry Astapov)

  • close: generated balance assertions now have exact amounts with all decimal digits, ignoring display precision. Also, balance assertion amounts will no longer contain prices. (#941, #824, #958)

  • files: now shows up in the commands list

  • import: be silent when there's nothing to import

  • roi: percentages smaller than 0.01% are displayed as zero (Dmitry Astapov)

  • stats, ui: correct file order is preserved when using --auto (#949)

  • journal: account directive: the account name can now be followed by a comment on the same line

  • journal: account directive: account types for the bs/bse/cf/is commands can now be set with a type: tag, whose value is Asset, Liability, Equity, Revenue, Expense, A, L, E, R or X (case-insensitive). The previous syntax (account assets A) is now deprecated.

  • journal: account directive: account sort codes like account 1000 (introduced in 1.9, deprecated in 1.11) are no longer supported.

  • journal: transaction modifiers (auto postings) can affect periodic transactions (--auto can add postings to transactions generated with --forecast). (Dmitry Astapov)

  • journal: balance assertion errors now show exact amounts with all decimal digits. Previously it was possible, in case of a commodity directive limiting the display precision, to have a balance assertion error with asserted and actual amounts looking the same. (#941)

  • journal: fixed a periodic transaction parsing failure (#942) (Dmitry Astapov)

hledger-ui 1.13

  • on posix systems, control-z suspends the program

  • control-l now works everywhere and redraws more reliably

  • the top status info is clearer

  • use hledger 1.13

hledger-web 1.13

  • use hledger 1.13

hledger-api 1.13

  • use hledger 1.13

hledger-lib 1.13

  • in Journal's jtxns field, forecasted txns are appended rather than prepended

  • API changes:

    added: +setFullPrecision +setMinimalPrecision +expectParseStateOn +embedFileRelative +hereFileRelative


    • amultiplier -> aismultiplier

    • Amount fields reordered for clearer debug output

    • tpreceding_comment_lines -> tprecedingcomment, reordered

    • Hledger.Data.TransactionModifier.transactionModifierToFunction -> modifyTransactions

    • Hledger.Read.Common.applyTransactionModifiers -> Hledger.Data.Journal.journalModifyTransactions

    • HelpTemplate -> CommandDoc

credits 1.13

Release contributors: Simon Michael, Jakob Schöttl, Dmitry Astapov.

2018/12/02 hledger 1.12

Account type declarations, complete balance assertions, GHC 8.6 support, hledger-ui usability updates, misc fixes (mail)

hledger cli 1.12

  • install script: ensure a new-enough version of stack; more informative output

  • build with GHC 8.6/base-4.12 (Peter Simons)

  • add required upper bound for statistics (Samuel May)

  • --anon anonymises more thoroughly (including linked original postings) (Moritz Kiefer)

  • unbalanced transaction errors now include location info (Mykola Orliuk)

  • accounts command: --drop also affects the default flat output, without needing an explicit --flat flag

  • accounts command: the --codes flag has been dropped

  • accounts command: filtering by non-account-name queries now works

  • add command: fix transaction rendering regression during data entry and in journal file

  • balance command: fix wrongful eliding of zero-balance parent accounts in tree mode (Dmitry Astapov)

  • journal format, bs/bse/cf/is commands: account directives can declare account types (#877)
    Previously you had to use one of the standard english account names (assets, liabilities..) for top-level accounts, if you wanted them to appear in the right place in the balancesheet, balancesheetequity, cashflow or incomestatement reports.

    Now you can use your preferred account names, and use account directives to declare which accounting class (Asset, Liability, Equity, Revenue or eXpense) an account (and its subaccounts) belongs to, by writing one of the letters A, L, E, R, X after the account name, after two or more spaces. This syntax may change (see issue). Experimental.

    Currently we allow unlimited account type declarations anywhere in the account tree. So you could declare a liability account somewhere under assets, and maybe a revenue account under that, and another asset account even further down. In such cases you start to see oddities like accounts appearing in multiple places in a tree-mode report. I have left it this way for now in case it helps with, eg, modelling contra accounts, or combining multiple files each with their own account type declarations. (In that scenario, if we only allowed type declarations on top-level accounts, or only allowed a single account of each type, complications seem likely.)

  • journal format: periodic transaction rules now require a double space separator.
    In periodic transaction rules which specify a transaction description or same-line transaction comment, this must be separated from the period expression by two or more spaces, to prevent ambiguous parsing. Eg this will parse correctly as "monthly" thanks to the double space:

    ~ monthly  In 2020 we'll end this monthly transaction.
  • journal format: exact/complete balance assertions (Samuel May).
    A stronger kind of balance assertion, written with a double equals sign, asserts an account's complete account balance, not just the balance in one commodity. (But only if it is a single-commodity balance, for now.) Eg:

      (a)  A 1
      (a)  B 1
      (a)  0   =  A 1   ; commodity A balance assertion, succeeds
      (a)  0   == A 1   ; complete balance assertion, fails
  • journal format: account directives now allow whitespace or a comment after the account name

  • journal format: using ~ for home directory in include directives now works (#896) (Mykola Orliuk)

  • journal format: prevent misleading parse error messages with cyclic include directives (#853) (Alex Chen)

  • journal format: transaction modifier multipliers handle total-priced amounts correctly (#928).
    Multipliers (*N) in transaction modifier rules did not multiply total-priced amounts properly. Now the total prices are also multiplied, keeping the transaction balanced.

  • journal format: do amount inference/balance assignments/assertions before transaction modifiers (#893, #908) (Jesse Rosenthal)
    Previously, transaction modifier (auto postings) rules were applied before missing amounts were inferred. This meant amount multipliers could generate too many missing-amount postings, making the transaction unbalanceable (#893).

    Now, missing amount inference (and balance assignments, and balance assertions, which are interdependent) are done earlier, before transaction modifier rules are applied (#900, #903).

    Also, we now disallow the combination of balance assignments and transaction modifier rules which both affect the same account, which could otherwise cause confusing balance assertion failures (#912). (Because assignments now generate amounts to satisfy balance assertions before transaction modifier rules are applied (#908).)

  • journal format: periodic transaction rules are now aware of Y default year directives. (#892)
    Ie when a default year Y is in effect, they resolve partial or relative dates using Y/1/1 as the reference date, rather than today's date.

hledger-ui 1.12

  • fix "Any" build error with GHC < 8.4

  • error screen: always show error position properly (#904) (Mykola Orliuk)

  • accounts screen: show correct balances when there's only periodic transactions

  • drop the --status-toggles flag

  • periodic transactions and transaction modifiers are always enabled.
    Rule-based transactions and postings are always generated (--forecast and --auto are always on). Experimental.

  • escape key resets to flat mode.
    Flat mode is the default at startup. Probably it should reset to tree mode if --tree was used at startup.

  • tree mode tweaks: add --tree/-T/-F flags, make flat mode the default,
    toggle tree mode with T, ensure a visible effect on register screen

  • hide future txns by default, add --future flag, toggle with F.
    You may have transactions dated later than today, perhaps piped from print --forecast or recorded in the journal, which you don't want to see except when forecasting.

    By default, we now hide future transactions, showing "today's balance". This can be toggled with the F key, which is easier than setting a date query. --present and --future flags have been added to set the initial mode.

    (Experimental. Interactions with date queries have not been explored.)

  • quick help tweaks; try to show most useful info first

  • reorganise help dialog, fit content into 80x25 again

  • styling tweaks; cyan/blue -> white/yellow

  • less noisy styling in horizontal borders (#838)

  • register screen: positive amounts: green -> black
    The green/red scheme helped distinguish the changes column from the black/red balance column, but the default green is hard to read on the pale background in some terminals. Also the changes column is non-bold now.

  • use hledger 1.12

hledger-web 1.12

  • fix duplicate package.yaml keys warned about by hpack

  • use hledger 1.12

hledger-api 1.12

  • use hledger 1.12

hledger-lib 1.12

  • switch to megaparsec 7 (Alex Chen)
    We now track the stack of include files in Journal ourselves, since megaparsec dropped this feature.

  • add 'ExceptT' layer to our parser monad again (Alex Chen)
    This was removed under the assumption that it would be possible to write our parser without this capability. However, after a hairy backtracking bug, we would now prefer to have the option to prevent backtracking.

  • more support for location-aware parse errors when re-parsing (Alex Chen)

  • make 'includedirectivep' an 'ErroringJournalParser' (Alex Chen)

  • drop Ord instance breaking GHC 8.6 build (Peter Simons)

  • flip the arguments of (divide|multiply)[Mixed]Amount

  • showTransaction: fix a case showing multiple missing amounts
    showTransaction could sometimes hide the last posting's amount even if one of the other posting amounts was already implcit, producing invalid transaction output.

  • plog, plogAt: add missing newline

  • split up journalFinalise, reorder journal finalisation steps (#893) (Jesse Rosenthal)
    The journalFinalise function has been split up, allowing more granular control.

  • journalSetTime --> journalSetLastReadTime

  • journalSetFilePath has been removed, use journalAddFile instead

credits 1.12

Release contributors: Simon Michael, Alex Chen, Jesse Rosenthal, Samuel May, Mykola Orliuk, Peter Simons, Moritz Kiefer, Dmitry Astapov, Felix Yan, Aiken Cairncross, Nikhil Jha.

2018/9/30 hledger 1.11

Customisable account display order, support for other delimiter-separated formats (eg semicolon-separated), new files and roi commands, fixes (mail)

hledger cli 1.11

  • The default display order of accounts is now influenced by the order of account directives. Accounts declared by account directives are displayed first (top-most), in declaration order, followed by undeclared accounts in alphabetical order. Numeric account codes are no longer used, and are ignored and considered deprecated.

    So if your accounts are displaying in a weird order after upgrading, and you want them alphabetical like before, just sort your account directives alphabetically.

  • Account sorting (by name, by declaration, by amount) is now more robust and supported consistently by all commands (accounts, balance, bs..) in all modes (tree & flat, tabular & non-tabular).

  • close: new --opening/--closing flags to print only the opening or closing transaction

  • files: a new command to list included files

  • prices: query arguments are now supported. Prices can be filtered by date, and postings providing transaction prices can also be filtered.

  • rewrite: help clarifies relation to print --auto (#745)

  • roi: a new command to compute return on investment, based on hledger-irr

  • test: has more verbose output, more informative failure messages, and no longer tries to read the journal

  • csv: We use a more robust CSV lib (cassava) and now support non-comma separators, eg --separator ';' (experimental, this flag will probably become a CSV rule) (#829)

  • csv: interpolated field names in values are now properly case insensitive, so this works:

    fields ...,Transaction_Date,... date %Transaction_Date

  • journal: D (default commodity) directives no longer break multiplier amounts in transaction modifiers (AKA automated postings) (#860)

  • journal: "Automated Postings" have been renamed to "Transaction Modifiers".

  • journal: transaction comments in transaction modifier rules are now parsed correctly. (#745)

  • journal: when include files form a cycle, we give an error instead of hanging.

  • upper-case day/month names in period expressions no longer give an error (#847, #852)

hledger-ui 1.11

  • uses hledger-lib 1.11

hledger-web 1.11

  • uses hledger-lib 1.11

hledger-api 1.11

  • uses hledger-lib 1.11

hledger-lib 1.11

  • compilation now works when locale is unset (#849)

  • all unit tests have been converted from HUnit+test-framework to easytest

  • doctests now run quicker by default, by skipping reloading between tests. This can be disabled by passing --slow to the doctests test suite executable.

  • doctests test suite executable now supports --verbose, which shows progress output as tests are run if doctest 0.16.0+ is installed (and hopefully is harmless otherwise).

  • doctests now support file pattern arguments, provide more informative output. Limiting to just the file(s) you're interested can make doctest start much quicker. With one big caveat: you can limit the starting files, but it always imports and tests all other local files those import.

  • a bunch of custom Show instances have been replaced with defaults, for easier troubleshooting. These were sometimes obscuring important details, eg in test failure output. Our new policy is: stick with default derived Show instances as far as possible, but when necessary adjust them to valid haskell syntax so pretty-show can pretty-print them (eg when they contain Day values, cf https://github.com/haskell/time/issues/101). By convention, when fields are shown in less than full detail, and/or in double-quoted pseudo syntax, we show a double period (..) in the output.

  • Amount has a new Show instance. Amount's show instance hid important details by default, and showing more details required increasing the debug level, which was inconvenient. Now it has a single show instance which shows more information, is fairly compact, and is pretty-printable.

    ghci> usd 1 OLD: Amount {acommodity="$", aquantity=1.00, ..} NEW: Amount {acommodity = "$", aquantity = 1.00, aprice = NoPrice, astyle = AmountStyle "L False 2 Just '.' Nothing..", amultiplier = False}

    MixedAmount's show instance is unchanged, but showMixedAmountDebug is affected by this change:

    ghci> putStrLn $ showMixedAmountDebug $ Mixed [usd 1] OLD: Mixed [Amount {acommodity="$", aquantity=1.00, aprice=, astyle=AmountStyle {ascommodityside = L, ascommodityspaced = False, asprecision = 2, asdecimalpoint = Just '.', asdigitgroups = Nothing}}] NEW: Mixed [Amount {acommodity="$", aquantity=1.00, aprice=, astyle=AmountStyle "L False 2 Just '.' Nothing.."}]

  • Same-line & next-line comments of transactions, postings, etc. are now parsed a bit more precisely (followingcommentp). Previously, parsing no comment gave the same result as an empty comment (a single newline); now it gives an empty string.
    Also, and perhaps as a consequence of the above, when there's no same-line comment but there is a next-line comment, we'll insert an empty first line, since otherwise next-line comments would get moved up to the same line when rendered.

  • Hledger.Utils.Test exports HasCallStack

  • queryDateSpan, queryDateSpan' now intersect date AND'ed date spans instead of unioning them, and docs are clearer.

  • pushAccount -> pushDeclaredAccount

  • jaccounts -> jdeclaredaccounts

  • AutoTransaction.hs -> PeriodicTransaction.hs & TransactionModifier.hs

  • Hledger.Utils.Debug helpers have been renamed/cleaned up

credits 1.11

Release contributors: Simon Michael, Joseph Weston, Dmitry Astapov, Gaith Hallak, Jakub Zárybnický, Luca Molteni, SpicyCat.

2018/6/30 hledger 1.10

hledger-web edit/upload/download and permissions, more expressive periodic transactions, more informative parse errors, misc fixes (mail)

project-wide changes 1.10

  • build cleanly with all supported GHC versions again (7.10 to 8.4)

  • support latest deps

  • back in Stackage LTS (12.0)

hledger-lib 1.10

  • extensive refactoring and cleanup of parsers and related types and utilities

  • readJournalFile(s) cleanup, these now use InputOpts

  • doctests now run a bit faster (#802)

hledger cli 1.10

  • journal: many parse error messages have become more informative, and some now show the source line and error location.

  • journal: ;tag: is no longer parsed as a tag named ";tag" (#655)

  • journal: transaction price amounts having their own price amounts is now a parse error

  • journal: amounts with space as digit group separator and trailing whitespace now parse correctly (#780)

  • journal: in amounts containing digits and a single space, the space is now interpreted as a digit group separator, not a decimal separator (#749)

  • journal: in commodity/format/D directives, the amount must now include a decimal separator.

    When more precise control is needed over number parsing, our recommended solution is commodity directives. Commodity directives that don't specify the decimal separator leave things ambiguous, increasing the chance of misparsing numbers. In some cases it could cause amounts with a decimal point to be parsed as if with a digit group separator, so 1.234 became 1234.

    It seems the simple and really only way to do this reliably is to require an explicit decimal point character. Most folks probably do this already. Unfortunately, it makes another potential incompatiblity with ledger and beancount journals. But the error message will be clear and easy to work around.

  • journal: directives currently have diverse and somewhat tricky semantics, especially with multiple files. The manual now describes their behaviour precisely.

  • journal: alias and apply account directives now affect account directives (#825)

  • journal: periodic transactions can now have all the usual transaction fields (status mark, code, description, comment), for generating more expressive forecast transactions.

  • journal: forecast transactions now have the generating period expression attached as a tag named "recur".

  • journal: periodic transactions now start on the first instance of the recurring date, rather than the day after the last regular transaction (#750)

  • journal: periodic transaction rules now allow period expressions relative to today's date

  • csv: amount-in/amount-out errors are more detailed

  • balance: --drop is now ignored when not in flat mode, rather than producing a corrupted report (#754)

  • budget: --drop now preserves the top-level account in --budget reports

  • register: in CSV output, the code field is now included (#746)

  • smart dates now allow the YYYYMM format, and are better documented

  • uses hledger-lib 1.10

hledger-ui 1.10

  • the effect of --value, --forecast, and --anon flags is now preserved on reload (#753)

  • edit-at-transaction-position is now also supported when $EDITOR is neovim

  • support/require fsnotify

  • uses hledger-lib 1.10

hledger-web 1.10

  • view, add, edit permissions can be set at CLI or by Sandstorm HTTP header

  • the edit form has been revived, for whole-journal editing

  • the journal can now be uploaded and downloaded

  • the e key toggles empty accounts in the sidebar

  • multiple -f options, and --auto, work again

  • uses hledger-lib 1.10

hledger-api 1.10

  • uses hledger-lib 1.10

credits 1.10

Release contributors: Simon Michael, Alex Chen, Everett Hildenbrandt, Jakub Zárybnický, Nolan Darilek, Dmitry Astapov, Jacob Weisz, Peter Simons, Stephen Morgan, Pavlo Kerestey, Trevor Riles, Léo Gaspard, Mykola Orliuk, Wad, Nana Amfo.

2018/3/31 hledger 1.9

Report cleanups, normal-positive reports, HTML output, account sort codes, budget improvements. (mail)

Release contributors: Simon Michael, Eli Flanagan, Peter Simons, Christoph Nicolai, agander, M Parker, Moritz Kiefer, Mykola Orliuk.

  • support ghc 8.4, latest deps

hledger-lib 1.9

  • when the system text encoding is UTF-8, ignore any UTF-8 BOM prefix found when reading files.

  • CompoundBalanceReport amounts are now normally positive. (experimental)

hledger cli 1.9

  • journal: account directives can define a numeric account code to customize sorting. bal/bs/cf/is will sort accounts by account code, if any, then account name.

  • journal: support scientific number notation (#704, #706)

  • csv: reading a CSV file containing no records is no longer an error

  • cli: when the system text encoding is UTF-8, ignore any UTF-8 BOM prefix found when reading files. (Paypal's new CSV has this BOM prefix, causing a confusing parse error.)

  • cli: tabular reports no longer have a trailing blank line added. (This allows omitting the ">=0" delimiters in our functional tests, making them easier to read and maintain.)

  • acc: the accounts command now has --declared and --used flags

  • bal: the --invert flag flips all signs

  • bal: --drop now works with CSV output

  • bal/bs/bse/cf/is: show overall report span in title

  • bal/bs/bse/cf/is: show short month names as headings in monthly reports

  • bal/bs/bse/cf/is: these commands can now generate HTML output

  • bal/bs/is/cf: drop short name and indent fields from multicolumn CSV

  • bs/bse/cf/is: these, the "financial statement" commands, now show normal income, liability and equity balances as positive numbers. Negative numbers now indicate a contra-balance (eg an overdrawn checking account), a net loss, or a negative net worth. This makes these reports more like conventional financial statements, and easier to read and share with others. (Other commands, like balance, have not changed.) (experimental)

  • bs/cf/is: always show a tabular report, even with no report interval. Previously you would get a simple borderless report like the original balance command. Less code, fewer bugs.

  • bs/bse/cf/is: in CSV output, don't repeat the headings row for each subreport

  • budget: warn that CSV output with bal --budget is unimplemented

  • budget: bal --budget shows budget goals even with no or zero actual amounts. Makes budget reports more intuitive, at the cost of a temporary hack which may misorder columns in some cases (if actual and budget activity occur in a different range of columns).

  • budget: --budget uses only periodic txns with the selected interval.
    Budgets with different interval, eg a daily and weekly budget, are independent.

  • budget: show mostly fixed-width columns for readability

  • budget: fix bug where a budget report could include budget goals ending on the day before the report start date (splitSpan issue)

  • close: the equity command has been renamed to close. It now ignores any begin date (it always closes historical end balances). It also ignores --date2.

hledger-ui 1.9

  • -E/--empty toggles zeroes at startup (with opposite default to cli)

hledger-web 1.9

  • -E/--empty toggles zeroes at startup (with opposite default to cli)

hledger-api 1.9

2017/12/31 hledger 1.5


Release contributors: Simon Michael, Dmitry Astapov, Mykola Orliuk, Eli Flanagan, Elijah Caine, Sam Jeeves, Matthias Kauer, Hans-Peter Deifel, Mick Dekkers, Nadrieril, Alvaro Fernando García.

project-wide changes 1.5

  • remove upper bounds on all but hledger* and base (experimental) It's rare that my deps break their api or that newer versions must be avoided, and very common that they release new versions which I must tediously and promptly test and release hackage revisions for or risk falling out of stackage. Trying it this way for a bit.

hledger-lib 1.5

  • -V/--value uses today's market prices by default, not those of last transaction date. #683, #648)

  • csv: allow balance assignment (balance assertion only, no amount) in csv records (Nadrieril)

  • journal: allow space as digit group separator character, #330 (Mykola Orliuk)

  • journal: balance assertion errors now show line of failed assertion posting, #481 (Sam Jeeves)

  • journal: better errors for directives, #402 (Mykola Orliuk)

  • journal: better errors for included files, #660 (Mykola Orliuk)

  • journal: commodity directives in parent files are inherited by included files, #487 (Mykola Orliuk)

  • journal: commodity directives limits precision even after -B, #509 (Mykola Orliuk)

  • journal: decimal point/digit group separator chars are now inferred from an applicable commodity directive or default commodity directive. #399, #487 (Mykola Orliuk)

  • journal: numbers are parsed more strictly (Mykola Orliuk)

  • journal: support Ledger-style automated postings, enabled with --auto flag (Dmitry Astapov)

  • journal: support Ledger-style periodic transactions, enabled with --forecast flag (Dmitry Astapov)

  • period expressions: fix "nth day of {week,month}", which could generate wrong intervals (Dmitry Astapov)

  • period expressions: month names are now case-insensitive (Dmitry Astapov)

  • period expressions: stricter checking for invalid expressions (Mykola Orliuk)

  • period expressions: support "every 11th Nov" (Dmitry Astapov)

  • period expressions: support "every 2nd Thursday of month" (Dmitry Astapov)

  • period expressions: support "every Tuesday", short for "every th day of week" (Dmitry Astapov)

hledger cli 1.5

  • --auto adds Ledger-style automated postings to transactions (Dmitry Astapov, Mykola Orliuk)

  • --forecast generates Ledger-style periodic transactions in the future (Dmitry Astapov, Mykola Orliuk)

  • -V/--value uses today's market prices by default, not those of last transaction date. #683, #648

  • add: suggest implied (parent) and declared (by account directives) account names also

  • bal: --budget shows performance compared to budget goals defined with periodic transactions. Accounts with budget goals are displayed folded (depth-clipped) at a depth matching the budget specification. Unbudgeted accounts are hidden, or with --show-unbudgeted, shown at their usual depth. (Dmitry Astapov)

  • import: the output of --dry-run is now valid journal format

  • print: -B shows converted amounts again, as in 1.1, even without -x. #551 (Mykola Orliuk, Simon Michael)

  • tag: the first argument now filters tag names, additional arguments filter transactions (#261)

hledger-ui 1.5

  • fix help -> view manual (on posix platforms) #623

  • support -V/--value, --forecast, --auto

hledger-web 1.5

  • add form account fields now suggest implied and declared account names also

  • add form date field now uses a datepicker (Eli Flanagan)

  • don't write a session file at startup, don't require a writable working directory

  • support -V/--value, --forecast, --auto

hledger-api 1.5

2017/9/30 hledger 1.4

easy install script, simpler help commands, experimental addon commands now built in, new balancesheetequity/tags commands, new import command for easy CSV merging, print can detect new transactions, balance reports can sort by amount, cli conveniences (mail)

Release contributors: Simon Michael, Nicholas Niro, Hans-Peter Deifel, Jakub Zárybnický, Felix Yan, Mark Hansen, Christian G. Warden, Nissar Chababy, Peter Simons.

  • update stack configs for the last three GHC versions, add "make test-stackage" for finding stackage build problems, switch to GHC 8.2.1 as default for developer builds

  • streamline docs page

  • improve changelog/release notes process

  • improve makefile help and speed

  • Added a new installer script for the hledger tools, which aims to dodge common pitfalls and just work. Based on the stack install script, this bash script is cross platform, uses cabal or stack, installs stack and GHC if needed, and installs the latest release of all major hledger packages. See http://hledger.org/download for details.

hledger-lib 1.4

  • add readJournalFile[s]WithOpts, with simpler arguments and support for detecting new transactions since the last read.

  • query: add payee: and note: query terms, improve description/payee/note docs (Jakub Zárybnický, Simon Michael, #598, #608)

  • journal, cli: make trailing whitespace significant in regex account aliases Trailing whitespace in the replacement part of a regular expression account alias is now significant. Eg, converting a parent account to just an account name prefix: --alias '/:acct:/=:acct '

  • timedot: allow a quantity of seconds, minutes, days, weeks, months or years to be logged as Ns, Nm, Nd, Nw, Nmo, Ny

  • csv: switch the order of generated postings, so account1 is first. This simplifies things and facilitates future improvements.

  • csv: show the "creating/using rules file" message only with --debug

  • csv: fix multiple includes in one rules file

  • csv: add "newest-first" rule for more robust same-day ordering

  • deps: allow ansi-terminal 0.7

  • deps: add missing parsec lower bound, possibly related to #596, fpco/stackage#2835

  • deps: drop oldtime flag, require time 1.5+

  • deps: remove ghc < 7.6 support, remove obsolete CPP conditionals

  • deps: fix test suite with ghc 8.2

  • Fix a bug with -H showing nothing for empty periods (#583, Nicholas Niro) This patch fixes a bug that happened when using the -H option on a period without any transaction. Previously, the behavior was no output at all even though it should have shown the previous ending balances of past transactions. (This is similar to previously using -H with -E, but with the extra advantage of not showing empty accounts)

  • allow megaparsec 6 (#594)

  • allow megaparsec-6.1 (Hans-Peter Deifel)

  • fix test suite with Cabal 2 (#596)

hledger cli 1.4

  • cli: a @FILE argument reads flags & args from FILE, one per line

  • cli: reorganized commands list, added some new command aliases: accounts: a balance: b print: p, txns register: r

  • cli: accept -NUM as a shortcut for --depth=NUM (eg: -2)

  • cli: improve command-line help for --date2 (#604)

  • cli: make --help and -h the same, drop --man and --info for now (#579)

  • help: offers multiple formats, accepts topic substrings. The separate info/man commands have been dropped. help now chooses an appropriate documentation format as follows:

    • it uses info if available,
    • otherwise man if available,
    • otherwise $PAGER if defined,
    • otherwise less if available,
    • otherwise it prints on stdout
    • (and it always prints on stdout when piped). You can override this with the --info/--man/--pager/--cat flags. (#579)
  • bal/bs/cf/is: --sort-amount/-S sorts by largest amount instead of account name

  • bs/cf/is: support --output-file and --output-format=txt|csv The CSV output should be reasonably ok for dragging into a spreadsheet and reformatting.

  • bal/bs/cf/is: consistent double space between columns, consistent single final blank line. Previously, amounts wider than the column headings would be separated by only a single space.

  • bs/is: don't let an empty subreport disable the grand totals (fixes #588)

  • cf: exclude asset accounts with ":fixed" in their name (Christian G. Warden, Simon Michael, #584)

  • new balancesheetequity command: like balancesheet but also shows equity accounts (Nicholas Niro)

  • new import command: adds new transactions seen in one or more input files to the main journal file

  • print: --new shows only transactions added since last time (saves state in .latest.JOURNALFILE file)

  • new tags command: lists tags in matched transactions

  • most addons formerly shipped in bin/ are now builtin commands. These include: check-dates, check-dupes, equity, prices, print-unique, register-match, rewrite.

  • refactor: new Commands module and subdirectory. Builtin commands are now gathered more tightly in a single module, Hledger.Cli.Commands, facilitating change. The legacy "convert" command has been dropped.

  • refactor: BalanceView -> CompoundBalanceCommand

  • deps: drop support for directory < 1.2

  • deps: allow ansi-terminal 0.7

  • deps: drop oldtime flag, require time 1.5+

  • deps: simplify shakespeare bounds

  • deps: remove ghc < 7.6 support

  • bs/is: don't let an empty subreport disable the grand totals (#588)

  • allow megaparsec 6 (#594)

  • allow megaparsec-6.1 (Hans-Peter Deifel)

  • restore upper bounds on hledger packages

hledger-ui 1.4

  • a @FILE argument reads flags & args from FILE, one per line

  • enable --pivot and --anon options, like hledger CLI (#474) (Jakub Zárybnický)

  • accept -NUM as a shortcut for --depth NUM

  • deps: allow ansi-terminal 0.7

  • deps: drop oldtime flag, require time 1.5+

  • allow megaparsec 6 (#594, Simon Michael, Hans-Peter Deifel)

  • allow megaparsec-6.1 (Hans-Peter Deifel)

  • allow vty 5.17 (Felix Yan)

  • allow brick 0.24

  • restore upper bounds on hledger packages

hledger-web 1.4

  • a @FILE argument reads flags & args from FILE, one per line

  • enable --pivot and --anon options, like hledger CLI (#474) (Jakub Zárybnický)

  • web: Make "Add transaction" button tabbable (#430) (Jakub Zárybnický)

  • accept -NUM as a shortcut for --depth NUM

  • deps: drop oldtime flag, require time 1.5+, remove ghc < 7.6 support

  • remove unnecessary bound to satisfy hackage server
  • allow megaparsec 6 (#594, Simon Michael, Hans-Peter Deifel)

  • allow megaparsec-6.1 (Hans-Peter Deifel)

  • restore upper bounds on hledger packages

hledger-api 1.4

  • api: add support for swagger2 2.1.5+ (fixes #612)
  • require servant-server 0.10+ to fix compilation warning

  • restore upper bounds on hledger packages

2017/6/30 hledger 1.3

terminology/UI improvements for the status field, selection/scrolling/movement improvements in hledger-ui, negative amounts shown in red, bugfixes. (mail)

Release contributors: Simon Michael, Mykola Orliuk, Christian G. Warden, Dmitry Astapov, Justin Le, Joe Horsnell, Nicolas Wavrant, afarrow, Carel Fellinger, flip111, David Reaver, Felix Yan, Nissar Chababy, Jan Zerebecki.


make ghci-prof starts GHCI in profiling mode, enabling stack traces with traceStack

make ghci-web now also creates required symlinks

make site-reload opens an auto-reloading browser on the latest site html

make changelog-draft shows the commits since last tag as org nodes

hledger-lib 1.3

journal format

The "uncleared" transaction/posting status (and associated UI flags and keys) has been renamed to "unmarked" to remove ambiguity and confusion. See the issue and linked mail list discussion for more background. (#564)

csv format

In CSV conversion rules, assigning to the "balance" field name creates balance assertions (#537, Dmitry Astapov).

Doubled minus signs are handled more robustly (fixes #524, Nicolas Wavrant, Simon Michael)


Multiple status: query terms are now OR'd together. (#564)

Deps: allow megaparsec 5.3.

hledger cli 1.3


The "uncleared" transaction/posting status, and associated UI flags and keys, have been renamed to "unmarked" to remove ambiguity and confusion. This means that we have dropped the --uncleared flag, and our -U flag now matches only unmarked things and not pending ones. See the issue and linked mail list discussion for more background. (#564)

Also the -P short flag has been added for --pending, and the -U/-P/-C flags can be combined.

bs/is: fix "Ratio has zero denominator" error (#535)

bs/is/cf: fix --flat (#552) (Justin Le, Simon Michael)

bal/bs/is/cf: show negative amounts in red (Simon Michael, Justin Le). These commands now show negative amounts in red, when hledger detects that ANSI codes are supported, (ie when TERM is not "dumb" and stdout is not being redirected or piped).

print: show pending mark on postings (fixes #563). A pending mark on postings is now displayed, just like a cleared mark. Also there will now be a space between the mark and account name.

print: amounts are now better aligned, eg when there are posting status marks or virtual postings.


prices: add --inverted-costs flag, sort output, increase precision (Mykola Orliuk)

rewrite: add support for rewriting multipler postings into different commodities. For example, postings in hours can be used to generate postings in USD. (#557) (Christian G. Warden)

make addons compiles the experimental add-ons.

hledger-ui 1.3

The register screen now shows transaction status marks.

The "uncleared" status, and associated UI flags and keys, have been renamed to "unmarked" to remove ambiguity and confusion. This means that we have dropped the --uncleared flag, and our -U flag now matches only unmarked things and not pending ones. See the issue and linked mail list discussion for more background. (#564)

The P key toggles pending mode, consistent with U (unmarked) and C (cleared). There is also a temporary --status-toggles flag for testing other toggle styles; see hledger-ui -h. (#564)

There is now less "warping" of selection when lists change:

  • When the selected account disappears, eg when toggling zero accounts, the selection moves to the alphabetically preceding item, instead of the first one.

  • When the selected transaction disappears, eg when toggling status filters, the selection moves to the nearest transaction by date (and if several have the same date, by journal order), instead of the last one.

In the accounts and register screens, you can now scroll down further so that the last item need not always be shown at the bottom of the screen. And we now try to show the selected item centered in the following situations:

  • after moving to the end with Page down/End
  • after toggling filters/display modes (status, real, historical..)
  • on pressing the control-l key (this forces a screen redraw, also)
  • on entering the register screen from the accounts screen (except the first time, a known problem).

Items near the top won't be centered because we don't scroll above the top of the list.

Emacs movement keys are now supported, as well as VI keys. CTRL-b/CTRL-f/CTRL-n/CTRL-p and hjkl should work wherever unmodified arrow keys work.

In the transaction screen, amounts are now better aligned, eg when there are posting status marks or virtual postings.

Deps: allow brick 0.19 (#575, Felix Yan, Simon Michael)

hledger-web 1.3

Depends on hledger 1.3.

hledger-api 1.3

Depends on hledger 1.3.

2017/3/31 hledger 1.2

new commands list, more powerful balancesheet/incomestatement/cashflow commands, more parseable print output, better --pivot, basic automated postings and periodic transactions support, more and easier addons, bugfixes

Release contributors: Simon Michael, Mykola Orliuk, Justin Le, Peter Simons, Stefano Rodighiero, Moritz Kiefer, Pia Mancini, Bryan Richter, Steven R. Baker, Hans-Peter Deifel, Joshua Chia, Joshua Kehn, Michael Walker.

project-wide changes 1.2


bump stack config to latest lts, bump brick to 0.15.2 to allow hledger-iadd install in hledger dir, update cabal files to latest hpack 0.17.0/stack 1.4 format (#512), use more accurate license tag in Cabal file (Peter Simons).


set up a hledger open collective (http://opencollective.com/hledger), more devguide links to issues with bounties, codefund link, start tracking and publishing project finances (dogfooding!).

Documentation and website

docs page & manual cleanups, begin organising a cookbook, update addons list, move detailed addon docs out of hledger manual, document addons installation, explain print's CSV output, note an issue with balance assertions & multiple -f options, clarify tags, add github stars widget to home and devguide, improve market price docs, ui & web screenshots layout fixes, fix extra whitespace after synopsis in hledger-web text manuals, update accounts directive/budget/rewrite/read-related mockups, drop old org notes.


consolidate extra/ and data/ in examples/, tarsnap csv rules & reporting example, xpensetracker csv rules.


Travis CI now checks functional tests/build warnings/addons, temporary workaround for Appveyor CI failures, remove accidentally committed pandoc executables, some pandoc filter fixes, mailmap file to clean up git log authors, bench.hs cleanup, fix gitignore of generated manuals, avoid excessive rebuilding with make [func]test, run functional tests more verbosely, add alex/happy update step to cabal-install.sh.

hledger-lib 1.2

journal format

A pipe character can optionally be used to delimit payee names in transaction descriptions, for more accurate querying and pivoting by payee. Eg, for a description like payee name | additional notes, the two parts will be accessible as pseudo-fields/tags named payee and note.

Some journal parse errors now show the range of lines involved, not just the first.

ledger format

The experimental ledger: reader based on the WIP ledger4 project has been disabled, reducing build dependencies.


Fix a bug when tying the knot between postings and their parent transaction, reducing memory usage by about 10% (#483) (Mykola Orliuk)

Fix a few spaceleaks (#413) (Moritz Kiefer)

Add Ledger.Parse.Text to package.yaml, fixing a potential build failure.

Allow megaparsec 5.2 (#503)

Rename optserror -> usageError, consolidate with other error functions

hledger cli 1.2


"hledger" and "hledger -h" now print a better organised commands list and general usage message respectively (#297).

The common reporting flags can now be used anywhere on the command line.

Fixed deduplication of addons in commands list.

Fixed ugly stack traces in command line parse error messages.

The -V/--value flag is now a global report flag, so it works with balance, print, register, balancesheet, incomestatement, cashflow, etc. (Justin Le)

The --pivot global reporting option replaces all account names with the value of some other field or tag. It has been improved, eg:

  • we don't add the field/tag name name as a prefix
  • when pivoting on a tag, if the tag is missing we show a blank (rather than showing mixed tag values and account names)
  • a pipe character delimiter may be used in descriptions to get a more accurate and useful payee report (hledger balance --pivot payee)

options cleanups


Easier installation: move add-ons and example scripts to bin/, convert to stack scripts, add a build script to install all deps, add some functional tests, test add-ons with Travis CI, add installation docs to download page.

Improved docs: all addons now contain their own documentation. Most of them (all but hledger-budget) use a new reduced-boilerplate declaration format and can show short (-h) and long (--help) command line help. (Long help is declared with pre and postambles to the generated options help, short help is that truncated at the start of the hledger common flags.)

hledger now shows a cleaner list of addon commands, showing only the compiled version of an addon when both source and compiled versions are in $PATH. (Addons with .exe extension or no extension are considered compiled. Modification time is not checked, ie, an old compiled addon will override a newer source version. If there are three or more versions of an addon, all are shown. )

New addons added/included:

  • autosync - example symlink to ledger-autosync
  • budget - experimental budget reporting command supporting Ledger-like periodic transactions and automated transactions (Mykola Orliuk)
  • chart - pie-chart-generating prototype, a repackaging of the old hledger-chart tool
  • check - more powerful balance assertions (Michael Walker)
  • check-dupes - find accounts sharing the same leaf name (Stefano Rodighiero)
  • prices - show all market price records (Mykola Orliuk)
  • register-match - a helper for ledger-autosync's deduplication, finds best match for a transaction description

The equity command now always generates a valid journal transaction, handles prices better, and adds balance assertions (Mykola Orliuk).

The rewrite command is more robust and powerful (Mykola Orliuk):

  • in addition to command-line rewrite options, it understands rewrite rules defined in the journal, similar to Ledger's automated transactions (#99). Eg:

    = ^income
        (liabilities:tax)  *.33
    = expenses:gifts
        budget:gifts  *-1
        assets:budget  *1
  • it can generate diff output, allowing easier review of the proposed changes, and safe modification of original journal files (preserving file-level comments and directives). Eg:

    hledger-rewrite --diff Agency --add-posting 'Expenses:Taxes  *0.17' | patch
  • rewrites can affect multiple postings in a transaction, not just one.

  • posting-specific dates are handled better


A new --pretty-tables option uses unicode characters for rendering table borders in multicolumn reports (#522) (Moritz Kiefer)


These commands are now more powerful, able to show multicolumn reports and generally having the same features as the balance command. (Justin Le)

balancesheet has always ignored a begin date specified with a -b or -p option; now it also ignores a begin date specified with a date: query. (Related discussion at #531)


The output of print is now always a valid journal (fixes #465) (Mykola Orliuk).

print now tries to preserves the format of implicit/explicit balancing amounts and prices, by default. To print with all amounts explicit, use the new --explicit/-x flag (fixes #442). (Mykola Orliuk)

Don't lose the commodity of zero amounts/zero balance assertions (fixes #475) (Mykola Orliuk)


Fix a regression in the readability of option parsing errors (#478) (Hans-Peter Deifel)

Fix an example in Cli/Main.hs (Steven R. Baker)

Allow megaparsec 5.2 (#503)

hledger-ui 1.2

Fix a pattern match failure when pressing E on the transaction screen (fixes #508)

Accounts with ? in name had empty registers (fixes #498) (Bryan Richter)

Allow brick 0.16 (Joshua Chia) and brick 0.17/vty 0.15 (Peter Simons)

Allow megaparsec 5.2 (fixes #503)

Allow text-zipper 0.10

hledger-web 1.2

Accounts with ? in name had empty registers (fixes #498) (Bryan Richter)

Allow megaparsec 5.2 (fixes #503)

2016/12/31 hledger 1.1

more robust file format detection, integration of WIP ledger4 parser, balance assignments, hledger-ui --watch, hledger-iadd integration, bugfixes

Release contributors: Simon Michael, Johannes Gerer, Mykola Orliuk, Shubham Lagwankar.

project-wide changes 1.1


  • don't show stack trace details in errors

  • more predictable file format detection

    When we don't recognise a file's extension, instead of choosing a subset of readers to try based on content sniffing, now we just try them all. Also, this can be overridden by prepending the reader name and a colon to the file path (eg timedot:file.dat, csv:-).

  • avoid creating junk CSV rules files when trying alternate readers. We now create it only after successfully reading a file as CSV.

  • improvements to -B and -V docs: clearer descriptions, more linkage (#403)

hledger-lib 1.1

journal format

  • balance assignments are now supported (#438, #129, #157, #288)

    This feature also brings a slight performance drop (~5%); optimisations welcome.

  • also recognise *.hledger files as hledger journal format

ledger format

  • use ledger-parse from the ledger4 project as an alternate reader for C++ Ledger journals

    The idea is that some day we might get better compatibility with Ledger files this way. Right now this reader is not very useful and will be used only if you explicitly select it with a ledger: prefix. It parses transaction dates, descriptions, accounts and amounts, and ignores everything else. Amount parsing is delegated to hledger's journal parser, and malformed amounts might be silently ignored.

    This adds at least some of the following as new dependencies for hledger-lib: parsers, parsec, attoparsec, trifecta.


  • update base lower bound to enforce GHC 7.10+

    hledger-lib had a valid install plan with GHC 7.8, but currently requires GHC 7.10 to compile. Now we require base 4.8+ everywhere to ensure the right GHC version at the start.

  • Hledger.Read api cleanups

  • rename dbgIO to dbg0IO, consistent with dbg0, and document a bug in dbg*IO

  • make readJournalFiles [f] equivalent to readJournalFile f (#437)

  • more general parser types enabling reuse outside of IO (#439)

hledger cli 1.1


  • with -V, don't ignore market prices in the future (#453, #403)

  • with -V and multiple same-date market prices, use the last parsed not the highest price (#403)


  • fix non-existent "oldtime" dependency (#431)

  • hledger-equity.hs now generates valid journal format when there are multiple commodities

hledger-ui 1.1

  • with --watch, the display updates automatically to show file or date changes

    hledger-ui --watch will reload data when the journal file (or any included file) changes. Also, when viewing a current standard period (ie this day/week/month/quarter/year), the period will move as needed to track the current system date.

  • the --change flag shows period changes at startup instead of historical ending balances

  • the A key runs the hledger-iadd tool, if installed

  • always reload when g is pressed

    Previously it would check the modification time and reload only if it looked newer than the last reload.

  • mark hledger-ui as "stable"

  • allow brick 0.15, vty 5.14, text-zipper 0.9

hledger-web 1.1

  • add --host option (#429)

    This came up in the context of Docker, but it seems it wasn't possible for hledger-web to serve remote clients directly (without a proxy) because of being hardcoded. That can now be changed with --host=IPADDR. Also, the default base url uses this address rather than a hard-coded "localhost".

  • rename --server to --serve

    The --server flag sounded too close in meaning to --host so I've renamed it to --serve. The old spelling is still accepted, but deprecated and will be removed in the next release.

hledger-api 1.1

  • serves on by default, --host option added (#432)

    Consistent with hledger-web: serves only local requests by default, use --host=IPADDR to change this.

  • fixed the version string in command-line help and swagger info

2016/10/26 hledger 1.0

More hledger-ui features, better hledger-web layout, new hledger-api server, new timedot format, --pivot & --anon, reorganized multi-format docs, built-in help.


Release contributors: Simon Michael, Dominik Süß, Thomas R. Koll, Moritz Kiefer, jungle-boogie, Sergei Trofimovich, Malte Brandy, Sam Doshi, Mitchell Rosen, Hans-Peter Deifel, Brian Scott, and Andrew Jones.


  • added GHC 8 support, dropped GHC 7.6 and 7.8 support.

    GHC 7.8 support could be restored with small code changes and a maintainer.

  • a cabal.project file has been added (Moritz Kiefer)

  • use hpack for maintaining cabal files (#371).

    Instead of editing cabal files directly, we now edit the less verbose and less redundant package.yaml files and let stack (or hpack) update the cabal files. We commit both the .yaml and .cabal files.

  • clean up some old cabal flags

  • tools/simplebench has been spun off as the quickbench package.

  • add Appveyor CI builds, provide more up-to-date Windows binaries

  • extra: add a bunch of CSV rules examples


  • the website is simpler, clearer, and more mobile-friendly.

    Docs are now collected on a single page and organised by type: getting started, reference, more.

  • reference docs have been split into one manual for each executable and file format.

    This helps with maintenance and packaging and also should make it easier to see what's available and to read just what you need.

  • manuals are now provided in html, plain text, man and info formats

    generated from the same source by a new Shake-based docs build system. (#292)

  • versioned manuals are provided on the website, covering recent releases and the latest dev version (#385, #387)

  • manuals are built in to the hledger executables, allowing easy offline reading on all platforms.

    PROG -h              shows PROG's command-line usage
    PROG --help          shows PROG's manual (fixed width)
    PROG --man           shows PROG's manual with man (formatted/paged)
    PROG --info          shows PROG's manual with info (hypertext)
    hledger help [TOPIC] shows any manual
    hledger man  [TOPIC] shows any manual with man
    hledger info [TOPIC] shows any manual with info
  • the general and reporting options are now listed in all executable manuals.

    We assume any of them which are unsupported are harmlessly ignored.

  • demo.hledger.org is using beancount's example journal.

    This is the somewhat realistic example journal from the beancount project, tweaked for hledger.

  • minor copyedits (jungle-boogie)


  • parsing multiple input files is now robust.

    When multiple -f options are provided, we now parse each file individually rather than just concatenating them, so they can have different formats (#320). Note this also means that directives (like Y or alias) no longer carry over from one file to the next.

  • -I has been added as the short flag for --ignore-assertions

    (this is different from Ledger's CLI, but useful for hledger-ui).

  • parsing an argument-less --debug option is more robust

hledger-lib 1.0

timedot format

  • new "timedot" format for retroactive/approximate time logging.

    Timedot is a plain text format for logging dated, categorised quantities (eg time), supported by hledger. It is convenient for approximate and retroactive time logging, eg when the real-time clock-in/out required with a timeclock file is too precise or too interruptive. It can be formatted like a bar chart, making clear at a glance where time was spent.

timeclock format

  • renamed "timelog" format to "timeclock", matching the emacs package

  • sessions can no longer span file boundaries (unclocked-out

    sessions will be auto-closed at the end of the file).

  • transaction ids now count up rather than down (#394)

  • timeclock files no longer support default year directives

  • removed old code for appending timeclock transactions to journal transactions.

    A holdover from the days when both were allowed in one file.

csv format

  • fix empty field assignment parsing, rule parse errors after megaparsec port (#407) (Hans-Peter Deifel)

journal format

  • journal files can now include timeclock or timedot files (#320)

    (but not yet CSV files).

  • fixed an issue with ordering of same-date transactions included from other files

  • the "commodity" directive and "format" subdirective are now supported, allowing

    full control of commodity style (#295) The commodity directive's format subdirective can now be used to override the inferred style for a commodity, eg to increase or decrease the precision. This is at least a good workaround for #295.

  • Ledger-style "apply account"/"end apply account" directives are now used to set a default parent account.

  • the Ledger-style "account" directive is now accepted (and ignored).

  • bracketed posting dates are more robust (#304)

    Bracketed posting dates were fragile; they worked only if you wrote full 10-character dates. Also some semantics were a bit unclear. Now they should be robust, and have been documented more clearly. This is a legacy undocumented Ledger syntax, but it improves compatibility and might be preferable to the more verbose "date:" tags if you write posting dates often (as I do). Internally, bracketed posting dates are no longer considered to be tags. Journal comment, tag, and posting date parsers have been reworked, all with doctests.

  • balance assertion failure messages are clearer

  • with --debug=2, more detail about balance assertions is shown.


  • file parsers have been ported from Parsec to Megaparsec \o/ (#289, #366) (Alexey Shmalko, Moritz Kiefer)

  • most hledger types have been converted from String to Text, reducing memory usage by 30%+ on large files

  • file parsers have been simplified for easier troubleshooting (#275).

    The journal/timeclock/timedot parsers, instead of constructing opaque journal update functions which are later applied to build the journal, now construct the journal directly by modifying the parser state. This is easier to understand and debug. It also rules out the possibility of journal updates being a space leak. (They weren't, in fact this change increased memory usage slightly, but that has been addressed in other ways). The ParsedJournal type alias has been added to distinguish "being-parsed" journals and "finalised" journals.

  • file format detection is more robust.

    The Journal, Timelog and Timedot readers' detectors now check each line in the sample data, not just the first one. I think the sample data is only about 30 chars right now, but even so this fixed a format detection issue I was seeing. Also, we now always try parsing stdin as journal format (not just sometimes).

  • all file formats now produce transaction ids, not just journal (#394)

  • git clone of the hledger repo on windows now works (#345)

  • added missing benchmark file (#342)

  • our stack.yaml files are more compatible across stack versions (#300)

  • use newer file-embed to fix ghci working directory dependence

  • report more accurate dates in account transaction report when postings have their own dates

    (affects hledger-ui and hledger-web registers). The newly-named "transaction register date" is the date to be displayed for that transaction in a transaction register, for some current account and filter query. It is either the transaction date from the journal ("transaction general date"), or if postings to the current account and matched by the register's filter query have their own dates, the earliest of those posting dates.

  • simplify account transactions report's running total.

    The account transactions report used for hledger-ui and -web registers now gives either the "period total" or "historical total", depending strictly on the --historical flag. It doesn't try to indicate whether the historical total is the accurate historical balance (which depends on the user's report query).

  • reloading a file now preserves the effect of options, query arguments etc.

  • reloading a journal should now reload all included files as well.

  • the Hledger.Read.* modules have been reorganised for better reuse.

    Hledger.Read.Utils has been renamed Hledger.Read.Common and holds low-level parsers & utilities; high-level read utilities are now in Hledger.Read.

  • clarify amount display style canonicalisation code and terminology a bit.

    Individual amounts still have styles; from these we derive the standard "commodity styles". In user docs, we might call these "commodity formats" since they can be controlled by the "format" subdirective in journal files.

  • Journal is now a monoid

  • expandPath now throws a proper IO error

  • more unit tests, start using doctest

hledger cli 1.0


  • suggest only one commodity at a time as default amount (#383)

    (since we currently can't input more than one at a time)


  • added --change flag for consistency

  • -H/--historical now also affects single-column balance reports with a start date (#392).

    This has the same effect as just omitting the start date, but adds consistency.

  • in CSV output, render amounts in one-line format (#336)


  • fix an infinite loop (#393)


  • in CSV output, fix and rename the transaction id field


  • fix a sorting regression with --date2 (#326)

  • --average/-A is now affected by --historical/-H

  • added --cumulative flag for consistency

  • in CSV output, include the transaction id and rename the total field (#391)


  • fixed an issue with ordering of include files


  • --pivot option added, groups postings by tag instead of account (#323) (Malte Brandy)

  • --anon option added, obfuscates account names and descriptions (#265) (Brian Scott)

    (Only affects the hledger tool, for now.)

  • try to clarify balance/register's various report modes,

    kinds of "balance" displayed, and related options and language.

  • with multiple --change/--cumulative/--historical flags, use the last one instead of complaining

  • don't add the "d" suffix when displaying day periods

  • stack-ify extra/hledger-rewrite.hs

hledger-ui 1.0

accounts screen

  • at depth 0, show accounts on one "All" line and show all transactions in the register

  • 0 now sets depth limit to 0 instead of clearing it

  • always use --no-elide for a more regular accounts tree

register screen

  • registers can now include/exclude subaccount transactions.

    The register screen now includes subaccounts' transactions if the accounts screen was in tree mode, or when showing an account which was at the depth limit. Ie, it always shows the transactions contributing to the balance displayed on the accounts screen. As on the accounts screen, F toggles between tree mode/subaccount txns included by default and flat mode/subaccount txns excluded by default. (At least, it does when it would make a difference.)

  • register transactions are filtered by realness and status (#354).

    Two fixes for the account transactions report when --real/--cleared/real:/status: are in effect, affecting hledger-ui and hledger-web:

    1. exclude transactions which affect the current account via an excluded posting type. Eg when --real is in effect, a transaction posting to the current account with only virtual postings will not appear in the report.

    2. when showing historical balances, don't count excluded posting types in the starting balance. Eg with --real, the starting balance will be the sum of only the non-virtual prior postings.

      This is complicated and there might be some ways to confuse it still, causing wrongly included/excluded transactions or wrong historical balances/running totals (transactions with both real and virtual postings to the current account, perhaps ?)

  • show more accurate dates when postings have their own dates.

    If postings to the register account matched by the register's filter query have their own dates, we show the earliest of these as the transaction date.


  • H toggles between showing "historical" or "period" balances (#392).

    By default hledger-ui now shows historical balances, which include transactions before the report start date (like hledger balance --historical). Use the H key to toggle to "period" mode, where balances start from 0 on the report start date.

  • shift arrow keys allow quick period browsing

    • shift-down narrows to the next smaller standard period (year/quarter/month/week/day), shift-up does the reverse
    • when narrowed to a standard period, shift-right/left moves to the next/previous period
    • `t` sets the period to today.
  • a runs the add command

  • E runs $HLEDGERUIEDITOR or $EDITOR or a default editor (vi) on the journal file.

    When using emacs or vi, if a transaction is selected the cursor will be positioned at its journal entry.

  • / key sets the filter query; BACKSPACE/DELETE clears it

  • Z toggles display of zero items (like --empty), and they are shown by default.

    -E/--empty is now the default for hledger-ui, so accounts with 0 balance and transactions posting 0 change are shown by default. The Z key toggles this, entering "nonzero" mode which hides zero items.

  • R toggles inclusion of only real (non-virtual) postings

  • U toggles inclusion of only uncleared transactions/postings

  • I toggles balance assertions checking, useful for troubleshooting

  • vi-style movement keys are now supported (for help, you must now use ? not h) (#357)

  • ESC cancels minibuffer/help or clears the filter query and jumps to top screen

  • ENTER has been reserved for later use

  • reloading now preserves any options and modes in effect

  • reloading on the error screen now updates the message rather than entering a new error screen

  • the help dialog is more detailed, includes the hledger-ui manual, and uses the full terminal width if needed

  • the header/footer content is more efficient; historical/period and tree/flat modes are now indicated in the footer

  • date: query args on the command line now affect the report period.

    A date2: arg or --date2 flag might also affect it (untested).

  • hledger-ui now uses the quicker-building microlens

hledger-web 1.0


  • use full width on large screens, hide sidebar on small screens, more standard bootstrap styling (#418, #422) (Dominik Süß)

  • show the sidebar by default (#310)

  • fix the add link's tooltip

  • when the add form opens, focus the first field (#338)

  • leave the add form's date field blank, avoiding a problem with tab clearing it (#322)

  • use transaction id instead of date in transaction urls (#308) (Thomas R. Koll)

  • after following a link to a transaction, highlight it (Thomas R. Koll)

  • misc. HTML/CSS/file cleanups/fixes (Thomas R. Koll)


  • startup is more robust (#226).

    Now we exit if something is already using the specified port, and we don't open a browser page before the app is ready.

  • termination is more robust, avoiding stray background threads.

    We terminate the server thread more carefully on exit, eg on control-C in GHCI.

  • more robust register dates and filtering in some situations (see hledger-ui notes)

  • reloading the journal preserves options, arguments in effect (#314).

    The initial query specified by command line arguments is now preserved when the journal is reloaded. This does not appear in the web UI, it's like an invisible extra filter.

  • show a proper not found page on 404

  • document the special `inacct:` query (#390)

hledger-api 1.0


  • new hledger-api tool: a simple web API server with example clients (#316)

  • start an Angular-based API example client (#316) (Thomas R. Koll)

2008-2015 Pre-1.0

2015/10/30 hledger 0.27

New curses-style interface, market value reporting, wide characters, fast regex aliases, man pages (mail)

Release contributors: Simon Michael, Carlos Lopez-Camey.

hledger 0.27:

Account aliases:

  • Regular expression account aliases are now fast enough that you can use lots of them without slowing things down. They now take O(aliases x accounts) time, instead of O(aliases x transactions); also, regular expressions are no longer recompiled unnecessarily.


  • The hledger packages now have man pages, based on the current user manual, thanks to the mighty pandoc (#282).

Journal format:

  • Dates must now begin with a digit (not /, eg).

  • The comment directive longer requires an end comment, and will extend to the end of the file(s) without it.

Command-line interface:

  • Output (balance reports, register reports, print output etc.) containing wide characters, eg chinese/japanese/korean characters, should now align correctly, when viewed in apps and fonts that show wide characters as double width (#242).

  • The argument for --depth or depth: must now be positive.


  • Journal entries are now saved with all amounts explicit, to avoid losing price info (#283).

  • Fixed a bug which sometimes (when the same letter pair was repeated) caused it not to pick the most similar past transaction for defaults.


  • There is now a -V/--value flag to report current market value (as in Ledger). It converts all reported amounts using their "default market price". "Market price" is the new name for "historical prices", defined with the P directive. The default market price for a commodity is the most recent one found in the journal on or before the report end date.

    Unlike Ledger, hledger's -V uses only the market prices recorded with P directives; it does not use the transaction prices recorded as part of posting amounts. Using both -B and -V at the same time is possible.

  • Fixed a bug in amount normalization which caused amount styles (commodity symbol placement, decimal point character, etc.) to be lost in certain cases (#230, #276).

  • The balance command's --format option can now adjust the rendering style of multi-commodity amounts, if you begin the format string with one of:

    %_ - renders amounts on multiple lines, bottom-aligned (the default) %^ - renders amounts on multiple lines, top-aligned %, - renders amounts on one line, comma-separated

  • The balance report's final total (and the line above it) now adapt themselves to a custom --format.


  • The --match option prints the journal entry that best matches a description (ie whose description field is most similar to the value given, and if there are several equally similar, the most recent). This was originally an add-on I used to guess account names for ledger-autosync. It's nice for quickly looking up a recent transaction from a guessed or partial description.

  • print now always right-aligns the amounts in an entry, even when they are wider than 12 characters. (If there is a price, it's considered part of the amount for right-alignment.)


  • Amount columns now resize automatically, using more space if it's needed and available.

hledger-ui 0.27:

  • hledger-ui is a new curses-style UI, intended to be a standard part of the hledger toolset for all users (except on native MS Windows, where the vty lib is not yet supported).

    The UI is quite simple, allowing just browsing of accounts and transactions, but it has a number of improvements over the old hledger-vty, which it replaces:

    • adapts to screen size
    • handles wide characters
    • shows multi-commodity amounts on one line
    • manages cursor and scroll position better
    • allows depth adjustment
    • allows --flat toggle
    • allows --cleared toggle
    • allows journal reloading
    • shows a more useful transaction register, like hledger-web
    • offers multiple color themes
    • includes some built-in help hledger-ui is built with brick, a new higher-level UI library based on vty, making it relatively easy to grow and maintain.

hledger-web 0.27:

  • Fix keyboard shortcut for adding a transaction (Carlos Lopez-Camey)

  • Clear the form when clicking 'Add a transaction' (just like the shortcut) (Carlos Lopez-Camey)

  • Disallow -f- (reading from standard input) which currently doesn't work (#202)

  • Fix broken links when using --base-url (#235)

  • Fix the --file-url option (#285)

  • Show fewer "other accounts" in the account register: to reduce clutter in the "other accounts" field, if there are both real and virtual postings to other accounts, show only the accounts posted to by real postings.

2015/7/12 hledger 0.26

Website & doc updates, account aliases, misc. bugfixes & cleanups, performance.

Release contributors: Simon Michael, Imuli, Carlos Lopez-Camey, Kyle Marek-Spartz, Rick Lupton, Simon Hengel.

Changes to hledger.org & docs:

  • examples everywhere, screenshots, content & style updates
  • manual: reorganise topics, add some undocumented things, clarify some things
  • dev guide: more links, put how-tos first, copy diagram from old wiki, update the setup docs

User-visible changes in hledger since 0.25.1:

Account aliases:

  • Account aliases are once again non-regular-expression-based, by default. (#252)

    The regex account aliases added in 0.24 tend to trip up people switching between hledger and Ledger. (Also they are currently slow). We now use the old non-regular-expression aliases again, by default; these are unsurprising, useful, and pretty close in functionality to Ledger's aliases.

    The new regex aliases are still available, but they must now be enclosed in forward slashes. (Ledger effectively ignores these.)

Journal format:

  • We now parse, and also print, journal entries with no postings, as proposed on the mail lists. These are not well-formed General Journal entries/transactions, but on the other hand: Ledger and beancount parse them; if they are parsed, they should be printed; they provide a convenient way to record (and report) non-transaction events; and they permit more gradual introduction and learning of the concepts (so eg a beginner can keep a simple journal before learning about accounts and postings).

  • Trailing whitespace after a comment directive is now ignored.

Command-line interface:

  • The -f/file option may now be used multiple times. This is equivalent to concatenating the input files before running hledger. The add command adds entries to the first file specified.


  • real: (no argument) is now a synonym for real:1

  • tag: now matches tag names with a regular expression, like most other queries

  • empty: is no longer supported, as it overlaps a bit confusingly with amt:0. The --empty flag is still available.

  • You can now match on pending status (#250)

    A transaction/posting status of ! (pending) was effectively equivalent to * (cleared). Now it's a separate state, not matched by --cleared. The new Ledger-compatible --pending flag matches it, and so does --uncleared.

    The relevant search query terms are now status:*, status:! and status: (the old status:1 and status:0 spellings are deprecated).

    Since we interpret --uncleared and status: as "any state except cleared", it's not currently possible to match things which are neither cleared nor pending.


  • activity no longer excludes 0-amount postings by default.


  • Don't show quotes around the journal file path in the "Creating..." message, for consistency with the subsequent "Adding..." message.


  • Accounts beginning with "debt" or now also recognised as liabilities.


  • We now limit the display precision of inferred prices. (#262)

    When a transaction posts to two commodities without specifying the conversion price, we generate a price which makes it balance (cf <http://hledger.org/journal.html#transaction-prices). The print command showed this with full precision (so that manual calculations with the displayed numbers would look right), but this sometimes meant we showed 255 digits (when there are multiple postings in the commodity being priced, and the averaged unit price is an irrational number). In this case we now set the price's display precision to the sum of the (max) display precisions of the commodities involved. An example:

    hledger -f- print
        c    C 10.00
        c    C 11.00
        d  D -320.00
        c  C 10.00 @ D 15.2381
        c  C 11.00 @ D 15.2381
        d     D -320.00

    There might still be cases where this will show more price decimal places than necessary.

  • We now show inferred unit prices with at least 2 decimal places.

    When inferring prices, if the commodities involved have low display precisions, we don't do a good job of rendering accurate-looking unit prices. Eg if the journal doesn't use any decimal places, any inferred unit prices are also displayed with no decimal places, which makes them look wrong to the user. Now, we always give inferred unit prices a minimum display precision of 2, which helps a bit.


  • Postings with no amounts could give a runtime error in some obscure case, now fixed.


  • stats now supports -o/--outputfile, like register/balance/print.
  • An O(n^2) performance slowdown has been fixed, it's now much faster on large journals.
    |                                      ||   0.25 |   0.26 |
    | -f data/100x100x10.journal     stats ||   0.10 |   0.16 |
    | -f data/1000x1000x10.journal   stats ||   0.45 |   0.21 |
    | -f data/10000x1000x10.journal  stats ||  58.92 |   2.16 |


  • The June 30 day span was not being rendered correctly; fixed. (#272)
  • The deprecated shakespeare-text dependency has been removed more thoroughly.
  • The bench script invoked by "cabal bench" or "stack bench" now runs some simple benchmarks. You can get more accurate benchmark times by running with --criterion. This will usually give much the same numbers and takes much longer. Or with --simplebench, it benchmarks whatever commands are configured in bench/default.bench. This mode uses the first "hledger" executable in $PATH.

User-visible changes in hledger-web since 0.25.1:

  • make the j keybinding respect --base-url (fixes #271)
  • respect command line options (fixes #225)
  • include the unminified jquery source again (#161)
  • fix build breakage from #165 (fixes #268)
  • fix a js error breaking add form in browsers other than firefox (fixes #251)
  • drop deprecated network-conduit dependency
2015/4/29 hledger-web 0.25.1
  • support/require base-compat >0.8 (#245)
2015/4/29 hledger 0.25.1
  • timelog: support the description field (#247)
2015/4/29 hledger-lib 0.25.1
  • support/require base-compat >0.8 (#245)

2015/4/7 hledger 0.25

GHC 7.10 compatibility, terminal width awareness, useful averages and totals columns, and a more robust hledger-web add form.


Release contributors: Simon Michael, Julien Moutinho.

User-visible changes in hledger since 0.24.1:

  • GHC 7.10 compatibility (#239)

  • On POSIX systems, the register command now uses the full terminal width by default. Specifically, the output width is set from:

    1. a --width option
    2. or a COLUMNS environment variable (NB: not the same as a bash shell var)
    3. or on POSIX (non-windows) systems, the current terminal width
    4. or the default, 80 characters. This feature requires the C curses dev libraries, making installation slightly harder. If that's a problem you can disable curses support with a cabal flag: cabal install -f-curses ....
  • register's --width option now accepts an optional description column width following the overall width (--width WIDTH[,DESCWIDTH]). This also sets the account column width, since the available space (WIDTH-41) is divided up between these two columns. Here's a diagram:

    <--------------------------------- width (W) ---------------------------------->
    date (10)  description (D)       account (W-41-D)     amount (12)   balance (12)
    DDDDDDDDDD dddddddddddddddddddd  aaaaaaaaaaaaaaaaaaa  AAAAAAAAAAAA  AAAAAAAAAAAA
    $ hledger reg                 # use terminal width on posix
    $ hledger reg -w 100          # width 100, equal description/account widths
    $ hledger reg -w 100,40       # width 100, wider description
    $ hledger reg -w $COLUMNS,100 # terminal width and set description width
  • balance: new -T/--row-total and -A/--average options

    In multicolumn balance reports, -T/--row-total now shows a totals column and -A/--average shows an averages column. This helps eg to see monthly average expenses (hledger bal ^expenses -MA).

    NB our use of -T deviates from Ledger's UI, where -T sets a custom final total expression.

  • balance: -N is now short for --no-total

  • balance: fix partially-visible totals row with --no-total

    A periodic (not using --cumulative or --historical) balance report with --no-total now hides the totals row properly.

  • journal, csv: comment lines can also start with *

    As in Ledger. This means you can embed emacs org/outline-mode nodes in your journal file and manipulate it like an outline.

User-visible changes in hledger-web since 0.24.1:

  • GHC 7.10 compatibility (#239)

  • fix the add form when there are included files (#234)

    NB to make this work, the add form now shows the full file path of the main and included journal files.

  • improve add form validation (#223, #234)

    All add form errors are displayed as form errors, not internal server errors, and when there are errors the add form is redisplayed (form inputs are not preserved, currently).

  • keep the add button right-aligned when pressing ctrl - on the add form

2015/3/15 hledger 0.24.1
  • timelog: show hours with 2 decimal places, not 1 (#237)
  • fix balance accumulation through assertions in several commodities (#195)
  • fix rendering of week 52 heading in weekly reports
  • allow utf8-string-1 (fpco/stackage/#426)
2015/3/15 hledger-lib 0.24.1
  • fix JournalReader "ctx" compilation warning
  • add some type signatures in Utils to help make ghci-web
2015/1/10 hledger-web 0.24.1
  • add missing modules to fix cabal tests (#232)

2014/12/25 hledger 0.24

Release contributors: Simon Michael, Julien Moutinho, Ryan Desfosses, Gergely Risko, Gwern Branwen.

CSV export, a non-floating point number representation, more powerful account aliases, speedups, and a streamlined web UI.

User-visible changes in hledger since 0.23.3:


  • fix redundant compilation when cabal installing the hledger packages
  • switch to Decimal for representing amounts (#118)
  • report interval headings (eg in balance, register reports) are shown compactly when possible
  • general speedups.
|                                            || hledger-0.23.3 | hledger-0.24 | ledger |
| -f data/100x100x10.journal     balance     ||           0.05 |         0.03 |   0.01 |
| -f data/1000x1000x10.journal   balance     ||           0.34 |         0.21 |   0.04 |
| -f data/10000x1000x10.journal  balance     ||           2.72 |         1.48 |   0.19 |
| -f data/10000x1000x10.journal  balance aa  ||           3.16 |         1.55 |   0.14 |
| -f data/100x100x10.journal     register    ||           0.09 |         0.05 |   0.04 |
| -f data/1000x1000x10.journal   register    ||           0.66 |         0.32 |   0.30 |
| -f data/10000x1000x10.journal  register    ||           6.27 |         2.77 |   2.80 |
| -f data/10000x1000x10.journal  register aa ||           3.30 |         1.62 |   0.21 |
| -f data/100x100x10.journal     print       ||           0.06 |         0.05 |   0.01 |
| -f data/1000x1000x10.journal   print       ||           0.42 |         0.25 |   0.04 |
| -f data/10000x1000x10.journal  print       ||           3.95 |         2.57 |   0.38 |
| -f data/10000x1000x10.journal  print aa    ||           3.23 |         1.56 |   0.14 |
| -f data/100x100x10.journal     stat        ||           0.04 |         0.03 |   0.01 |
| -f data/1000x1000x10.journal   stat        ||           0.35 |         0.24 |   0.03 |
| -f data/10000x1000x10.journal  stat        ||          14.84 |        13.29 |   0.20 |
| -f data/10000x1000x10.journal  stat aa     ||          12.08 |        10.16 |   0.17 |

Journal format:

  • detect decimal point and digit groups more robustly (#196)
  • check that transaction dates are followed by whitespace or newline
  • check that dates use a consistent separator character
  • balance assertions now are specific to a single commodity, like Ledger (#195)
  • support multi-line comments using "comment", "end comment" directives, like Ledger

CSV format:

  • fix: reading CSV data from stdin now works better
  • the original order of same-day transactions is now usually preserved (if the records appear to be in reverse date order, we reverse them before finally sorting by transaction date)
  • the rules file include directive is now relative to the current file's directory (#198)
  • CSV output is now built in to the balance, print, and register commands, controlled by -O/--output-format (and -o/--output-file, see below). This means that hledger data can be easily exported, eg for spreadsheet reporting or to migrate to a different tool.


  • the --width and --debug options now require their argument (#149)
  • when an option is repeated, the last value takes precedence (#219). This is helpful eg for customising your reporting command aliases on the fly.
  • smart dates (used in -p/-b/-e/date:/date2:) now must use a consistent separator character, and must be parseable to the end
  • output destination and format selection is now built in to the balance, print and register commands, controlled by -o/--output-file and -O/--output-format options. Notes: -o - means stdout. An output file name suffix matching a supported format will also set the output format, unless overridden by --output-format. Commands' supported output formats are listed in their command-line help. Two formats are currently available: txt (the default) and csv.
  • balance assertions can be disabled with --ignore-assertions

Account aliases:

  • all matching account aliases are now applied, not just one directive and one option
  • account aliases now match by case insensitive regular expressions matching anywhere in the account name
  • account aliases can replace multiple occurrences of the pattern within an account name
  • an account alias replacement pattern can reference matched groups with \N


  • date:/date2: with a malformed date now reports an error instead of being ignored
  • amt: now supports >= or <=
  • clarify status: docs and behaviour; "*" is no longer a synonym for "1" (fixes #227)


  • fix: in tree mode, --drop is ignored instead of showing empty account names
  • a depth limit of 0 now shows summary items with account name "...", instead of an empty report (#206)
  • in multicolumn balance reports, -E now also shows posting-less accounts with a non-zero balance during the period (in addition to showing leading & trailing empty columns)
  • in multicolumn reports, multi-commodity amounts are rendered on one line for better layout (#186)
  • multicolumn reports' title now includes the report span


  • runs faster with large output
  • supports date2:, and date:/date2: combined with --date2, better (fixes #201, #221, #222)
  • a depth limit of 0 now shows summary items (see balance)
  • -A/--average now implies -E/--empty
  • postings with multi-commodity amounts are now top-aligned, like Ledger

User-visible changes in hledger-web since 0.23.3:


  • fix: add missing hs/js files to package
  • the web UI has been streamlined, dropping the raw and entries views and the edit form
  • the help dialog has been improved
  • keyboard shortcuts are now available
  • the sidebar can be toggled open or closed (press s)

Journal view:

  • layout tweaks for less truncation of descriptions and account names

Register view:

  • fix: don't show all zero amounts when searching by account within an account register view
  • chart improvements: show zero balances with correct commodity; show accurate balance at all dates; show transaction events & tooltips; show zero/today lines & background colors

Add form:

  • parses data more strictly and gives better errors (eg #194)
  • allows any number of postings, not just two
  • after adding a transaction, goes back to the journal
  • keyboard shortcut (a) allows quick access


  • allow warp 3*, wai-handler-launch 3*
  • require yesod 1.4* (fixes #212)
  • js updated (jquery, bootstrap, flot), added (typeahead, cookie, hotkeys), removed (select2)

API-ish changes in hledger-lib since 0.23.3:

  • fix combineJournalUpdates folding order
  • fix a regexReplaceCI bug
  • fix a splitAtElement bug with adjacent separators
  • mostly replace slow regexpr with regex-tdfa (fixes #189)
  • use the modern Text.Parsec API
  • allow transformers 0.4*
  • regexReplace now supports backreferences
  • Transactions now remember their parse location in the journal file
  • export Regexp types, disambiguate CsvReader's similarly-named type
  • export failIfInvalidMonth/Day (closes #216)
  • track the commodity of zero amounts when possible (useful eg for hledger-web's multi-commodity charts)
  • show posting dates in debug output
  • more debug helpers
2014/9/12 hledger-web 0.23.3
  • remove warp, wai-handler-launch upper bounds (fixes #205)
2014/9/12 hledger 0.23.3
  • allow text 1.2+ (fixes #207)
2014/5/8 hledger 0.23.2
  • register: also fix date sorting of postings (#184)
2014/5/7 hledger 0.23.1
  • register: fix a refactoring-related regression that the tests missed: if transactions were not ordered by date in the journal, register could include postings before the report start date in the output. (#184)
  • add: don't apply a default commodity to amounts on entry (#138)
  • cli: options before the add-on command name are now also passed to it (#182)
  • csv: allow the first name in a fields list to be empty (#178)
  • csv: don't validate fields count in skipped lines (#177)

2014/5/1 hledger 0.23

command-line fixes and polish, a new accounts command, and a number of changes to the balance command relating to --depth, --flat, and multicolumn mode, which I find has made it much more useful. mail

Changes since 0.22.2:

Journal format:

  • A # (hash) in column 0 is now also supported for starting a top-level journal comment, like Ledger.
  • The "too many missing amounts" error now reminds about the 2-space rule.
  • Fix: . (period) is no longer parsed as a valid amount.
  • Fix: default commodity directives no longer limit the maximum display precision (#169).
  • Fix: + before an amount is no longer parsed as part of the commodity (#181).


  • Command-line help cleanups, layout improvements.
  • Descriptions are shown for known add-ons in the command list.
  • Command aliases have been simplified.
  • Add-ons can now have any of these file extensions: none, hs, lhs, pl, py, rb, rkt, sh, bat, com, exe.
  • Add-ons are displayed without their file extensions when possible.
  • Add-ons with the same name as a built-in command or alias are ignored.
  • Fix: add-on detection and invocation now works on windows.
  • Fix: add-ons with digits in the name are now found.
  • Fix: add-on arguments containing a single quote now work.
  • Fix: when -- is used to hide add-on options from the main program, it is no longer passed through as an add-on argument.


  • An accounts command has been added, similar to Ledger's, for listing account names in flat or hierarchical mode.


  • Tab completion now works at all prompts, and will insert the default if the input area is empty.
  • Account and amount defaults are more robust and useful.
  • Transactions may also be completed by the enter key, when there are no more default postings.
  • Input prompts are displayed in a different colour when supported.


  • Balance reports in flat mode now always show exclusive (subaccount-excluding) balances.
  • Balance reports in flat mode with --depth now aggregate deeper accounts at the depth limit instead of excluding them.
  • Multicolumn reports in flat mode now support --drop.
  • Multicolumn balance reports can now show the account hierarchy with --tree.
  • Multicolumn report start/end dates are adjusted to encompass the displayed report periods, so the first and last periods are "full" and comparable to the others.
  • Fix: zero-balance leaf accounts below a non-zero-balance parent are no longer always shown (#170).
  • Fix: multicolumn reports now support --date2 (cf #174).

balancesheet, cashflow, incomestatement:

  • These commands now support --flat and --drop.


  • Tag queries (tag:) will now match a transaction if any of its postings match.


  • The --display option has been dropped. To see an accurate running total which includes the prior starting balance, use --historical/-H (like balance).
  • With a report interval, report start/end dates are adjusted to encompass the displayed periods, so the first and last periods are "full" and comparable to the others.
  • Fix: --date2 now works with report intervals (fixes #174).


  • The currency/commodity query prefix (sym:) has been renamed to cur:.
  • Currency/commodity queries are applied more strongly in register and balance reports, filtering out unwanted currencies entirely. Eg hledger balance cur:'$' now reports only the dollar amounts even if there are multi-currency transactions or postings.
  • Amount queries like amt:N, amt:N, where N is not 0, now do an unsigned comparison of the amount and N. That is, they compare the absolute magnitude. To do a signed comparison instead, write N with its sign (eg amt:+N, amt:<+N, amt:>-N).
  • Fix: amount queries no longer give false positives on multi-commodity amounts.


  • Default report dates now derive from the secondary dates when --date2 is in effect.
  • Default report dates now notice any posting dates outside the transaction dates' span.
  • Debug output improvements.
  • New add-on example: extra/hledger-rewrite.hs, adds postings to matched entries.
  • Compatible with GHC 7.2 (#155) - GHC 7.8, shakespeare 2

2014/5/1 hledger-web 0.23

Changes since 0.22.8:

  • The --static-root flag has been renamed to --file-url.
  • hledger-web now builds with Cabal's default -O, not -O2, so may be a little quicker/less memory-hungry to install.
2014/4/29 hledger-web 0.22.8
  • allow shakespeare 2.* (#179)
2014/4/17 hledger-web 0.22.7
  • add Peter Simons' patch fixing Data.Conduit.Network HostIPv4 error (#171)
2014/4/16 hledger-web 0.22.6
  • depend on hledger[-lib] 0.22.2
2014/4/16 hledger 0.22.2
  • display years before 1000 with four digits, not three
  • avoid pretty-show to build with GHC < 7.4
  • allow text 1.1, drop data-pprint to build with GHC 7.8.x
2014/4/15 hledger-web 0.22.5
  • allow http-client 0.3.*, fixing cabal install again with GHC <= 7.6 (not yet 7.8)
  • use pretty-show only with GHC 7.4+, fixing GHC 7.2 (fixes #155)
  • allow warp 2.1, fixing cabal install
2014/2/10 hledger-web 0.22.4
  • web: include the right unminified version of jquery.url.js (1.1) to avoid js breakage
2014/2/10 hledger-web 0.22.3
  • web: fix version number reported by --version
2014/2/10 hledger-web 0.22.2


  • web: new option --static-root to set the base url for static files


  • web: include unminified source of all javascript to help packagers (fixes #161)
  • web: work around clang-related build failures with OS X mavericks/XCode 5
  • web: allow blaze-html 0.7 (closes #159)
2014/1/6 hledger 0.22.1
  • require the latest pretty-show so hledger installation no longer needs an upgraded version of happy, and the docs build on hackage

  • require regex-tdfa directly instead of regex-compat-tdfa, simplifying Debian packaging

2013/12/13 hledger 0.22



  • balance: with a reporting interval (monthly, yearly etc.), the balance command will now show a multi-column report, showing either the per-period changes in balance (by default), the period ending balances starting from zero (--cumulative), or the actual period ending balances (--historical). A more detailed specification of the balance command's behaviour has been added to Hledger.Cli.Balance.

  • csv: rules files can now include other rules files, useful for factoring out common rules

  • queries: sym:REGEXP matches commodity symbols

  • register: --average/-A shows a running average, like ledger

  • in period expressions, - (hyphen) can be used as a more compact synonym for from and to. Eg: -p 2012/12/1-2013/2/1 or date:aug-.

  • the add-on script examples in extra/ have been updated; get the hledger source and add .../hledger/extra/ to your PATH to make them available. They include:

    • hledger-accountnames.hs - print account names
    • hledger-balance-csv.hs - print a balance report as CSV
    • hledger-equity.hs - print an entry matching all account balances (like ledger)
    • hledger-print-unique.hs - print only journal entries unique descriptions
    • hledger-register-csv.hs - print a register report as CSV


  • balancesheet: now shows just assets and liabilities, not equity

  • print: comment positions (same line or next line) are now preserved

  • queries: amt now uses the = operator by default, eg amt:50 is equivalent to amt:=50

  • command line processing has been overhauled and made more consistent, and now has tests and debug output. More flags now work both before and after COMMAND: -f, --rule-file, --alias, --help, --debug, --version. Command line help, command aliases, API docs and code have been improved.

  • --debug now takes an optional numeric argument to set the debug level higher than 1, for more verbose debug output in a few cases.


  • csv: CSV data containing non-ascii characters is now supported

  • build with latest versions of dependencies (text, warp, http-conduit etc.)

Release contributors:

Marko Kocić, Max Bolingbroke, and a big welcome to first-time committer John Wiegley! :)

2013/7/10 hledger-web 0.21.3
  • drop yesod-platform dependency, it is not worthwhile. The other yesod dependencies are currently without version ranges, so cabal install might require --constraint to restrict them in some cases.
2013/6/23 hledger 0.21.3
  • csv: fix wrong application of multiple assignments in a conditional block
2013/6/4 hledger 0.21.2
  • web: fix a build failure
2013/6/3 hledger 0.21.1
  • web: show proper Y-values in register chart (fixes #122)
  • web: avoid trailing commas in register chart values, in case of trouble with IE

2013/6/1 hledger 0.21

Bugs fixed:

  • parsing: don't fail when a csv amount has trailing whitespace (fixes #113)
  • web: don't show prices in the accounts sidebar (fixes #114)
  • web: show one line per commodity in charts. Needs more polish, but fixes #109.
  • web: bump yesod-platform dependency to avoid a cabal install failure

Journal reading:

  • balance assertions are now checked after reading a journal

web command:

  • web: support/require yesod 1.2
  • web: show zero-balance accounts in the sidebar (fixes #106)
  • web: use nicer select2 autocomplete widgets in the add form

Documentation and infrastructure:

  • add basic cabal test suites for hledger-lib and hledger
2013/5/4 hledger
  • web: require at least version 1.1.7 of yesod-core to avoid a potential build error
  • Update the bug tracker and source repository links on hackage

2013/5/1 hledger 0.20

Bugs fixed:

  • balance: a 0.19 regression which showed wrong total balance with --flat has been fixed (#94)
  • register: when --date2 is used, the register is now sorted by the secondary date
  • web: some missing static & template files have been added to the package, fixing cabal-dev and hackage builds (#97, #98)
  • web: some hardcoded static urls have been fixed
  • Dependencies and code have been updated to support the latest libraries and GHC versions. For now, hledger requires GHC 7.2+ and hledger-web requires GHC 7.4+.

Journal reading:

  • DOS-style line-endings are now also supported in journal and rules files.
  • ! is now accepted in the status field as well as *, like ledger
  • The actual date and effective date terminology has changed to primary date and secondary date. Use --date2 to select the secondary date for reports. (--aux-date or --effective are also accepted for ledger and backwards compatibility).
  • Per-posting dates are supported, using hledger tags or ledger's posting date syntax
  • Comment and tag handling has been improved

CSV reading:

  • CSV conversion rules have a simpler, more flexible syntax. Existing rules files will need to be updated manually:
    • the filename is now FILE.csv.rules instead of FILE.rules
    • FIELD-field N is now FIELD %N+1 (or set them all at once with a fields rule)
    • base-currency is now currency
    • base-account is now account1
    • account-assigning rules: add if before the list of regexps, add indented account2 before the account name
  • parenthesised amounts are parsed as negative


  • Use code: to match the transaction code (check number) field
  • Use amt: followed by <, = or > and a number N to match amounts by magnitude. Eg amt:<0 or amt:=100. This works only with single-commodity amounts (multi-commodity amounts are always matched).
  • tag: can now match (exact, case sensitive) tag values. Eg tag:TAG=REGEXP.

add comand:

  • Transaction codes and comments (which may contain tags) can now be entered, following a date or amount respectively. (#45)
  • The current entry may be restarted by entering < at any prompt. (#47)
  • Entries are displayed and confirmed before they are written to the journal.
  • Default values may be specified for the first entry by providing them as command line arguments.
  • Miscellaneous UI cleanups

register command:

  • The --related/-r flag shows the other postings in each transaction, like ledger.
  • The --width/-w option increases or sets the output width.

web command:

  • The web command now also starts a browser, and auto-exits when unused, by default ("local ui mode"). With --server, it keeps running and logs requests to the console ("server mode").
  • Bootstrap is now used for styling and layout
  • A favicon is served
  • The search field is wider
  • yesod devel is now supported; it uses $LEDGER_FILE or ~/.hledger.journal
  • the blaze_html_0_5 build flag has been reversed and renamed to blaze_html_0_4


  • The hledger-interest and hledger-irr commands have been released/updated.
  • hledger-chart and hledger-vty remain unmaintained and deprecated.

Documentation and infrastructure:

  • The hledger docs and website have been reorganised and updated
  • Manuals for past releases are provided as well as the latest dev version
  • hledger has moved from darcs and darcs hub to git and github (!)
  • The bug tracker has moved from google code to github
  • Feature requests and project planning are now managed on trello
  • A build bot builds against multiple GHC versions on each commit

Release contributors:

  • Sascha Welter commissioned register enhancements (--related and --width)
  • David Patrick contributed a bounty for add enhancements
  • Joachim Breitner added support for ! in status field
  • Xinruo Sun provided hledger-web build fixes
  • Peter Simons provided hledger-web build fixes, and a build bot
  • Marko Kocić provided hledger-web fixes
2012/11/24 hledger-web 0.19.3
  • web: fix "Prelude.read: no parse" errors with GHC >= 7.6
  • web & lib refactoring

2012/11/16 hledger-web 0.19

  • builds with yesod 1.1.3
  • obeys command-line query options at startup again
  • the autogenerated session file is now a dot file (.hledger-web_client_session.aes)
2012/11/16 hledger 0.19.1
  • 87: fix an arithmetic and transaction balancing bug with multiple total-priced amounts ( @@ PRICE )
  • parsing: ignore ledger-style balance assertions ( = BAL ) and fixed lot price declarations ( {= PRICE} )

2012/10/21 hledger 0.19

a much faster balance command, and support for the latest GHC and libs. mail

  • hledger, hledger-lib: support GHC 7.6 and latest cmdargs, haskeline, split

  • balance report no longer has an O(n^2) slowdown with large numbers of accounts, and is generally more speedy. Benchmark on a 2010 macbook:

    |                                           || hledger-0.18 | hledger-0.19 | ledger |
    | -f data/100x100x10.journal     balance    ||         0.21 |         0.07 |   0.09 |
    | -f data/1000x1000x10.journal   balance    ||        10.13 |         0.47 |   0.62 |
    | -f data/1000x10000x10.journal  balance    ||        40.67 |         0.67 |   1.01 |
    | -f data/10000x1000x10.journal  balance    ||        15.01 |         3.22 |   2.36 |
    | -f data/10000x1000x10.journal  balance aa ||         4.77 |         4.40 |   2.33 |
  • build version is set with CPP instead of cabal-file-th

2012/7/7 hledger 0.18.2

  • web: fix compilation error with -fblaze_html_0_5 flag
  • bump base lower bound to 4.3 to enforce GHC 7 requirement

2012/6/29 hledger 0.18.1

  • register, print: fix reverse ordering of same-day transactions
  • balance: respect all query terms, not just acct
  • combine command-line flags like --depth properly with non-flag query patterns
  • web: don't auto-create a missing journal file at startup
  • stats: list included journal files
  • support tilde (~) in journal and rules file paths
  • expose more utilities from CsvReader
  • remove ensureRulesFile debug trace

2012/5/29 hledger 0.18


  • web: hledger-web is now based on yesod 1.0
  • web: fix js error breaking second use of add form (#72)
  • web: make yesod devel work
  • the command-line now supports a more powerful query language, consistent with the web UI
  • hledger now fully supports tags (aka metadata) on both transactions and postings, and querying by tag or tag value
  • new commands incomestatement, balancesheet, and cashflow provide basic financial statements under certain conditions
  • format conversion is now done on demand, and the convert command has been dropped. So instead of hledger convert FILE.csv just do hledger -f FILE.csv print or any other command. You can also pipe any supported format into hledger -f- CMD and hledger will try to do the right thing.
  • support for GHC 6.12 has been dropped; this release has been tested with GHC 7.0.4, 7.2.2, and 7.4.1
  • unicode is now handled properly on all supported GHC versions
  • API and internal cleanups
2012/3/3 hledger-web 0.17.1
  • set more upper bounds to fix cabal install issues with latest packages

2012/2/1 hledger 0.17

fixes bugs and updates dependencies mail

  • support HP 2011.4.0.0
  • support and require cmdargs 0.9
  • allow non-threaded builds, supporting more debian architectures
  • parsing: give a clearer error when journal file path contains ~
  • parsing: -B/--cost now ignores P historical prices, like ledger
  • parsing: inferred amounts now use the cost commodity if known, like ledger (#69)
  • balance: report differently-priced lots in an account as a single amount, like ledger
  • web: support and require yesod >= 0.9.4
  • web: use the main aeson package again
  • web: fix a regression with dollar signs in hamlet templates
  • web: add form allowed blank account names (#81)
  • chart, vty: hledger-chart and hledger-vty demoted to non-maintained extras for now
2011/10/26 hledger-web 0.16.5
  • web: fix a ghc 6.12 incompatibility in Settings.hs
2011/10/24 hledger-web 0.16.4
  • web: yet another cabal install fix, fix AppConfig name clash
2011/10/4 hledger-web 0.16.3
  • web: another cabal install fix, disable favicon.ico since it's not easily embeddable
2011/10/4 hledger-web 0.16.2
  • web: more cabal install fixes (remove bad path, add routes and models) (#63)
2011/10/4 hledger 0.16.1
  • parsing: show correct line number for posting parse errors (#67)
  • web: declare static files as extra-source-files to fix cabal install (#63)
  • web: add a threaded flag for debian (#68)
  • web: fewer build warnings by default

2011/10/1 hledger 0.16

a stability/bugfix/polish release (which may become the pattern for even-numbered releases in future.) mail

  • cli: strip the -- when calling add-on commands, so their options work (#64)
  • cli: hledger ADDON --version now shows add-on command's version
  • cli: only the add and web commands auto-create the journal file
  • cli: give a non-confusing error if LEDGER_FILE contains a literal tilde
  • add: clearer prompts, more validation, use . to end also
  • add: use unix line endings consistently, avoiding parse error on windows (#51)
  • add: avoid excess whitespace between transactions (#46)
  • balance: ledger compatibility fix: don't elide parent accounts with multiple displayed subaccounts
  • convert: always order converted transactions by date
  • convert: rename currency -> base-currency, in-field, out-field -> amount-in-field, amount-out-field
  • convert: give an error, not a zero when date or amount-in-field/amount-out-field parsing fails
  • register: show more useful range of intervals with --empty and a query pattern
  • print, web: always show both dates, ignoring --effective (#42)
  • web: production builds (the default with cabal) have all web content embedded (dev builds use ./static/) (#63)
  • web: update to yesod 0.9
  • web: obey at least some of the general reporting options, like --cost
  • web: adjust the default base url when a custom port is specified
  • web: prevent an infinite redirect when custom base url has a trailing slash
  • web: fix "not:'multi word'" patterns
  • web: hide old title and search form when adding/editing
  • web: adjust --help to indicate command-line arguments are not expected
  • web: don't bother running cli unit tests at startup
2011/9/12 hledger 0.15.2, hledger-web 0.15.3
  • handle multiple filter patterns on the command-line again
  • don't pass an add-on command's name to it as an extra argument
  • don't give a confusing error with -f and no command
  • fix a regression balancing a transaction containing different prices
  • web: fix journal edit form
  • web: fix wrong transaction amount in account register with virtual postings
  • web: fix some invalid html
2011/9/2 hledger 0.15.1, hledger-web 0.15.2
  • fix a parsec 2 incompatibility
  • web: add missing Hledger.Web.Options to cabal file
  • web: tighten up dependencies to reduce build problems

2011/9/1 hledger 0.15


  • hledger's options are now modal, providing better help (using cmdargs)
  • hledger now lists and runs any hledger-* add-ons found in the user's path
  • case insensitivity of filter patterns has been fixed
  • parsing: alias/end aliases directives, for renaming accounts, are supported, like ledger's but a bit more powerful; also an --alias option for renaming on the fly
  • parsing: the account directive now preserves posting type (normal/virtual/balanced virtual)
  • parsing: the pop directive is supported as an alias for end tag, like ledger
  • parsing: P (historical price) directives can contain a (ignored) numeric time zone, like ledger
  • parsing: the leading ! in directives is now optional and deprecated, like ledger
  • parsing: entries with a negative amount in the first posting now infer the correct balancing amount
  • parsing: bad date checking is more accurate
  • balance: collapsing of boring accounts to one line can be disabled with --no-elide
  • balance: fix a wrong precision regression from last release
  • convert: standard input can be converted
  • convert: an alternate rules file can be specified with --rules
  • convert: account2-field can be used when the CSV file specifies both accounts
  • convert: description-field can have a custom format and combine multiple CSV fields
  • convert: in-field and out-field support CSV files that use two amount columns
  • convert: don't fail when there's no default journal file
  • web: the web interface has been overhauled/cleaned up
  • web: account register views are now transaction-based, like gnucash etc., and show accurate historical balances when possible
  • web: simple balance charts are displayed (using flot)
  • web: more expressive and consistent search patterns, using a new matching engine
  • web: add form uses currently focussed account as default, redirects to itself, formats status messages better
  • web: sidebar now shows empty/boring accounts too
  • web: now uses warp and a newer yesod
  • api simplifications
  • importable Hledger, Hledger.Web, Hledger.Vty and Hledger.Chart modules
  • the basic reports are now provided by hledger-lib for easier reuse
  • new api use examples: equity.hs, uniquify.hs
  • some old base 3 support has been dropped
  • the old -s flag has been dropped

2011/4/22 hledger 0.14


  • remove the specific process dependency that caused too many cabal install problems
  • treat arguments as possibly-encoded platform strings, do not assume UTF-8
  • hledger now always reads and writes data as UTF-8, ignoring the system locale (#34)
  • look at the LEDGER_FILE env var for the journal path, otherwise LEDGER, like ledger
  • handle a blank LEDGER_FILE or LEDGER value more gracefully (use the default file path)
  • the default journal file path is now ~/.hledger.journal, to avoid breaking mac filevault (#41)
  • amounts with different prices are now aggregated, like ledger
  • zero amounts now have no sign or commodity, like ledger
  • parsing: assume current year when transaction dates have no year and there is no default year
  • parsing: more careful validation of eg leap years in transaction dates
  • parsing: better international number format support, allowing comma as decimal point and flexible digit groups (#32)
  • parsing: support @@ syntax specifying total price
  • parsing: infer the conversion price in transactions involving two unpriced commodities
  • parsing: support per-posting cleared status
  • parsing: more reporting interval syntax: biweekly, bimonthly, every N days/weeks/months/quarters/years, every Nst/nd/rd/th day of month/week
  • add: avoid offering account names for completion in inappropriate contexts
  • add: remember default account even if user submits a different amount.
  • convert: account-field directive specifies a field containing the base account name
  • convert: effective-date-field directive specifies a field containing the effective date
  • convert: date-format directive specifies custom date formats
  • convert: allow amount fields containing "AMT @@ PRICE"
  • histogram: honour the specified start or end dates
  • print: don't show a trailing space when description is blank
  • web: allow filter patterns with spaces if quoted, like command line
  • web: make edit form more cross-browser compatible, fixing it in firefox (#38)
  • web: move hidden add/edit/import forms below main content to help text-mode browsers a bit (#33)

Release contributors: Simon Michael, Dmitry Astapov, Eric Kow, Max Bolingbroke, Omari Norman. Stats: 137 days, 113 commits, 11 end-user features and 15 end-user bugfixes since last release. 189 unit & functional tests and 59% unit test coverage (hledger, hledger-lib packages). 5540 lines of code (all packages).

2010/12/6 hledger 0.13

readline editing and tab completion from Judah Jacobson, more ledger compatibility, a more robust and installable web interface, bugfixes, and a much-deliberated package split. mail

  • move web, vty, chart commands into separate hledger-web, hledger-vty, hledger-chart packages. This both simplifies (no more build flags) and complicates (more room for dependency hassles), but I hope overall it will be easier and more scalable.
  • all packages but chart are now marked "beta", ie "not finished but suitable for everyday use"
  • parsing: ledger compatibility: support D default commodity directive
  • parsing: ledger compatibility: ignore metadata tags on transactions and postings
  • parsing: ledger compatibility: ignore cleared flags at the start of postings
  • parsing: ledger compatibility: ignore C commodity conversion directives
  • parsing: price precisions no longer affect commodities' display precisions
  • add: readline-style editing
  • add: tab-completion for account names
  • add: add the default commodity, if any, to commodity-less amounts (#26)
  • add: misc. commodity/precision/defaults-related bugfixes
  • chart: give a meaningful error message for empty journals
  • chart: update for current Chart lib (0.14)
  • web: support files now live in ./.hledger/web/ and will be auto-created at startup
  • web: page layout is more robust with wide content
  • web: allow editing of included files
  • web: handle multiple filter patterns correctly
  • web: allow single- or double-quoted filter patterns containing spaces
  • web: update for current yesod lib (0.6.*)
  • transaction balancing is now based on display precision (#23)
  • briefer, more informative usage error messages
2010/9/6 hledger 0.12.1


  • web: fix account filtering breakage
  • installing: tighten up utf8-string dependency

2010/9/5 hledger 0.12

  • web: new, better web ui; accounts are now a permanent sidebar; add form uses auto-completing combo fields
  • installing: fix a build error with parsec 3 (#22)
  • installing: require exactly matching hledger-lib version for more robust builds
  • installing: explicit data-object dependency to ensure hledger and hledger-lib use the same time version
  • installing: explicit hamlet dependency for more robust building
  • installing: build threaded and with warnings
  • installing: drop -fweb610 flag
  • installing: add gtk2hs-buildtools dependency needed to build with -fchart
  • installing: require cabal 1.6 or greater
  • add -D/--daily flag
  • register: with --depth, clip account names or aggregate postings rather than excluding them
  • fix !include with deeply nested directories (#21)
  • fix obscured date parse errors with parsec 3
  • handle unicode better in errors
  • fix a ghc 6.12.3 error when running interpreted

Stats: 50 days and 90 commits since last release, now at 5741 lines of code with 136 tests and 41% unit test coverage.

2010/07/17 hledger 0.11.1
  • fix --version output

2010/07/17 hledger 0.11


  • split --help, adding --help-options and --help-all/-H, and make it the default command
  • use "journal" instead of "ledger file"; default suffix is .journal, default file is ~/.journal
  • auto-create missing journal files rather than giving an error
  • new format-detecting file reader (mixed journal transactions and timelog entries are no longer supported)
  • work around for first real-world rounding issue (test zero to 8 decimal places instead of 10)
  • when reporting a balancing error, convert the error amount to cost
  • parsing: support double-quoted commodity symbols, containing anything but a newline or double quote
  • parsing: allow minus sign before commodity symbol as well as after (also fixes a convert bug)
  • parsing: fix wrong parse error locations within postings
  • parsing: don't let trailing whitespace in a timelog description mess up account names
  • add: allow blank descriptions
  • balance: --flat provides a simple non-hierarchical format
  • balance: --drop removes leading account name components from a --flat report
  • print, register, balance: fix layout issues with mixed-commodity amounts
  • print: display non-simple commodity names with double-quotes
  • stats: layout tweaks, add payee/description count
  • stats: don't break on an empty file
  • stats: -p/--period support; a reporting interval generates multiple reports
  • test: drop verbose test runner and testpack dependency
  • web: a new web ui based on yesod, requires ghc 6.12; old ghc 6.10-compatible version remains as -fweb610
  • web: allow wiki-like journal editing
  • web: warn and keep running if reloading the journal gives an error
  • web: --port and --base-url options set the webserver's tcp port and base url
  • web: slightly better browser opening on microsoft windows, should find a standard firefox install now
  • web: in a web-enabled build on microsoft windows, run the web ui by default

Stats: 55 days and 136 commits since last release. Now at 5552 lines of code with 132 tests and 54% unit test coverage.

2010/05/23 hledger 0.10

installation and bug fixes and api improvements mail

  • fix too-loose testpack dependency, missing safe dependency
  • fix ghc 6.12 compatibility with -fweb
  • fix handling of non-ascii arguments with ghc 6.12
  • fix "0.8" in --version output
  • fix an occasional stack overflow error due to infinite recursion in Posting/Transaction equality tests
  • the -fwebhappstack build flag is gone for now, to avoid a cabal problem
  • parsing: if there is no description, don't require a space after the transaction date
  • parsing: balance balanced-virtual postings separately, allow them to have an implicit amount
  • parsing: timelog entries now generate balanced transactions, using virtual postings
  • parsing: simpler high-level parse error message
  • parsing: clearer bad date errors
  • add: fix wrongful program exit on bad dates
  • print: negative account patterns now exclude transactions containing any posting to a matched account
  • vty: rename the ui command to vty for consistency
  • vty: fix restricted account scope when backing up to top level
  • web: fix non-ascii handling with ghc 6.12
  • web: fix a bug possibly affecting reload-on-change
  • consolidate module namespace under Hledger, api cleanups

Stats: 44 days, 81 commits since last release. Now at 4904 lines of code including tests, 144 tests, 53% coverage.

2010/04/10 hledger 0.9

many bugfixes and small improvements, GHC 6.12 support, and a separate library package to make building (h)ledger-compatible tools easier. mail

  • ghc 6.12 support
  • split off hledger-lib package, containing core types & utils
  • parsing: ignore D, C, N, tag, end tag directives; we should now accept any ledger 2.6 file
  • parsing: allow numbers in commodities if double-quoted, like ledger
  • parsing: allow transactions with empty descriptions
  • parsing: show a better error for illegal month/day numbers in dates
  • parsing: don't ignore trailing junk in a smart date, eg in web add form
  • parsing: don't ignore unparsed text following an amount
  • parsing: @ was being treated as a currency symbol
  • add: fix precision handling in default amounts (#19)
  • add: elide last amount in added transactions
  • convert: keep original description by default, allow backreferences in replace pattern
  • convert: basic csv file checking, warn instead of dying when it looks wrong
  • convert: allow blank/comment lines at end of rules file
  • print: always show zero amounts as 0, hiding any commodity/decimal places/price, like ledger
  • register: fix bad layout with years < 1000
  • register: fix a Prelude.head error with reporting interval, --empty, and --depth
  • register: fix a regression, register should not show posting comments
  • register: with --empty, intervals should continue to ends of the specified period
  • stats: better output when last transaction is in the future
  • stats: show commodity symbols, account tree depth, reorder slightly
  • web: -fweb now builds with simpleserver; to get happstack, use -fwebhappstack instead
  • web: pre-fill the add form with today's date
  • web: help links, better search form wording
  • web: show a proper error for a bad date in add form (#17)
  • web: fix for unicode search form values
  • web: fix stack overflow caused by regexpr, and handle requests faster (#14)
  • web: look for more-generic browser executables
  • web: more robust browser starting (#6)
  • error message cleanups
  • more tests, refactoring, docs

Stats: 58 days, 2 contributors, 102 commits since last release. Now at 3983 lines of non-test code, 139 tests, 53% coverage.

2010/02/11 hledger 0.8

Bug fixes, refactoring and Hi-Res Graphical Charts. mail

  • parsing: in date=date2, use first date's year as a default for the second
  • add: ctrl-d doesn't work on windows, suggest ctrl-c instead
  • add: --no-new-accounts option disallows new accounts (Roman Cheplyaka)
  • add: re-use the previous transaction's date as default (Roman Cheplyaka)
  • add: a command-line argument now filters by account during history matching (Roman Cheplyaka)
  • chart: new command, generates balances pie chart (requires -fchart flag, gtk2hs) (Roman Cheplyaka, Simon Michael)
  • register: make reporting intervals honour a display expression (#18)
  • web: fix help link
  • web: use today as default when adding with a blank date
  • web: re-enable account/period fields, they seem to be fixed, along with file re-reading (#16)
  • web: get static files from the cabal data dir, or the current dir when using make (#13)
  • web: preserve encoding during add, assuming it's utf-8 (#15)
  • fix some non-utf8-aware file handling (#15)
  • filter ledger again for each command, not just once at program start
  • refactoring, clearer data types

Stats: 62 days, 2 contributors, 76 commits since last release. Now at 3464 lines of non-test code, 97 tests, 53% test coverage.

2009/12/11 hledger 0.7


  • price history support (first cut): P directives now work, though differently from ledger. Each posting amount takes its fixed unit price from the price history (or @) when available. This is simple and useful for things like foreign currency expenses (but not investment tracking). Like ledger, balance and register don't show amount prices any more, and don't separate differently-priced amounts. Unlike ledger, print shows all amount prices, and supports -B.
  • --effective option, will use transactions' effective dates if any
  • convert: new rules file format, find/create rules file automatically, more robust parsing, more useful --debug output
  • print: always sort by date, fix long account name truncation, align amounts, show end of line comments, show all amounts for clarity (don't elide the final balancing amount)
  • ui: use vty 4, fixes non-ascii and gnome terminal problems (issues #3, #4)
  • web: allow data entry, react to data file changes, better layout, help links, remove histogram command and filter fields for now, fix bad localhost redirect, filter form did not work in eg firefox (issue #7), reset link did not work in all browsers
  • parsing: require whitespace between date and status code, allow (and ignore) a time in price records, better error messages, non-zero exit code on parse failure
  • display non-ascii error messages properly (issue #5)
  • fix an arithmetic bug that occasionally rejected valid transactions
  • fix a regex bug in showtree
  • don't break if HOME is undefined
  • --debug now implies --verbose
  • add functional tests like ledger's, use test-framework for speedy running, release shelltestrunner as a separate package
  • many hlint cleanups (Marko Kocić)
  • many site and documentation updates

Stats: 60 days, 1 contributor, 50 commits since last release. Now at 3377 lines of non-test code, 97 tests, 53% test coverage.

2009/06/22 hledger 0.6.1
  • avoid use of exitSuccess which was breaking ghc 6.8/base 3 compatibility (issue #2)

2009/06/13 hledger 0.6

Some pre-built binaries are now available. cabal install works on gnu/linux, mac and windows. Hurrah! mail

  • now cabal-installable on unix, mac, and windows, with Haskell Platform
  • provide experimental platform binaries
  • parsing: fix a silly failure to open ledger file paths containing ~
  • parsing: show better errors for unbalanced transaction and missing default year
  • parsing: allow parentheses and brackets inside account names, as ledger does
  • parsing: fail on empty account name components, don't just ignore
  • add: description passed as arguments now affects first transaction only
  • add: better handling of virtual postings and default amounts
  • print, register: show virtual accounts bracketed/parenthesised
  • web: improved web ui supporting full patterns & period expressions
  • new "stats" command reports some ledger statistics
  • many dev/doc/deployment infrastructure improvements
  • move website into darcs repo, update home page
  • move issue tracker to google code

Release stats:

  • Contributors: Simon Michael
  • Days since last release: 21
  • Commits: 94
  • Lines of non-test code: 2865
  • Tests: 82
  • Test coverage: 53% expressions
  • Known errors: 3 (inconsistent eliding, vty-related failures)
  • Performance: similar (http://hledger.org/profs/200906131120.bench)
2009/05/23 hledger 0.5.1
  • two fixes: really disable vty flag by default, and include ConvertCommand in cabal file

2009/05/23 hledger 0.5


  • the vty flag is disabled by default again, to ease installation on windows
  • use ledger 3 terminology: a ledger contains transactions which contain postings
  • new "add" command prompts for transactions interactively and adds them to the ledger
  • new "convert" command transforms bank CSV exports to ledger format, with rule-based cleanup
  • new "histogram" command shows transaction counts per day or other reporting interval
  • most commands now work properly with UTF8-encoded text (Sergey Astanin)
  • invoking as "hours" is now less different: it just uses your timelog, not your ledger
  • --quarterly/-Q option summarises by quarter
  • --uncleared/-U option looks only at uncleared transactions
  • be more accurate about checking balanced amounts, don't rely on display precision
  • enforce balancing for bracketed virtual postings
  • fix bug in eliding of posting amounts
  • don't show trailing spaces on amountless postings
  • parse null input as an empty ledger
  • don't treat comments as part of transaction descriptions
  • require some postings in ledger transactions
  • require a non-empty description in ledger transactions
  • don't fail when matching an empty pattern, as in "not:"
  • make the web server handle the null path
  • code, api and documentation updates
  • add a contributor agreement/list

Release stats:

  • Contributors: Simon Michael, Sergey Astanin
  • Days since last release: 51
  • Commits: 101
  • Lines of non-test code: 2795
  • Tests: 76
  • Known errors: 0

2009/04/03 hledger 0.4

There is also a new website at hledger.org, with screenshots (textual!), a demo (will it survive!?), and docs (not too many!) ... I wrote it because I did not want to hack on c++ and because haskell seemed a good fit ... new happstack-based web interface. mail

  • new "web" command serves reports in a web browser (install with -f happs to build this)
  • make the vty-based curses ui a cabal build option, which will be ignored on MS windows
  • drop the --options-anywhere flag, that is now the default
  • patterns now use not: and desc: prefixes instead of ^ and ^^
  • patterns are now case-insensitive, like ledger
  • !include directives are now relative to the including file (Tim Docker)
  • "Y2009" default year directives are now supported, allowing m/d dates in ledger
  • individual transactions now have a cleared status
  • unbalanced entries now cause a proper warning
  • balance report now passes all ledger compatibility tests
  • balance report now shows subtotals by default, like ledger 3
  • balance report shows the final zero total when -E is used
  • balance report hides the final total when --no-total is used
  • --depth affects print and register reports (aggregating with a reporting interval, filtering otherwise)
  • register report sorts transactions by date
  • register report shows zero-amount transactions when -E is used
  • provide more convenient timelog querying when invoked as "hours"
  • multi-day timelog sessions are split at midnight
  • unterminated timelog sessions are now counted. Accurate time reports at last!
  • the test command gives better --verbose output
  • --version gives more detailed version numbers including patchlevel for dev builds
  • new make targets include: ghci, haddocktest, doctest, unittest, view-api-docs
  • a doctest-style framework for functional/shell tests has been added

Release stats:

  • Contributors: Simon Michael, Tim Docker; thanks to the HAppS, happstack and testpack developers
  • Days since release: 76
  • Commits: 144
  • Lines of non-test code: 2367
  • Tests: 56
  • Known errors: 0

2009/01/17 hledger 0.3


  • count timelog sessions on the day they end, like ledger, for now
  • when options are repeated, use the last instead of the first
  • builds with ghc 6.10 as well as 6.8
  • a simple ui for interactive report browsing: hledger ui
  • accept smart dates everywhere (YYYYMMDD, Y/M/D, Y, M/D, D, jan, today, last week etc.)
  • --period/-p flag accepting period expressions like "in 2008", "weekly from last month"..
  • -W/-M/-Y convenience flags to summarise register weekly, monthly, yearly
  • --depth and -E flags also affect summarised register reports (including depth=0)
  • --display/-d flag supporting date predicates (like "d<[DATE]", "d>=[DATE]")
  • !include directive to include additional ledger files
  • !account directive to set a default parent account
  • Added support for reading historical prices from files
  • timelog and ledger entries can be intermixed in one file
  • modifier and periodic entries can appear anywhere (but are still ignored)
  • help and readme improvements
  • runs much faster than 0.2

Release stats:

  • Contributors: Simon Michael, Nick Ingolia, Tim Docker; thanks to Corey O'Connor & the vty team
  • Lines of non-test code: 2123
  • Tests: 58
  • Known errors: 1

2008/11/23 hledger 0.2


  • fix balance report totals when filtering by account
  • fix balance report selection of accounts when filtering by account
  • fix a bug with account name eliding in balance report
  • if we happen to be showing a not-yet-auto-balanced entry, hide the AUTO marker
  • fix print command filtering by account
  • omit transactions with zero amount from register report
  • Fix bug in parsing of timelogs
  • rename --showsubs to --subtotal, like ledger
  • drop --usage flag
  • don't require quickcheck
  • priced amounts (eg "10h @ $50") and --basis/--cost/-B flag to show them with cost basis
  • easy --depth option, equivalent to ledger's -d 'l<=N'
  • smarter y/m/d date parsing for -b and -e (any number of digits, month and day default to 1, separator can be / - or .)
  • -n flag for balance command
  • --empty/-E flag
  • build a library, as well as the exe
  • new home page url (http://joyful.com/hledger)
  • publish html and pdf versions of README
  • detect display preferences for each commodity like ledger
  • support amounts with multiple currencies/commodities
  • support --real/-R flag
  • support -C/--cleared flag to filter by entry status (not transaction status)
  • support virtual and balanced virtual transactions
  • parse comment lines beginning with a space, as from M-; in emacs ledger-mode
  • allow any non-whitespace in account names, perhaps avoiding misleading missing amounts errors
  • clearer error message when we can't balance an entry
  • when we fail because of more than one missing amount in an entry, show the full entry
  • document the built-in test runner in --help
  • add a --verbose/-v flag, use it to show more test-running detail

Release stats:

  • Contributors: Simon Michael, Tim Docker
  • Lines of non-test code: 1350
  • Tests: 43
  • Known errors: 0

2008/10/15 hledger 0.1

mail I'm pleased to announce the first release of hledger, a command-line accounting tool similar to John Wiegley's c++ ledger. hledger generates simple ledger-compatible transaction & account balance reports from a plain text ledger file. It's simple to use, at least for techies. This has been my "learning Haskell" project, but I think it's also useful. It is much less featureful than ledger, and not quite as fast, but it has the virtue of being fun for haskellers to hack on. I am documenting the code, the app is simple, and I'm not too far up the haskell learning curve, so I think other people learning haskell might enjoy a look. It is currently ~1100 lines of haskell excluding tests. My thanks to John Wiegley for help with compatibility and for his very useful ledger tool. I use it (and now, both of them) daily to track time and money. This is of course a hot topic around our planet. I hope you find it useful or intriguing.

Release stats:

  • Contributors: Simon Michael

hledger Quick Start

Welcome! This hledger intro aims to distill just the most needed practical info to help you get productive as quickly as possible. When you want more detail, follow links to the full website (and particularly the hledger manual).

What is it ?

hledger: free GPLv3+ accounting software for linux, mac, windows, web, etc.

How do I use it ?

At the start:

  1. Install one or more of the hledger tools
  2. Set up a journal, and maybe version control

On a regular basis (eg daily, can be <5m):

  1. Enter transactions manually and/or
  2. Import transactions from banks' CSV
  3. Reconcile to catch mistakes

Whenever you like:

  1. Run reports to answer questions and gain insight
  2. Refine account names, CSV rules etc. to improve your reports and efficiency.

Knowing some double entry accounting will help you get the most from hledger, but you can do fine just by following the examples below. You'll find your bookkeeping/accounting skills improve naturally (and help is available).


Fastest: download binaries, eg one of:

$ apt install hledger hledger-ui hledger-web
$ brew install hledger
$ curl -LO https://github.com/simonmichael/hledger/releases/download/1.21/hledger-ubuntu.zip; unzip hledger-ubuntu.zip  # also macos, windows, etc.
$ dnf install hledger
$ docker pull dastapov/hledger
$ make -C /usr/ports/openbsd-wip/productivity/hledger install
$ nix-env -f https://github.com/NixOS/nixpkgs/archive/915ef210.tar.gz -iA hledger hledger-ui hledger-web
$ pacman -S hledger hledger-ui hledger-web
$ sudo layman -a haskell && sudo emerge hledger hledger-ui hledger-web
$ xbps-install -S hledger hledger-ui hledger-web

Freshest: build from source:

  1. $ apt install libtinfo-dev or equivalent
  2. check UTF-8 locale
  3. then one of:
    $ curl -sO https://raw.githubusercontent.com/simonmichael/hledger/master/hledger-install/hledger-install.sh; bash hledger-install.sh
    $ stack update; stack install --resolver=lts-17 hledger-lib-1.21 hledger-1.21 hledger-ui-1.21 hledger-web-1.21 --silent
    $ cabal update; cabal install alex happy; cabal install hledger-1.21 hledger-ui-1.21 hledger-web-1.21
    $ git clone https://github.com/simonmichael/hledger; cd hledger; stack install  # super fresh

Set up a journal

The journal file is a plain text file where transactions are recorded. By default it is ~/.hledger.journal, and the add command or web add form described below will create it automatically, so actually you don't need to do anything here.

But here are some common changes people make sooner or later, so why not now:

  • A dedicated folder, to consolidate financial files and make version control and backups easier:

    $ mkdir ~/finance
    $ cd ~/finance
  • A separate journal file for each year, for performance and data compartmentalisation:

    $ touch 2021.journal
  • A LEDGER_FILE environment variable, so you won't have to type "-f ~/finance/2021.journal" with every command:

    $ echo "export LEDGER_FILE=~/finance/2021.journal" >> ~/.bashrc
    $ source ~/.bashrc

    Or if environment variables annoy you, symbolic-link the file to ~/.hledger.journal:

    $ ln -s ~/finance/2021.journal ~/.hledger.journal
  • Some optional directives, useful especially with non-english account names:

    $ cat > 2021.journal
    ; Declare top level accounts, setting their types and display order;
    ; Replace these account names with yours; it helps commands like bs and is detect them.
    account assets      ; type:A, things I own
    account liabilities ; type:L, things I owe
    account equity      ; type:E, net worth or "total investment"; equal to A - L
    account revenues    ; type:R, inflow categories; part of E, separated for reporting
    account expenses    ; type:X, outflow categories; part of E, separated for reporting
    ; Declare commodities/currencies and their decimal mark, digit grouping,
    ; number of decimal places..
    commodity $1000.00
    commodity 1.000,00 EUR
    <CTRL-D> (paste the command & text above into the terminal, then press control-d)
  • Version control, for tracking changes:

    $ git init
    $ git add 2021.journal
    $ git commit 2021.journal -m 'start 2021 journal'
  • Remember to also keep backups.

Enter transactions

Recording transactions manually may sound tedious, but with a good text editor or other data entry tool it can be fast. It also provides greatest financial awareness. Some people enter everything by hand for this reason.

Run the add command for assisted data entry in the terminal (tutorial):

$ hledger add
Date [2021-03-10]: ...

Or run hledger-web and when the web browser opens, press a to add (tutorial):

$ hledger-web
Opening web browser...

Or using a text editor, add transactions to your journal file like so:

2021-01-01 opening balances on january 1st
    assets:checking         $1000  ; a posting, increasing assets:checking's balance by $1000
    assets:cash              $100
    liabilities                $0
    equity                 $-1100  ; each transaction must sum to zero

2021-03-05 client payment
    assets:checking         $2000
    revenues:consulting    $-2000  ; revenues/liabilities/equity normally appear negative

2021-03-20 Sprouts
    expenses:food:groceries  $100
    assets:cash               $40
    assets:checking                ; a missing amount will be inferred ($-140 here)

As shown above, make the first transaction a dummy one that sets the opening balances of your asset & liability accounts on some start date. hledger will show accurate real-world account balances from this date onward, as long as you record the subsequent transactions.

To make things easy on yourself, you can pick a very recent start date, like today or last monday. Prioritise recording the transactions that happen after this date. (Tip: the more often you do this, the easier it is.)

Then, as your time and financial records and desire for historical reports allow, you can add older transactions. As you do, you'll need to adjust the opening balances transaction, moving it back in time. Perhaps focus on one account at a time, each with its own opening balances transaction if necessary.

Import transactions

Import means 1. convert transaction data from some other format (usually a downloaded CSV file) and 2. save any new transactions to the main journal file. It is often possible to automate this, perhaps to the point of a nightly cron job and no manual data entry at all. This is convenient but costs some financial awareness.

Download one or more CSV files containing transaction info, then create a csv rules file for each. Eg if SomeBank.csv looks like:

"2021/3/23","ATM WITHDRAWAL","-10.00"

Create SomeBank.csv.rules containing rules like:

skip 1
fields date, description, amount
currency $
account1 assets:checking
account2 expenses:misc
 account2 revenues:misc
 account2 assets:cash

Check the csv conversion looks ok:

$ hledger -f SomeBank.csv print
2021-03-22 DEPOSIT
    assets:checking          $50.00
    revenues:misc           $-50.00

    assets:checking         $-10.00
    assets:cash              $10.00

You can run reports directly from the csv, but I like to import the new transactions into the main journal, keeping things in one place. The import command ignores csv records it has seen before, saving the latest dates in .latest.SomeBank.csv. This works for most csv files - you can try a dry run first:

$ hledger import *.csv --dry-run
; would import 2 new transactions from SomeBank.csv:

2021-03-22 DEPOSIT
    assets:checking          $50.00
    revenues:misc           $-50.00

    assets:checking         $-10.00
    assets:cash              $10.00

$ hledger import *.csv 
imported 2 new transactions from SomeBank.csv
$ hledger import *.csv
no new transactions found in SomeBank.csv

Now to commit the new rules file and changed journal file:

$ git add SomeBank.csv.rules
$ git commit -m 'SomeBank csv rules' SomeBank.csv.rules
$ git commit -m 'txns' 2021.journal

In the above workflow, the journal file is permanent and downloaded csv files are temporary. Some folks (Full-fledged hledger, hledger-flow) prefer to instead commit all csv files and regenerate the journal file.


After entering or importing transactions, it's important to check for mistakes (yours or others'), by comparing your reports with reality - your wallet, statements, online balances etc. See Reconciling.

Run reports

$ hledger accounts   # account names declared and used, as a list

$ hledger accounts --tree   # accounts are actually a hierarchy

$ hledger balancesheet    # what do I own and owe ?
$ hledger bs              # short form
Balance Sheet 2021-03-20

                 || 2021-03-20 
 Assets          ||            
 assets:cash     ||       $140 
 assets:checking ||      $2860 
                 ||      $3000 
 Liabilities     ||            
 Net:            ||      $3000 

$ hledger aregister --forecast checking   # or: hledger register checking
Transactions in assets:checking and subaccounts:
2021-01-01 opening balances ..  as:cash, liabiliti..         $1000         $1000
2021-03-05 client payment       re:consulting                $2000         $3000
2021-03-20 Sprouts              ex:fo:groceries, a..         $-140         $2860

$ hledger incomestatement --monthly --depth 2    # where is it coming from and going to ?
$ hledger is -M -2                               # short form
Income Statement 2021Q1

                     || Jan  Feb    Mar 
 Revenues            ||                 
 revenues:consulting ||   0    0  $2000 
                     ||   0    0  $2000 
 Expenses            ||                 
 expenses:food       ||   0    0   $100 
                     ||   0    0   $100 
 Net:                ||   0    0  $1900 

$ hledger                         # show commands

$ hledger --help                  # show general options

$ hledger --man                   # show hledger's man page

$ hledger --info                  # show hledger's Info manual

$ hledger is --help               # show incomestatement's options and docs

$ hledger is --man                # show incomestatement in man page

$ hledger is --info               # show incomestatement's Info page

$ hledger help                    # show hledger docs in best available viewer

$ hledger help incomestatement    # show incomestatement docs in best available viewer

$ hledger-ui                      # start TUI

$ hledger-web                     # start WUI in default browser

For more detail, see:

Easy workflow #1: hledger add

Here we'll walk you through a simple way of using hledger, using the built-in add command. This requires only command-line hledger, and works on all platforms with no further setup.

We'll also introduce some basic hledger concepts. So even if you don't plan on using hledger add, it might be worth reading through this quickly.

Download/install hledger and let's get started!

Check your installation

Open a terminal or command prompt, and check your hledger version. It should be reasonably up to date. This doc was last tested with:

$ hledger --version
hledger 1.9

Locate your journal file with "hledger stats"

hledger reads financial transactions from a "journal file" (so named because it represents a General Journal). The default journal file is in your home directory; check its path using the stats command. You should see something like:

$ hledger stats
The hledger journal file "/home/YOU/.hledger.journal" was not found.
Please create it first, eg with "hledger add" or a text editor.
Or, specify an existing journal file with -f or LEDGER_FILE.

Most hledger commands read this file but can not change it; the add and web commands can also write it.

(If stats reports that the file exists, eg because you previously created it, move it out of the way temporarily for these exercises.)

Record a transaction with "hledger add"

Follow the help and use the add command to record your first transaction, an imaginary purchase at the supermarket. We'll go through this in detail. Later you'll learn other ways to enter data.

$ hledger add
Creating hledger journal file "/home/YOU/.hledger.journal".
Adding transactions to journal file /home/YOU/.hledger.journal
Any command line arguments will be used as defaults.
Use tab key to complete, readline keys to edit, enter to accept defaults.
An optional (CODE) may follow transaction dates.
An optional ; COMMENT may follow descriptions or amounts.
If you make a mistake, enter < at any prompt to restart the transaction.
To end a transaction, enter . when prompted.
To quit, enter . at a date prompt or press control-d or control-c.
Date [2015/05/25]:

add prompts for each transaction field. The first is the date. The value in square brackets is the suggested default (today's date). Press enter to accept it.

Description: trip to the supermarket

Transactions have an optional description (a single line of text) to help you understand them. You can describe the transaction here, or put a payee name, or leave it blank. Type trip to the supermarket and press enter.

Account 1: expenses

Transactions have two or more accounts. Keep it simple; just enter expenses for the first one.

If you're thinking "expenses sounds more like a category": it is, but double entry accounting calls those "accounts", too. A purchase is a transfer of money from an asset account to an expense account. An asset is something you own, like some money in a bank account or in your pocket. Once the money has been "moved" to an expense, you no longer own it, but the increasing balance in the expense account reminds you where it went.

Amount  1: $10

The amount being "moved" to expenses. In this case 10 US dollars.

Account 2: assets

Next, specify which account the money comes from. Just say assets.

Amount  2 ? [$-10.0]: 

Now you're asked for the amount to "move" to or from the assets account. As the default, hledger offers the amount required to "balance" the postings entered so far. The minus sign indicates the money is moving from this account. (hledger uses the positive and negative sign instead of accounting's traditional "debit" and "credit" terminology.) In a balanced transaction, the sum of posted amounts is zero, in other words no money disappears into thin air. Press enter to accept the default. It has an extra decimal place, but never mind.

Account 3 (or . to finish this transaction): .

Type . (period) and press enter.

2015/05/25 trip to the supermarket
    expenses           $10
    assets          $-10.0

Save this transaction to the journal ? [y]:

You are given a chance to review the transaction just entered. Here you see hledger's plain text data format for journal entries: a non-indented YYYY/MM/DD date, space, and description, followed by two or more indented posting lines, each containing an account name, two or more spaces, and an amount. (Account names can contain spaces, so at least two spaces are needed to separate them from the amount.) Press enter.

Starting the next transaction (. or ctrl-D/ctrl-C to quit)
Date [2015/05/25]: <CTRL-D>

hledger has saved it to the journal file and is ready for the next entry. Press control-d (on Windows, control-c) once to exit.

stats should now report that your journal exists and contains one transaction:

$ hledger stats
Main journal file        : /home/YOU/.hledger.journal
Included journal files   : 
Transactions span        : 2015-05-25 to 2015-05-26 (1 days)
Last transaction         : 2015-05-25 (0 days ago)
Transactions             : 1 (1.0 per day)
Transactions last 30 days: 1 (0.0 per day)
Transactions last 7 days : 1 (0.1 per day)
Payees/descriptions      : 1
Accounts                 : 2 (depth 1)
Commodities              : 1 ($)

Show transactions with "hledger print"

The print command shows a tidied-up view of the transaction entries in your journal. Since there's just one so far, you should see:

$ hledger print
2015/05/25 trip to the supermarket
    expenses           $10
    assets            $-10

Examine your journal file

List and print the journal file (on Windows, use dir and type and the file path from hledger stats):

$ ls -l ~/.hledger.journal
-rw-r--r--  1 YOU  YOU  114 May 25 16:55 /home/YOU/.hledger.journal
$ cat ~/.hledger.journal
; journal created 2015-05-25 by hledger

2015/05/25 trip to the supermarket
    expenses           $10

A convenience: inferred amounts

Why is the amount missing from the assets posting above ? As a convenience to make manual data entry easier, if one amount is missing hledger infers it so as to balance the transaction ($-10 in this case). Only one missing amount is allowed in each transaction. add uses the same convention when it writes an entry. (To see all such inferred amounts in full, you can use hledger print -x.)

Edit the journal file

Since the journal file is plain text, you can edit it directly with any text editor. Edit the file and change it to test whether two missing amounts is reported as an error. Eg:

$ emacs ~/.hledger.journal

Remove the expenses amount and save the file. It now looks like this:

2015/05/25 trip to the supermarket

Running print again, you should see:

hledger: could not balance this transaction (can't have more than one missing amount; remember to put 2 or more spaces before amounts)
2015/05/25 trip to the supermarket

All hledger commands expect the journal to be well-formed, and will report an error and exit otherwise.

Two spaces

Notice the last part of that error message: "... remember to put 2 or more spaces before amounts)". Another cause of this error is forgetting to put two spaces before the amount, like this:

2015/05/25 trip to the supermarket
    expenses $10  ; <- problem: only one space between expenses and $10

Since account names may contain spaces, hledger thinks the first posting is to an account named "expenses $10", with a missing amount. So remember: two or more spaces.

Unbalanced transactions

Edit the file to look like this:

2015/05/25 trip to the supermarket
    expenses           $10
    assets             $10  ; <- deliberate problem: both amounts are positive

Here, we wrote both posting amounts but got the sign wrong on one of them, so they don't add up to zero. hledger should detect this mistake. Verify it by running some command, eg print. You should see:

$ hledger print
hledger: could not balance this transaction (real postings are off by $20)
2015/05/25 trip to the supermarket
    expenses           $10
    assets             $10

That makes sense. (It calls them "real" postings because there are some other kinds of posting you haven't learned about yet; they aren't important.)

Correct the mistake by adding the minus sign, or just removing the assets amount entirely, and verify that print works again:

$ hledger print
2015/05/25 trip to the supermarket
    expenses           $10
    assets            $-10

Record a transaction by editing

Edit the file again and manually add a second purchase transaction. It's often quickest to copy & paste a similar entry, then change it. Make the file look like this:

2015/05/25 trip to the supermarket
    expenses           $10
    assets            $-10

2015/05/26 forgot the bread
    expenses            $5

The blank line between transactions is customary, though not required. Test your work with print. You should see:

$ hledger print
2015/05/25 trip to the supermarket
    expenses           $10
    assets            $-10

2015/05/26 forgot the bread
    expenses            $5
    assets             $-5

What's in a Transaction ?

Here's a basic hledger transaction with the parts named:

hledger basic transaction, showing names of parts

And here's a more complicated hledger transaction:

hledger complicated transaction with names of parts

Show postings and a running total with "hledger register"

The register command shows transactions in a different format. More precisely, it shows postings. Remember, a posting is an increase or decrease of some account by some amount, and a transaction contains two or more of them. Run register and compare with the output of print above. You should see:

$ hledger register
2015/05/25 trip to the supermarket  expenses                          $10           $10
                                    assets                           $-10             0
2015/05/26 forgot the bread         expenses                           $5            $5
                                    assets                            $-5             0

Postings are displayed one per line. The transaction's date and description is displayed only for the first posting in each transaction. Next we see the posted account's name and the amount posted. The final column is a running total of the posted amounts.

Show a per-account register report

Notice how the running total above keeps resetting to 0. This makes sense (since we know each transaction's postings add up to zero) but isn't very useful. The register report is more useful when we restrict it to a subset of postings - say, only the postings within a single account. You can do this by specifying the account name as a command line argument.

Run a register report for the expenses account. You should see something like the below. (On POSIX platforms, this command uses the terminal width so the output may look slightly different. You can force it to look like the below by running export COLUMNS=80 first:

$ hledger register expenses
2015/05/25 trip to the super..  expenses                       $10           $10
2015/05/26 forgot the bread     expenses                        $5           $15

Now it's clear that your expenses balance - ie, the total amount spent - has increased to $15.

Your assets balance should have dropped accordingly. Check it:

$ hledger register assets
2015/05/25 trip to the super..  assets                        $-10          $-10
2015/05/26 forgot the bread     assets                         $-5          $-15

Set initial account balances

hledger assumes every account starts with a zero balance, so in the previous example, we see the withdrawals producing a negative running balance. Let's assume assets represents a real-world asset, like your bank checking account, and you want to start tracking it from 2015/05/01 onward, and on that day it contained exactly $500. To show the real-world account balance, edit your journal file and add this transaction at the top:

2015/05/01 set initial assets balance
    assets                              $500
    equity:opening balances

The other account name doesn't matter too much; equity:opening balances is conventional. (You could also use an unbalanced transaction for this if you prefer.) Now the report looks like this, with an accurate running balance on each date (hledger calls this a historical balance):

$ hledger register assets
2015/05/01 set initial asset..  assets                        $500          $500
2015/05/25 trip to the super..  assets                        $-10          $490
2015/05/26 forgot the bread     assets                         $-5          $485

Query expressions

The account name argument above is an example of a query expression, a search pattern which restricts a report to a subset of the data. In this way you can make very precise queries.

Note that it is a case-insensitive regular expression which matches anywhere inside the account name. So "e" would match both expenses and assets.

And if you had an account named other assets, "assets" would also match that, so to match only the assets account you'd need a more precise pattern like "^assets$". (In a regular expression ^ means "match at the beginning" and $ means "match at the end".) If this doesn't make sense, read a little about regular expressions.

Multiple query arguments are ANDed and ORed together in a fixed way - follow the link for details. Basically queries on the same field are ORed, and queries on different fields are ANDed.

Run the following examples and make sure they make sense, consulting the manual as needed.

Show only transactions whose description ends with "bread":

$ hledger print desc:bread$
2015/05/26 forgot the bread
    expenses            $5
    assets             $-5

Show only postings on or after a certain date to an account whose name contains "exp":

$ hledger register date:2015/5/26- exp
2015/05/26 forgot the bread     expenses                        $5            $5

Show accounts and their balances with "hledger balance"

The third of hledger's three core reporting commands is balance. Use it to list all the accounts posted to, and their ending balance. You should see account balances agreeing with the final running total in the register reports above:

$ hledger balance
                $-15  assets
                 $15  expenses

The overall total of these balances is also shown. As with other reports, you can use a query expression to select a subset of the data to report on. Eg:

$ hledger balance assets
                $-15  assets

balance shows the sum of matched posting amounts

Here's a balance report based only on the postings dated 2015/5/26:

$ hledger balance date:2015/5/26
                 $-5  assets
                  $5  expenses

As you can see from this, balance does not necessarily report real-world account balances; rather, it shows the sum of the postings you have selected. If you're not sure what those are, run a register report with the same arguments to see them:

$ hledger register date:2015/5/26
2015/05/26 forgot the bread     expenses                        $5            $5
                                assets                         $-5             0


You have learned:

  • a simple plain text notation for recording financial transactions, used by hledger, Ledger and others

  • what is the journal file, where it is, and how to get statistics on it with hledger stats

  • how to record new transactions using hledger add

  • how to record transactions by editing the journal file

  • what the journal entry for a purchase looks like

  • how to detect some common errors, by eye or with hledger

  • how hledger selects data to report on, and how to select by account, description, or date

  • how to list transactions with hledger print

  • how to list postings and see an account's balance over time with hledger register

  • how to list accounts and their current balance, or the sum of their postings in some period, with hledger balance

Easy workflow #2: hledger-web

hledger-web is hledger's web browser-based UI. It's probably the easiest way to get started with hledger. Eg on windows, you can download and unpack hledger.zip and double click on hledger-web.exe. Screenshots below!

Check your installation

Open a terminal or command prompt and check your hledger-web version. It should be reasonably up to date. This doc was last tested with:

$ hledger-web --version
hledger-web 1.17.1

If this fails, check download/install for install and setup tips.

Start hledger-web

Normally, you start hledger-web by running hledger-web in a terminal, with no arguments. Browsing to the executable file and double-clicking on it can also work. Normally, this will start the web app, making it accessible only from your local machine.

For this tutorial, to follow the steps/screenshots below and avoid disturbing any existing data, we'll start hledger-web with a new temporary journal file:

$ mkdir tmp
$ echo > tmp/.hledger.journal
$ hledger-web -f tmp/.hledger.journal

It will print a startup message and keep running, logging any web requests received:

22/Mar/2020:17:34:19 -0700 [Info#yesod-core] Application launched @(yesod-core- src/Yesod/Core/Dispatch.hs:163:11)
Serving web UI and API on with base url
This server will exit after 2m with no browser windows open (or press ctrl-c)
Opening web browser... - - [22/Mar/2020:17:34:21 -0700] "GET / HTTP/1.1" 303 0 "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:74.0) Gecko/20100101 Firefox/74.0" - - [22/Mar/2020:17:34:21 -0700] "GET /journal HTTP/1.1" 200 - "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:74.0) Gecko/20100101 Firefox/74.0" - - [22/Mar/2020:17:34:23 -0700] "GET /static/js/typeahead.bundle.min.js HTTP/1.1" 200 - "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:74.0) Gecko/20100101 Firefox/74.0"

You can leave this terminal in the background, or minimised, but don't quit it (unless you want the web app to quit).

And, your web browser should open, showing the UI. If not, open it yourself and browse to the url shown, ie (and let us know).

A quick tour

Here's the "home" screen, showing the empty default journal. Or if your journal already contained transactions, you'll see them listed. "General Journal" means the list of transactions, basically.

Let's hide the sidebar, for a simpler UI. Pressing the s key should do it (if not, try again after clicking somewhere on the page, and let us know):

Let's record some transactions. Press the a key, or click "Add a transaction", to bring up the add form. You may need to click or press TAB to focus the Date field:

We'll copy the sample transactions from the quick start. Press TAB to advance to each next field. Amount2 can be left blank, or you can fill in $-1234 if you prefer:

Press RETURN or click the add button to save the transaction:

Press a again to add the next transaction. As you type "assets:checking", you'll see there's some autocompletion available, since we have used that account before:

Finish the transaction, and press RETURN:

Press a and enter the final transaction. This one has three postings (we spent $100 on groceries and also withdrew $40 as cash). As before, you can optionally leave one of the amounts blank:

After pressing RETURN. The journal now shows three transactions, with the most recent at the top:

Now, press s to show the sidebar again. We can see it shows the account hierarchy, and each account's balance, based on the transactions entered so far:

Clicking on, eg, checking shows the Register for that account - a list of the transactions affecting this account, and their running total, and a chart of that total over time:

Press j, or click Journal, to return to the General Journal page showing everything:

Let's try filtering these transactions with a query. Type food in the search field and press RETURN. Now we see just the transaction involving a food account:

Click the x button or click Journal or press j to see all data again.

You can see quick help by pressing ?, or clicking the ? button near the search field. More help is available in the hledger-web and other manuals, of course:

That's mostly it! Although there are basically just two screens, with hledger's full query language available (and with the general flags available on the command line), you can do a surprising amount with hledger-web. You can also change permissions to enable edit/upload/download access.

Easy workflow #3: hledger-ui

hledger-ui is hledger's "curses-style" UI, ie a full-window terminal app. It's fast, efficient, and slightly more powerful than the web UI. One limitation: on Microsoft Windows, it can only be installed inside WSL. Screenshots below!

Check your installation

Open a terminal or command prompt and check your hledger-ui version. It should be reasonably up to date. This doc was last tested with:

$ hledger-ui --version

If this fails, check download/install for install and setup tips.

Start hledger-ui

Normally, you start hledger-ui by running hledger-ui in a terminal. Any standard terminal app will do, but not a command prompt that doesn't allow cursor positioning (such as an emacs shell buffer). (In that case you'd see garbage output, like ^[(B^[(B──────────── tmp.journal account balances..., and would have to press q, RETURN to exit.)

For this tutorial, we'll specify a new temporary journal file, to suit the steps below and avoid disturbing any existing data:

$ echo > tmp.journal
$ hledger-ui -f tmp.journal

A quick tour

With an empty journal, there's not much to see. But we can see: the name of the journal file (tmp.journal) and the screen (account balances) at the top:

And some quick help across the bottom, telling us that pressing q is a way to quit hledger-ui, and pressing a is a way to add transactions. Press a:

This bare-bones data entry UI is hledger's add command, also introduced in Easy workflow #1: hledger add.

Here, we'll use it to enter a few transactions, similar to the quick start example. After each prompt, type the value shown in white and press RETURN.

Note when we get to Amount 2, hledger guesses that this is a two-posting transaction and as a default value offers the amount that would balance the transaction ($-1234):

In such cases we can press TAB to enter the default value, with an opportunity to edit it before pressing RETURN:

Or just press RETURN to use it. At this point transaction is balanced, and so we have the option to end the transaction. Press RETURN (enter) to do that:

We get one more opportunity to preview the resulting journal entry. Press RETURN again to accept the default answer (y):

When we see "Saved.", we know the entry has been written (appended) to the journal file. And we get another Date prompt, ready to enter another transaction:

Continue entering the values shown in white (2020-03-15, client payment..). This time, based on the description, hledger has picked a "similar transaction", which will supply defaults for the following prompts. Eg assets:checking for Account 1. That's what we want, so press RETURN to accept it:

The next defaults ($1234, equity..) are not what we want, so ignore them and continue entering the values shown ($2000, income:consulting..):

And as before, press RETURN a few times to finish and save this transaction:

Enter the values for the final transaction (2020-03-20, Sprouts..). Note, we don't accept the default for Amount 2, so the transaction remains unbalanced, and we can continue on, to enter the third posting:

When entering a value we've used before, we can type just the start of it and press TAB to auto-complete the rest, saving some work:

Complete this transaction, and this time at the Date prompt enter . (period) and RETURN to end data entry:

We have returned to hledger-ui, and now we see the accounts and their ending balances:

Press DOWN (down arrow) to move the selection (the yellow highlight) to the assets:checking account:

Press RIGHT to "drill in" to see the transactions in assets:checking ("register" screen). The third column shows the other account(s) involved, abbreviated when necessary. The last column is the account's running balance:

Press RIGHT again to see the selected transaction's journal entry ("transaction" screen). We can see this is transaction number 3 in the journal, and also number 3 of 3 in the assets:checking account:

On this screen we can use the up and down arrows to step back and forth through transactions. Press UP. Now we're at transaction number 2:

Press LEFT to go back to the previous screen (assets:checking's register):

And LEFT again to go back to the top screen (accounts):

Press T (capital T) to switch from "flat mode" to "tree mode". This shows the account hierarchy more clearly:

With a lot of accounts, sometimes you want to see less detail. Press the - (minus) key once to reduce the account depth limit by 1. Now the expenses:food:groceries subaccount (depth 3) has been hidden, and the heading says "to depth 2":

Press the - key once more, reducing the depth limit to 1:

We could press + to increase the depth again, or a number key to set it directly. Press 9 to make it "unlimited", removing the depth limit:

Press the / (forward slash) key to enter a filter query. A filter: prompt appears at the bottom:

Let's filter for accounts with "checking" in their name. Type checking and press RETURN:

Now only assets:checking is shown. (And its parent assets account, because we're in tree mode):

Press RIGHT to see the register for the assets account. Note, while there are no transactions directly referencing the assets account, the transactions of its subaccount assets:checking are shown, because we're in tree mode. (In flat mode, they wouldn't be. That's what the T:flat(-subs)/tree(+subs) in the help line is about.):

On a register screen, the SHIFT-DOWN/SHIFT-UP keys shrink/expand the time period being shown. By default, the register shows all of the account's transactions. Hold down SHIFT and press the DOWN arrow once. Not much has changed, but now the heading says "in 2020" (at least, if you are doing this in the year 2020..). The report period has shrunk to "the current year":

Press SHIFT-DOWN again. Now it says "in 2020q1" (at least, if you are doing this in the first quarter of 2020..). The report period has shrunk to "the current quarter of the current year":

Press SHIFT-DOWN again. The next smaller period is "the current month", and (at least, if you are doing this in March, 2020..) the heading now says 2020/03, and now we can see that only 2 of the transactions are in March:

When the report period has been shrunk in this way, the SHIFT-LEFT/SHIFT-RIGHT keys move backward/forward in time. So pressing SHIFT-LEFT now should take you to the previous month (2020-02). There are no transactions in this month. (ignore the columns of dots; it's a display bug):

Press SHIFT-LEFT once more to show the previous month (2020-01), where we can see the single January transaction:

To expand the report period again, press SHIFT-UP several times, until the "in ..." suffix disappears from the heading:

Press the ESC (escape) key - this is another way to return to the top screen, and also clears any filter query.

Pressing the ? key shows a more detailed help dialog. To close it, press ? again, or ESC.

While the help dialog is open, you can press p, m or i to see the hledger-ui user manual in several offline formats (useful if you can't access the web version).

The formats are: plain text (p), using your $PAGER for scrolling if possible:

Unix man (m):

or GNU info (i):

Press q to return to hledger-ui.

Accounting concepts

Here we'll give a quick hledger-oriented intro to some useful accounting concepts (continuing with the journal file from Easy workflow #1: hledger add). Also we'll discuss account hierarchy in hledger. At the end, there's a collection of useful links to learn more.

Assets, Liabilities and Equity

Accounting describes the status of a business, person or other entity at any point in time in terms of three amounts:

  • Assets - Things owned
  • Liabilities - Things owed
  • Equity - The amount invested by owners/shareholders

The foundation of double-entry accounting is the accounting equation, which says Equity is always equal to Assets minus Liabilities (or, Net Assets).

This is also written as: Assets = Liabilities + Equity. Another way to say it: what the entity owns is funded either by debt or by the capital provided by its owners.

These three are called the Balance Sheet accounts. Their balances summarise the overall financial status at some point in time.

Revenue and Expenses

Two more amounts are used to describe changes in the above during a given period:

  • Revenue - Money flowing in
  • Expenses - Money flowing out

You may be accustomed to using the word Income instead Revenue. That's fine, just remember that Income is sometimes used to mean Net Income, which is Revenue - Expenses.

These two are called the Income Statement accounts. The balances they accumulate during some period of time indicate the inflows and outflows during that period (which will affect the Assets and Liabilities balances).

Chart of Accounts

Five numbers do not give a lot of detail. If you want to know what portion of expenses went to buy food, you could add up just the transactions with (say) "supermarket" in their description. You know how to do this with hledger:

$ hledger register desc:supermarket expenses
2015/05/25 trip to the super..  expenses                       $10           $10

But descriptions are irregular, and as you can see we missed the $5 purchase on the following day.

Instead, the major "top-level" accounts above are subdivided into subaccounts which can be used in transactions, thereby categorising them in a more structured way. If needed, these subaccounts can be subdivided further. This tree of accounts is called the Chart of Accounts. Here's a simple example where assets, revenue and expenses each have a few subaccounts:

  business income
  gifts received

In some organisations and accounting systems (eg, QuickBooks), the tree structure is de-emphasised, so the above is represented more like:

 Account name      Account type
 checking          ASSET
 cash              ASSET
 business income   REVENUE
 gifts received    REVENUE
 food              EXPENSE
 rent              EXPENSE
 supplies          EXPENSE

In others, the tree structure is encoded as decimal account numbers, something like this:

1000 assets
1100   checking
1200   cash
2000 liabilities
3000 equity
4000 revenue
4100   business income
4200   gifts received
5000 expenses
5100   food
5200   rent
5300   supplies

A digression: subaccounts in hledger

With hledger, tree structure is implied by writing account names like ACCOUNT:SUBACCOUNT. Try it: edit your journal file and change the account names like so:

$ cat ~/.hledger.journal

2015/05/25 trip to the supermarket
    expenses:supplies           $10
    assets:checking            $-10

2015/05/26 forgot the bread
    expenses:food            $5

hledger will infer the chart of accounts from these names. The accounts command will list all accounts posted to:

$ hledger accounts

and accounts --tree will show the tree structure, indenting subaccounts below their parents (and eliding the common part of their names):


Conversely, the balance command shows the tree structure by default:

$ hledger balance
                $-15  assets
                 $-5    cash
                $-10    checking
                 $15  expenses
                  $5    food
                 $10    supplies

As you can see, the balance reported for parent accounts includes the balances of any subaccounts (it would also include any postings to the parent account itself.)

To see full account names in a flat list, use --flat:

$ hledger balance --flat
                 $-5  assets:cash
                $-10  assets:checking
                  $5  expenses:food
                 $10  expenses:supplies

hledger accepts whatever account names you choose, so you can use as much or as little account hierarchy as you need. Most users have at least two levels of accounts. You can limit the amount of detail in a balance report by hiding accounts below a certain depth:

$ hledger balance --depth 1
                $-15  assets
                 $15  expenses

Accounting links





This is the command-line interface (CLI) for the hledger accounting tool. Here we also describe hledger's concepts and file formats. This manual is for hledger 1.21.



hledger [-f FILE] ADDONCMD -- [OPTIONS] [ARGS]

hledger is a reliable, cross-platform set of programs for tracking money, time, or any other commodity, using double-entry accounting and a simple, editable file format. hledger is inspired by and largely compatible with ledger(1).

The basic function of the hledger CLI is to read a plain text file describing financial transactions (in accounting terms, a general journal) and print useful reports on standard output, or export them as CSV. hledger can also read some other file formats such as CSV files, translating them to journal format. Additionally, hledger lists other hledger-* executables found in the user’s $PATH and can invoke them as subcommands.

hledger reads data from one or more files in hledger journal, timeclock, timedot, or CSV format specified with -f, or $LEDGER_FILE, or $HOME/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.journal). If using $LEDGER_FILE, note this must be a real environment variable, not a shell variable. You can specify standard input with -f-.

Transactions are dated movements of money between two (or more) named accounts, and are recorded with journal entries like this:

2015/10/16 bought food
 expenses:food          $10

For more about this format, see hledger_journal(5).

Most users use a text editor to edit the journal, usually with an editor mode such as ledger-mode for added convenience. hledger’s interactive add command is another way to record new transactions. hledger never changes existing transactions.

To get started, you can either save some entries like the above in ~/.hledger.journal, or run hledger add and follow the prompts. Then try some commands like hledger print or hledger balance. Run hledger with no arguments for a list of commands.


General options

To see general usage help, including general options which are supported by most hledger commands, run hledger -h.

General help options:

-h --help : show general or COMMAND help

--man : show general or COMMAND user manual with man

--info : show general or COMMAND user manual with info

--version : show general or ADDONCMD version

--debug[=N] : show debug output (levels 1-9, default: 1)

General input options:

-f FILE --file=FILE : use a different input file. For stdin, use - (default: $LEDGER_FILE or $HOME/.hledger.journal)

--rules-file=RULESFILE : Conversion rules file to use when reading CSV (default: FILE.rules)

--separator=CHAR : Field separator to expect when reading CSV (default: ',')

--alias=OLD=NEW : rename accounts named OLD to NEW

--anon : anonymize accounts and payees

--pivot FIELDNAME : use some other field or tag for the account name

-I --ignore-assertions : disable balance assertion checks (note: does not disable balance assignments)

-s --strict : do extra error checking (check that all posted accounts are declared)

General reporting options:

-b --begin=DATE : include postings/txns on or after this date

-e --end=DATE : include postings/txns before this date

-D --daily : multiperiod/multicolumn report by day

-W --weekly : multiperiod/multicolumn report by week

-M --monthly : multiperiod/multicolumn report by month

-Q --quarterly : multiperiod/multicolumn report by quarter

-Y --yearly : multiperiod/multicolumn report by year

-p --period=PERIODEXP : set start date, end date, and/or reporting interval all at once using period expressions syntax

--date2 : match the secondary date instead (see command help for other effects)

-U --unmarked : include only unmarked postings/txns (can combine with -P or -C)

-P --pending : include only pending postings/txns

-C --cleared : include only cleared postings/txns

-R --real : include only non-virtual postings

-NUM --depth=NUM : hide/aggregate accounts or postings more than NUM levels deep

-E --empty : show items with zero amount, normally hidden (and vice-versa in hledger-ui/hledger-web)

-B --cost : convert amounts to their cost/selling amount at transaction time

-V --market : convert amounts to their market value in default valuation commodities

-X --exchange=COMM : convert amounts to their market value in commodity COMM

--value : convert amounts to cost or market value, more flexibly than -B/-V/-X

--infer-market-prices : use transaction prices (recorded with @ or @@) as additional market prices, as if they were P directives

--auto : apply automated posting rules to modify transactions.

--forecast : generate future transactions from periodic transaction rules, for the next 6 months or till report end date. In hledger-ui, also make ordinary future transactions visible.

--color=WHEN (or --colour=WHEN) : Should color-supporting commands use ANSI color codes in text output. : 'auto' (default): whenever stdout seems to be a color-supporting terminal. : 'always' or 'yes': always, useful eg when piping output into 'less -R'. : 'never' or 'no': never. : A NO_COLOR environment variable overrides this.

When a reporting option appears more than once in the command line, the last one takes precedence.

Some reporting options can also be written as query arguments.

Command options

To see options for a particular command, including command-specific options, run: hledger COMMAND -h.

Command-specific options must be written after the command name, eg: hledger print -x.

Additionally, if the command is an add-on, you may need to put its options after a double-hyphen, eg: hledger ui -- --watch. Or, you can run the add-on executable directly: hledger-ui --watch.

Command arguments

Most hledger commands accept arguments after the command name, which are often a query, filtering the data in some way.

You can save a set of command line options/arguments in a file, and then reuse them by writing @FILENAME as a command line argument. Eg: hledger bal @foo.args. (To prevent this, eg if you have an argument that begins with a literal @, precede it with --, eg: hledger bal -- @ARG).

Inside the argument file, each line should contain just one option or argument. Avoid the use of spaces, except inside quotes (or you'll see a confusing error). Between a flag and its argument, use = (or nothing). Bad:

assets depth:2



For special characters (see below), use one less level of quoting than you would at the command prompt. Bad:




See also: Save frequently used options.

Special characters

Single escaping (shell metacharacters)

In shell command lines, characters significant to your shell - such as spaces, <, >, (, ), |, $ and \ - should be "shell-escaped" if you want hledger to see them. This is done by enclosing them in single or double quotes, or by writing a backslash before them. Eg to match an account name containing a space:

$ hledger register 'credit card'


$ hledger register credit\ card

Double escaping (regular expression metacharacters)

Characters significant in regular expressions (described below) - such as ., ^, $, [, ], (, ), |, and \ - may need to be "regex-escaped" if you don't want them to be interpreted by hledger's regular expression engine. This is done by writing backslashes before them, but since backslash is typically also a shell metacharacter, both shell-escaping and regex-escaping will be needed. Eg to match a literal $ sign while using the bash shell:

$ hledger balance cur:'\$'


$ hledger balance cur:\\$

Triple escaping (for add-on commands)

When you use hledger to run an external add-on command (described below), one level of shell-escaping is lost from any options or arguments intended for by the add-on command, so those need an extra level of shell-escaping. Eg to match a literal $ sign while using the bash shell and running an add-on command (ui):

$ hledger ui cur:'\\$'


$ hledger ui cur:\\\\$

If you wondered why four backslashes, perhaps this helps:


Or, you can avoid the extra escaping by running the add-on executable directly:

$ hledger-ui cur:\\$

Less escaping

Options and arguments are sometimes used in places other than the shell command line, where shell-escaping is not needed, so there you should use one less level of escaping. Those places include:

  • an @argumentfile
  • hledger-ui's filter field
  • hledger-web's search form
  • GHCI's prompt (used by developers).

Unicode characters

hledger is expected to handle non-ascii characters correctly:

  • they should be parsed correctly in input files and on the command line, by all hledger tools (add, iadd, hledger-web's search/add/edit forms, etc.)

  • they should be displayed correctly by all hledger tools, and on-screen alignment should be preserved.

This requires a well-configured environment. Here are some tips:

  • A system locale must be configured, and it must be one that can decode the characters being used. In bash, you can set a locale like this: export LANG=en_US.UTF-8. There are some more details in Troubleshooting. This step is essential - without it, hledger will quit on encountering a non-ascii character (as with all GHC-compiled programs).

  • your terminal software (eg Terminal.app, iTerm, CMD.exe, xterm..) must support unicode

  • the terminal must be using a font which includes the required unicode glyphs

  • the terminal should be configured to display wide characters as double width (for report alignment)

  • on Windows, for best results you should run hledger in the same kind of environment in which it was built. Eg hledger built in the standard CMD.EXE environment (like the binaries on our download page) might show display problems when run in a cygwin or msys terminal, and vice versa. (See eg #961).

Regular expressions

hledger uses regular expressions in a number of places:

  • query terms, on the command line and in the hledger-web search form: REGEX, desc:REGEX, cur:REGEX, tag:...=REGEX
  • CSV rules conditional blocks: if REGEX ...
  • account alias directives and options: alias /REGEX/ = REPLACEMENT, --alias /REGEX/=REPLACEMENT

hledger's regular expressions come from the regex-tdfa library. If they're not doing what you expect, it's important to know exactly what they support:

  1. they are case insensitive
  2. they are infix matching (they do not need to match the entire thing being matched)
  3. they are POSIX ERE (extended regular expressions)
  4. they also support GNU word boundaries (\b, \B, \<, \>)
  5. they do not support backreferences; if you write \1, it will match the digit 1. Except when doing text replacement, eg in account aliases, where backreferences can be used in the replacement string to reference capturing groups in the search regexp.
  6. they do not support mode modifiers ((?s)), character classes (\w, \d), or anything else not mentioned above.

Some things to note:

  • In the alias directive and --alias option, regular expressions must be enclosed in forward slashes (/REGEX/). Elsewhere in hledger, these are not required.

  • In queries, to match a regular expression metacharacter like $ as a literal character, prepend a backslash. Eg to search for amounts with the dollar sign in hledger-web, write cur:\$.

  • On the command line, some metacharacters like $ have a special meaning to the shell and so must be escaped at least once more. See Special characters.


LEDGER_FILE The journal file path when not specified with -f. Default: ~/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.journal).

A typical value is ~/DIR/YYYY.journal, where DIR is a version-controlled finance directory and YYYY is the current year. Or ~/DIR/current.journal, where current.journal is a symbolic link to YYYY.journal.

On Mac computers, you can set this and other environment variables in a more thorough way that also affects applications started from the GUI (say, an Emacs dock icon). Eg on MacOS Catalina I have a ~/.MacOSX/environment.plist file containing

  "LEDGER_FILE" : "~/finance/current.journal"

To see the effect you may need to killall Dock, or reboot.

COLUMNS The screen width used by the register command. Default: the full terminal width.

NO_COLOR If this variable exists with any value, hledger will not use ANSI color codes in terminal output. This overrides the --color/--colour option.


hledger reads transactions from one or more data files. The default data file is $HOME/.hledger.journal (or on Windows, something like C:/Users/USER/.hledger.journal).

You can override this with the $LEDGER_FILE environment variable:

$ setenv LEDGER_FILE ~/finance/2016.journal
$ hledger stats

or with one or more -f/--file options:

$ hledger -f /some/file -f another_file stats

The file name - means standard input:

$ cat some.journal | hledger -f-

Data formats

Usually the data file is in hledger's journal format, but it can be in any of the supported file formats, which currently are:

Reader:Reads:Used for file extensions:
journalhledger journal files and some Ledger journals, for transactions.journal .j .hledger .ledger
timeclocktimeclock files, for precise time logging.timeclock
timedottimedot files, for approximate time logging.timedot
csvcomma/semicolon/tab/other-separated values, for data import.csv .ssv .tsv

These formats are described in their own sections, below.

hledger detects the format automatically based on the file extensions shown above. If it can't recognise the file extension, it assumes journal format. So for non-journal files, it's important to use a recognised file extension, so as to either read successfully or to show relevant error messages.

You can also force a specific reader/format by prefixing the file path with the format and a colon. Eg, to read a .dat file as csv format:

$ hledger -f csv:/some/csv-file.dat stats

Or to read stdin (-) as timeclock format:

$ echo 'i 2009/13/1 08:00:00' | hledger print -ftimeclock:-

Multiple files

You can specify multiple -f options, to read multiple files as one big journal. There are some limitations with this:

  • most directives do not affect sibling files
  • balance assertions will not see any account balances from previous files

If you need either of those things, you can

  • use a single parent file which includes the others
  • or concatenate the files into one before reading, eg: cat a.journal b.journal | hledger -f- CMD.

Strict mode

hledger checks input files for valid data. By default, the most important errors are detected, while still accepting easy journal files without a lot of declarations:

  • Are the input files parseable, with valid syntax ?
  • Are all transactions balanced ?
  • Do all balance assertions pass ?

With the -s/--strict flag, additional checks are performed:

See also: https://hledger.org/checking-for-errors.html



Smart dates

hledger's user interfaces accept a flexible "smart date" syntax. Smart dates allow some english words, can be relative to today's date, and can have less-significant date parts omitted (defaulting to 1).


2004/10/1, 2004-01-01, 2004.9.1exact date, several separators allowed. Year is 4+ digits, month is 1-12, day is 1-31
2004start of year
2004/10start of month
10/1month and day in current year
21day in current month
october, octstart of month in current year
yesterday, today, tomorrow-1, 0, 1 days from today
last/this/next day/week/month/quarter/year-1, 0, 1 periods from the current period
201812018 digit YYYYMMDD with valid year month and day
2018126 digit YYYYMM with valid year and month

Counterexamples - malformed digit sequences might give surprising results:

2018136 digits with an invalid month is parsed as start of 6-digit year
201813018 digits with an invalid month is parsed as start of 8-digit year
201812328 digits with an invalid day gives an error
2018010129+ digits beginning with a valid YYYYMMDD gives an error

Report start & end date

By default, most hledger reports will show the full span of time represented by the journal data. The report start date will be the earliest transaction or posting date, and the report end date will be the latest transaction, posting, or market price date.

Often you will want to see a shorter time span, such as the current month. You can specify a start and/or end date using -b/--begin, -e/--end, -p/--period or a date: query (described below). All of these accept the smart date syntax.

Some notes:

  • As in Ledger, end dates are exclusive, so you need to write the date after the last day you want to include.
  • As noted in reporting options: among start/end dates specified with options, the last (i.e. right-most) option takes precedence.
  • The effective report start and end dates are the intersection of the start/end dates from options and that from date: queries. That is, date:2019-01 date:2019 -p'2000 to 2030' yields January 2019, the smallest common time span.


-b 2016/3/17begin on St. Patrick’s day 2016
-e 12/1end at the start of december 1st of the current year (11/30 will be the last date included)
-b thismonthall transactions on or after the 1st of the current month
-p thismonthall transactions in the current month
date:2016/3/17..the above written as queries instead (.. can also be replaced with -)

Report intervals

A report interval can be specified so that commands like register, balance and activity will divide their reports into multiple subperiods. The basic intervals can be selected with one of -D/--daily, -W/--weekly, -M/--monthly, -Q/--quarterly, or -Y/--yearly. More complex intervals may be specified with a period expression. Report intervals can not be specified with a query.

Period expressions

The -p/--period option accepts period expressions, a shorthand way of expressing a start date, end date, and/or report interval all at once.

Here's a basic period expression specifying the first quarter of 2009. Note, hledger always treats start dates as inclusive and end dates as exclusive:

-p "from 2009/1/1 to 2009/4/1"

Keywords like "from" and "to" are optional, and so are the spaces, as long as you don't run two dates together. "to" can also be written as ".." or "-". These are equivalent to the above:

-p "2009/1/1 2009/4/1"

Dates are smart dates, so if the current year is 2009, the above can also be written as:

-p "1/1 4/1"
-p "january-apr"
-p "this year to 4/1"

If you specify only one date, the missing start or end date will be the earliest or latest transaction in your journal:

-p "from 2009/1/1"everything after january 1, 2009
-p "from 2009/1"the same
-p "from 2009"the same
-p "to 2009"everything before january 1, 2009

A single date with no "from" or "to" defines both the start and end date like so:

-p "2009"the year 2009; equivalent to “2009/1/1 to 2010/1/1”
-p "2009/1"the month of jan; equivalent to “2009/1/1 to 2009/2/1”
-p "2009/1/1"just that day; equivalent to “2009/1/1 to 2009/1/2”

Or you can specify a single quarter like so:

-p "2009Q1"first quarter of 2009, equivalent to “2009/1/1 to 2009/4/1”
-p "q4"fourth quarter of the current year

The argument of -p can also begin with, or be, a report interval expression. The basic report intervals are daily, weekly, monthly, quarterly, or yearly, which have the same effect as the -D,-W,-M,-Q, or -Y flags. Between report interval and start/end dates (if any), the word in is optional. Examples:

-p "weekly from 2009/1/1 to 2009/4/1"
-p "monthly in 2008"
-p "quarterly"

Note that weekly, monthly, quarterly and yearly intervals will always start on the first day on week, month, quarter or year accordingly, and will end on the last day of same period, even if associated period expression specifies different explicit start and end date.

For example:

-p "weekly from 2009/1/1 to 2009/4/1"starts on 2008/12/29, closest preceding Monday
-p "monthly in 2008/11/25"starts on 2018/11/01
-p "quarterly from 2009-05-05 to 2009-06-01"starts on 2009/04/01, ends on 2009/06/30, which are first and last days of Q2 2009
-p "yearly from 2009-12-29"starts on 2009/01/01, first day of 2009

The following more complex report intervals are also supported: biweekly, fortnightly, bimonthly, every day|week|month|quarter|year, every N days|weeks|months|quarters|years.

All of these will start on the first day of the requested period and end on the last one, as described above.


-p "bimonthly from 2008"periods will have boundaries on 2008/01/01, 2008/03/01, ...
-p "every 2 weeks"starts on closest preceding Monday
-p "every 5 month from 2009/03"periods will have boundaries on 2009/03/01, 2009/08/01, ...

If you want intervals that start on arbitrary day of your choosing and span a week, month or year, you need to use any of the following:

every Nth day of week, every WEEKDAYNAME (eg mon|tue|wed|thu|fri|sat|sun), every Nth day [of month], every Nth WEEKDAYNAME [of month], every MM/DD [of year], every Nth MMM [of year], every MMM Nth [of year].


-p "every 2nd day of week"periods will go from Tue to Tue
-p "every Tue"same
-p "every 15th day"period boundaries will be on 15th of each month
-p "every 2nd Monday"period boundaries will be on second Monday of each month
-p "every 11/05"yearly periods with boundaries on 5th of Nov
-p "every 5th Nov"same
-p "every Nov 5th"same

Show historical balances at end of 15th each month (N is exclusive end date):

hledger balance -H -p "every 16th day"

Group postings from start of wednesday to end of next tuesday (N is start date and exclusive end date):

hledger register checking -p "every 3rd day of week"


With the --depth N option (short form: -N), commands like account, balance and register will show only the uppermost accounts in the account tree, down to level N. Use this when you want a summary with less detail. This flag has the same effect as a depth: query argument (so -2, --depth=2 or depth:2 are equivalent).


One of hledger's strengths is being able to quickly report on precise subsets of your data. Most commands accept an optional query expression, written as arguments after the command name, to filter the data by date, account name or other criteria. The syntax is similar to a web search: one or more space-separated search terms, quotes to enclose whitespace, prefixes to match specific fields, a not: prefix to negate the match.

We do not yet support arbitrary boolean combinations of search terms; instead most commands show transactions/postings/accounts which match (or negatively match):

  • any of the description terms AND
  • any of the account terms AND
  • any of the status terms AND
  • all the other terms.

The print command instead shows transactions which:

  • match any of the description terms AND
  • have any postings matching any of the positive account terms AND
  • have no postings matching any of the negative account terms AND
  • match all the other terms.

The following kinds of search terms can be used. Remember these can also be prefixed with not:, eg to exclude a particular subaccount.

REGEX, acct:REGEX : match account names by this regular expression. (With no prefix, acct: is assumed.) : same as above

amt:N, amt:<N, amt:<=N, amt:>N, amt:>=N : match postings with a single-commodity amount that is equal to, less than, or greater than N. (Multi-commodity amounts are not tested, and will always match.) The comparison has two modes: if N is preceded by a + or - sign (or is 0), the two signed numbers are compared. Otherwise, the absolute magnitudes are compared, ignoring sign.

code:REGEX : match by transaction code (eg check number)

cur:REGEX : match postings or transactions including any amounts whose currency/commodity symbol is fully matched by REGEX. (For a partial match, use .*REGEX.*). Note, to match characters which are regex-significant, like the dollar sign ($), you need to prepend \. And when using the command line you need to add one more level of quoting to hide it from the shell, so eg do: hledger print cur:'\$' or hledger print cur:\\$.

desc:REGEX : match transaction descriptions.

date:PERIODEXPR : match dates within the specified period. PERIODEXPR is a period expression (with no report interval). Examples: date:2016, date:thismonth, date:2000/2/1-2/15, date:lastweek-. If the --date2 command line flag is present, this matches secondary dates instead.

date2:PERIODEXPR : match secondary dates within the specified period.

depth:N : match (or display, depending on command) accounts at or above this depth

note:REGEX : match transaction notes (part of description right of |, or whole description when there's no |)

payee:REGEX : match transaction payee/payer names (part of description left of |, or whole description when there's no |)

real:, real:0 : match real or virtual postings respectively

status:, status:!, status:* : match unmarked, pending, or cleared transactions respectively

tag:REGEX[=REGEX] : match by tag name, and optionally also by tag value. Note a tag: query is considered to match a transaction if it matches any of the postings. Also remember that postings inherit the tags of their parent transaction.

The following special search term is used automatically in hledger-web, only:

inacct:ACCTNAME : tells hledger-web to show the transaction register for this account. Can be filtered further with acct etc.

Some of these can also be expressed as command-line options (eg depth:2 is equivalent to --depth 2). Generally you can mix options and query arguments, and the resulting query will be their intersection (perhaps excluding the -p/--period option).


The -B/--cost flag converts amounts to their cost or sale amount at transaction time, if they have a transaction price specified. If this flag is supplied, hledger will perform cost conversion first, and will apply any market price valuations (if requested) afterwards.


Instead of reporting amounts in their original commodity, hledger can convert them to cost/sale amount (using the conversion rate recorded in the transaction), and/or to market value (using some market price on a certain date). This is controlled by the --value=TYPE[,COMMODITY] option, which will be described below. We also provide the simpler -V and -X COMMODITY options, and often one of these is all you need:

-V: Value

The -V/--market flag converts amounts to market value in their default valuation commodity, using the market prices in effect on the valuation date(s), if any. More on these in a minute.

-X: Value in specified commodity

The -X/--exchange=COMM option is like -V, except you tell it which currency you want to convert to, and it tries to convert everything to that.

Valuation date

Since market prices can change from day to day, market value reports have a valuation date (or more than one), which determines which market prices will be used.

For single period reports, if an explicit report end date is specified, that will be used as the valuation date; otherwise the valuation date is the journal's end date.

For multiperiod reports, each column/period is valued on the last day of the period, by default.

Market prices

To convert a commodity A to its market value in another commodity B, hledger looks for a suitable market price (exchange rate) as follows, in this order of preference :

  1. A declared market price or inferred market price: A's latest market price in B on or before the valuation date as declared by a P directive, or (with the --infer-market-price flag) inferred from transaction prices.

  2. A reverse market price: the inverse of a declared or inferred market price from B to A.

  3. A forward chain of market prices: a synthetic price formed by combining the shortest chain of "forward" (only 1 above) market prices, leading from A to B.

  4. Any chain of market prices: a chain of any market prices, including both forward and reverse prices (1 and 2 above), leading from A to B.

There is a limit to the length of these price chains; if hledger reaches that length without finding a complete chain or exhausting all possibilities, it will give up (with a "gave up" message visible in --debug=2 output). That limit is currently 1000.

Amounts for which no suitable market price can be found, are not converted.

--infer-market-price: market prices from transactions

Normally, market value in hledger is fully controlled by, and requires, P directives in your journal. Since adding and updating those can be a chore, and since transactions usually take place at close to market value, why not use the recorded transaction prices as additional market prices (as Ledger does) ? We could produce value reports without needing P directives at all.

Adding the --infer-market-price flag to -V, -X or --value enables this. So for example, hledger bs -V --infer-market-price will get market prices both from P directives and from transactions. (And if both occur on the same day, the P directive takes precedence).

There is a downside: value reports can sometimes be affected in confusing/undesired ways by your journal entries. If this happens to you, read all of this Valuation section carefully, and try adding --debug or --debug=2 to troubleshoot.

--infer-market-price can infer market prices from:

  • multicommodity transactions with explicit prices (@/@@)

  • multicommodity transactions with implicit prices (no @, two commodities, unbalanced). (With these, the order of postings matters. hledger print -x can be useful for troubleshooting.)

  • but not, currently, from "more correct" multicommodity transactions (no @, multiple commodities, balanced).

Valuation commodity

When you specify a valuation commodity (-X COMM or --value TYPE,COMM):
hledger will convert all amounts to COMM, wherever it can find a suitable market price (including by reversing or chaining prices).

When you leave the valuation commodity unspecified (-V or --value TYPE):
For each commodity A, hledger picks a default valuation commodity as follows, in this order of preference:

  1. The price commodity from the latest P-declared market price for A on or before valuation date.

  2. The price commodity from the latest P-declared market price for A on any date. (Allows conversion to proceed when there are inferred prices before the valuation date.)

  3. If there are no P directives at all (any commodity or date) and the --infer-market-price flag is used: the price commodity from the latest transaction-inferred price for A on or before valuation date.

This means:

  • If you have P directives, they determine which commodities -V will convert, and to what.

  • If you have no P directives, and use the --infer-market-price flag, transaction prices determine it.

Amounts for which no valuation commodity can be found are not converted.

Simple valuation examples

Here are some quick examples of -V:

; one euro is worth this many dollars from nov 1
P 2016/11/01 € $1.10

; purchase some euros on nov 3
    assets:euros        €100

; the euro is worth fewer dollars by dec 21
P 2016/12/21 € $1.03

How many euros do I have ?

$ hledger -f t.j bal -N euros
                €100  assets:euros

What are they worth at end of nov 3 ?

$ hledger -f t.j bal -N euros -V -e 2016/11/4
             $110.00  assets:euros

What are they worth after 2016/12/21 ? (no report end date specified, defaults to today)

$ hledger -f t.j bal -N euros -V
             $103.00  assets:euros

--value: Flexible valuation

-V and -X are special cases of the more general --value option:

 --value=TYPE[,COMM]  TYPE is then, end, now or YYYY-MM-DD.
                      COMM is an optional commodity symbol.
                      Shows amounts converted to:
                      - default valuation commodity (or COMM) using market prices at posting dates
                      - default valuation commodity (or COMM) using market prices at period end(s)
                      - default valuation commodity (or COMM) using current market prices
                      - default valuation commodity (or COMM) using market prices at some date

The TYPE part selects cost or value and valuation date:

--value=then : Convert amounts to their value in the default valuation commodity, using market prices on each posting's date.

--value=end : Convert amounts to their value in the default valuation commodity, using market prices on the last day of the report period (or if unspecified, the journal's end date); or in multiperiod reports, market prices on the last day of each subperiod.

--value=now : Convert amounts to their value in the default valuation commodity using current market prices (as of when report is generated).

--value=YYYY-MM-DD : Convert amounts to their value in the default valuation commodity using market prices on this date.

To select a different valuation commodity, add the optional ,COMM part: a comma, then the target commodity's symbol. Eg: --value=now,EUR. hledger will do its best to convert amounts to this commodity, deducing market prices as described above.

More valuation examples

Here are some examples showing the effect of --value, as seen with print:

P 2000-01-01 A  1 B
P 2000-02-01 A  2 B
P 2000-03-01 A  3 B
P 2000-04-01 A  4 B

  (a)      1 A @ 5 B

  (a)      1 A @ 6 B

  (a)      1 A @ 7 B

Show the cost of each posting:

$ hledger -f- print --cost
    (a)             5 B

    (a)             6 B

    (a)             7 B

Show the value as of the last day of the report period (2000-02-29):

$ hledger -f- print --value=end date:2000/01-2000/03
    (a)             2 B

    (a)             2 B

With no report period specified, that shows the value as of the last day of the journal (2000-03-01):

$ hledger -f- print --value=end
    (a)             3 B

    (a)             3 B

    (a)             3 B

Show the current value (the 2000-04-01 price is still in effect today):

$ hledger -f- print --value=now
    (a)             4 B

    (a)             4 B

    (a)             4 B

Show the value on 2000/01/15:

$ hledger -f- print --value=2000-01-15
    (a)             1 B

    (a)             1 B

    (a)             1 B

You may need to explicitly set a commodity's display style, when reverse prices are used. Eg this output might be surprising:

P 2000-01-01 A 2B

  a  1B
$ hledger print -x -X A
    a               0
    b               0

Explanation: because there's no amount or commodity directive specifying a display style for A, 0.5A gets the default style, which shows no decimal digits. Because the displayed amount looks like zero, the commodity symbol and minus sign are not displayed either. Adding a commodity directive sets a more useful display style for A:

P 2000-01-01 A 2B
commodity 0.00A

  a  1B
$ hledger print -X A
    a           0.50A
    b          -0.50A

Effect of valuation on reports

Here is a reference for how valuation is supposed to affect each part of hledger's reports (and a glossary). (It's wide, you'll have to scroll sideways.) It may be useful when troubleshooting. If you find problems, please report them, ideally with a reproducible example. Related: #329, #1083.

Report type-B, --cost-V, -X--value=then--value=end--value=DATE, --value=now
posting amountscostvalue at report end or todayvalue at posting datevalue at report or journal endvalue at DATE/today
balance assertions/assignmentsunchangedunchangedunchangedunchangedunchanged

starting balance (-H)costvalue at day before report or journal startvalued at day each historical posting was madevalue at day before report or journal startvalue at DATE/today
posting amountscostvalue at report end or todayvalue at posting datevalue at report or journal endvalue at DATE/today
summary posting amounts with report intervalsummarised costvalue at period endssum of postings in interval, valued at interval startvalue at period endsvalue at DATE/today
running total/averagesum/average of displayed valuessum/average of displayed valuessum/average of displayed valuessum/average of displayed valuessum/average of displayed values

balance (bs, bse, cf, is)
balance changessums of costsvalue at report end or today of sums of postingsvalue at posting datevalue at report or journal end of sums of postingsvalue at DATE/today of sums of postings
budget amounts (--budget)like balance changeslike balance changeslike balance changeslike balanceslike balance changes
grand totalsum of displayed valuessum of displayed valuessum of displayed valuedsum of displayed valuessum of displayed values

balance (bs, bse, cf, is) with report interval
starting balances (-H)sums of costs of postings before report startvalue at report start of sums of all postings before report startsums of values of postings before report start at respective posting datesvalue at report start of sums of all postings before report startsums of postings before report start
balance changes (bal, is, bs --change, cf --change)sums of costs of postings in periodsame as --value=endsums of values of postings in period at respective posting datesbalance change in each period, valued at period endsvalue at DATE/today of sums of postings
end balances (bal -H, is --H, bs, cf)sums of costs of postings from before report start to period endsame as --value=endsums of values of postings from before period start to period end at respective posting datesperiod end balances, valued at period endsvalue at DATE/today of sums of postings
budget amounts (--budget)like balance changes/end balanceslike balance changes/end balanceslike balance changes/end balanceslike balanceslike balance changes/end balances
row totals, row averages (-T, -A)sums, averages of displayed valuessums, averages of displayed valuessums, averages of displayed valuessums, averages of displayed valuessums, averages of displayed values
column totalssums of displayed valuessums of displayed valuessums of displayed valuessums of displayed valuessums of displayed values
grand total, grand averagesum, average of column totalssum, average of column totalssum, average of column totalssum, average of column totalssum, average of column totals

--cumulative is omitted to save space, it works like -H but with a zero starting balance.


cost : calculated using price(s) recorded in the transaction(s).

value : market value using available market price declarations, or the unchanged amount if no conversion rate can be found.

report start : the first day of the report period specified with -b or -p or date:, otherwise today.

report or journal start : the first day of the report period specified with -b or -p or date:, otherwise the earliest transaction date in the journal, otherwise today.

report end : the last day of the report period specified with -e or -p or date:, otherwise today.

report or journal end : the last day of the report period specified with -e or -p or date:, otherwise the latest transaction date in the journal, otherwise today.

report interval : a flag (-D/-W/-M/-Q/-Y) or period expression that activates the report's multi-period mode (whether showing one or many subperiods).


Normally hledger sums amounts, and organizes them in a hierarchy, based on account name. The --pivot FIELD option causes it to sum and organize hierarchy based on the value of some other field instead. FIELD can be: code, description, payee, note, or the full name (case insensitive) of any tag. As with account names, values containing colon:separated:parts will be displayed hierarchically in reports.

--pivot is a general option affecting all reports; you can think of hledger transforming the journal before any other processing, replacing every posting's account name with the value of the specified field on that posting, inheriting it from the transaction or using a blank value if it's not present.

An example:

2016/02/16 Member Fee Payment
    assets:bank account                    2 EUR
    income:member fees                    -2 EUR  ; member: John Doe

Normal balance report showing account names:

$ hledger balance
               2 EUR  assets:bank account
              -2 EUR  income:member fees

Pivoted balance report, using member: tag values instead:

$ hledger balance --pivot member
               2 EUR
              -2 EUR  John Doe

One way to show only amounts with a member: value (using a query, described below):

$ hledger balance --pivot member tag:member=.
              -2 EUR  John Doe
              -2 EUR

Another way (the acct: query matches against the pivoted "account name"):

$ hledger balance --pivot member acct:.
              -2 EUR  John Doe
              -2 EUR


Output destination

hledger commands send their output to the terminal by default. You can of course redirect this, eg into a file, using standard shell syntax:

$ hledger print > foo.txt

Some commands (print, register, stats, the balance commands) also provide the -o/--output-file option, which does the same thing without needing the shell. Eg:

$ hledger print -o foo.txt
$ hledger print -o -        # write to stdout (the default)

Output format

Some commands (print, register, the balance commands) offer a choice of output format. In addition to the usual plain text format (txt), there are CSV (csv), HTML (html), JSON (json) and SQL (sql). This is controlled by the -O/--output-format option:

$ hledger print -O csv

or, by a file extension specified with -o/--output-file:

$ hledger balancesheet -o foo.html   # write HTML to foo.html

The -O option can be used to override the file extension if needed:

$ hledger balancesheet -o foo.txt -O html   # write HTML to foo.txt

Some notes about JSON output:

  • This feature is marked experimental, and not yet much used; you should expect our JSON to evolve. Real-world feedback is welcome.

  • Our JSON is rather large and verbose, as it is quite a faithful representation of hledger's internal data types. To understand the JSON, read the Haskell type definitions, which are mostly in https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Data/Types.hs.

  • hledger represents quantities as Decimal values storing up to 255 significant digits, eg for repeating decimals. Such numbers can arise in practice (from automatically-calculated transaction prices), and would break most JSON consumers. So in JSON, we show quantities as simple Numbers with at most 10 decimal places. We don't limit the number of integer digits, but that part is under your control. We hope this approach will not cause problems in practice; if you find otherwise, please let us know. (Cf #1195)

Notes about SQL output:

  • SQL output is also marked experimental, and much like JSON could use real-world feedback.

  • SQL output is expected to work with sqlite, MySQL and PostgreSQL

  • SQL output is structured with the expectations that statements will be executed in the empty database. If you already have tables created via SQL output of hledger, you would probably want to either clear tables of existing data (via delete or truncate SQL statements) or drop tables completely as otherwise your postings will be duped.


hledger provides a number of commands for producing reports and managing your data. Run hledger with no arguments to list the commands available, and hledger CMD to run a command. CMD can be the full command name, or its standard abbreviation shown in the commands list, or any unambiguous prefix of the name. Eg: hledger bal.

Here are the built-in commands, with the most often-used in bold:

Data entry:

These data entry commands are the only ones which can modify your journal file.

  • add - add transactions using guided prompts
  • import - add any new transactions from other files (eg csv)

Data management:

  • check - check for various kinds of issue in the data
  • close (equity) - generate balance-resetting transactions
  • diff - compare account transactions in two journal files
  • rewrite - generate extra postings, similar to print --auto

Financial statements:

Miscellaneous reports:

  • accounts - show account names
  • activity - show postings-per-interval bar charts
  • balance (bal) - show balance changes/end balances/budgets in any accounts
  • codes - show transaction codes
  • commodities - show commodity/currency symbols
  • descriptions - show unique transaction descriptions
  • files - show input file paths
  • help - show hledger user manuals in several formats
  • notes - show unique note segments of transaction descriptions
  • payees - show unique payee segments of transaction descriptions
  • prices - show market price records
  • print - show transactions (journal entries)
  • print-unique - show only transactions with unique descriptions
  • register (reg) - show postings in one or more accounts & running total
  • register-match - show a recent posting that best matches a description
  • stats - show journal statistics
  • tags - show tag names
  • test - run self tests

Add-on commands:

Programs or scripts named hledger-SOMETHING in your PATH are add-on commands; these appear in the commands list with a + mark. Two of these are maintained and released with hledger:

  • ui - an efficient terminal interface (TUI) for hledger
  • web - a simple web interface (WUI) for hledger

And these add-ons are maintained separately:

  • iadd - a more interactive alternative for the add command
  • interest - generates interest transactions according to various schemes
  • stockquotes - downloads market prices for your commodities from AlphaVantage (experimental)

Next, the detailed command docs, in alphabetical order.


Show account names.

This command lists account names, either declared with account directives (--declared), posted to (--used), or both (the default). With query arguments, only matched account names and account names referenced by matched postings are shown. It shows a flat list by default. With --tree, it uses indentation to show the account hierarchy. In flat mode you can add --drop N to omit the first few account name components. Account names can be depth-clipped with depth:N or --depth N or -N.


$ hledger accounts


Show an ascii barchart of posting counts per interval.

The activity command displays an ascii histogram showing transaction counts by day, week, month or other reporting interval (by day is the default). With query arguments, it counts only matched transactions.


$ hledger activity --quarterly
2008-01-01 **
2008-04-01 *******
2008-10-01 **


Prompt for transactions and add them to the journal. Any arguments will be used as default inputs for the first N prompts.

Many hledger users edit their journals directly with a text editor, or generate them from CSV. For more interactive data entry, there is the add command, which prompts interactively on the console for new transactions, and appends them to the journal file (if there are multiple -f FILE options, the first file is used.) Existing transactions are not changed. This is the only hledger command that writes to the journal file.

To use it, just run hledger add and follow the prompts. You can add as many transactions as you like; when you are finished, enter . or press control-d or control-c to exit.


  • add tries to provide useful defaults, using the most similar (by description) recent transaction (filtered by the query, if any) as a template.
  • You can also set the initial defaults with command line arguments.
  • Readline-style edit keys can be used during data entry.
  • The tab key will auto-complete whenever possible - accounts, descriptions, dates (yesterday, today, tomorrow). If the input area is empty, it will insert the default value.
  • If the journal defines a default commodity, it will be added to any bare numbers entered.
  • A parenthesised transaction code may be entered following a date.
  • Comments and tags may be entered following a description or amount.
  • If you make a mistake, enter < at any prompt to go one step backward.
  • Input prompts are displayed in a different colour when the terminal supports it.

Example (see the tutorial for a detailed explanation):

$ hledger add
Adding transactions to journal file /src/hledger/examples/sample.journal
Any command line arguments will be used as defaults.
Use tab key to complete, readline keys to edit, enter to accept defaults.
An optional (CODE) may follow transaction dates.
An optional ; COMMENT may follow descriptions or amounts.
If you make a mistake, enter < at any prompt to go one step backward.
To end a transaction, enter . when prompted.
To quit, enter . at a date prompt or press control-d or control-c.
Date [2015/05/22]: 
Description: supermarket
Account 1: expenses:food
Amount  1: $10
Account 2: assets:checking
Amount  2 [$-10.0]: 
Account 3 (or . or enter to finish this transaction): .
2015/05/22 supermarket
    expenses:food             $10
    assets:checking        $-10.0

Save this transaction to the journal ? [y]: 
Starting the next transaction (. or ctrl-D/ctrl-C to quit)
Date [2015/05/22]: <CTRL-D> $

On Microsoft Windows, the add command makes sure that no part of the file path ends with a period, as that would cause problems (#1056).


aregister, areg

Show the transactions and running historical balance in an account, with each line item representing one transaction.

aregister shows the transactions affecting a particular account and its subaccounts, with each line item representing a whole transaction - as in bank statements, hledger-ui, hledger-web and other accounting apps.

Note this is unlike the register command, which shows individual postings and does not always show a single account or a historical balance.

A reminder, "historical" balances include any balance from transactions before the report start date, so (if opening balances are recorded correctly) aregister will show the real-world balances of an account, as you would see in a bank statement.

As a quick rule of thumb, use aregister for reconciling real-world asset/liability accounts and register for reviewing detailed revenues/expenses.

aregister shows the register for just one account (and its subaccounts). This account must be specified as the first argument. You can write either the full account name, or a case-insensitive regular expression which will select the alphabetically first matched account. (Eg if you have assets:aaa:checking and assets:bbb:checking accounts, hledger areg checking would select assets:aaa:checking.)

Any additional arguments form a query which will filter the transactions shown.

Each aregister line item shows:

  • the transaction's date (or the relevant posting's date if different, see below)
  • the names of all the other account(s) involved in this transaction (probably abbreviated)
  • the total change to this account's balance from this transaction
  • the account's historical running balance after this transaction.

Transactions making a net change of zero are not shown by default; add the -E/--empty flag to show them.

aregister ignores a depth limit, so its final total will always match a balance report with similar arguments.

This command also supports the output destination and output format options The output formats supported are txt, csv, and json.

aregister and custom posting dates

Transactions whose date is outside the report period can still be shown, if they have a posting to this account dated inside the report period. (And in this case it's the posting date that is shown.) This ensures that aregister can show an accurate historical running balance, matching the one shown by register -H with the same arguments.

To filter strictly by transaction date instead, add the --txn-dates flag. If you use this flag and some of your postings have custom dates, it's probably best to assume the running balance is wrong.


Show all transactions and historical running balance in the first account whose name contains "checking":

$ hledger areg checking

Show transactions and historical running balance in all asset accounts during july:

$ hledger areg assets date:jul


balance, bal
Show accounts and their balances.

balance is one of hledger's oldest and most versatile commands, for listing account balances, balance changes, values, value changes and more, during one time period or many. Generally it shows a table, with rows representing accounts, and columns representing periods.

Note there are some higher-level variants of the balance command with convenient defaults, which can be simpler to use: balancesheet, balancesheetequity, cashflow and incomestatement. When you need more control, then use balance.

balance features

Here's a quick overview of the balance command's features, followed by more detailed descriptions and examples. Many of these work with the higher-level commands as well.

balance can show..

..and their..

  • balance changes (the default)
  • or actual and planned balance changes (--budget)
  • or value of balance changes (-V)
  • or change of balance values (--valuechange)



  • per period (the default)
  • or accumulated since report start date (--cumulative)
  • or accumulated since account creation (--historical/-H)

..possibly converted to..


  • totals (-T), averages (-A), percentages (-%), inverted sign (--invert)
  • rows and columns swapped (--transpose)
  • another field used as account name (--pivot)
  • custom-formatted line items (single-period reports only) (--format)

This command supports the output destination and output format options, with output formats txt, csv, json, and (multi-period reports only:) html. In txt output in a colour-supporting terminal, negative amounts are shown in red.

Simple balance report

With no arguments, balance shows a list of all accounts and their change of balance - ie, the sum of posting amounts, both inflows and outflows - during the entire period of the journal. For real-world accounts, this should also match their end balance at the end of the journal period (more on this below).

Accounts are sorted by declaration order if any, and then alphabetically by account name. For instance, using examples/sample.journal:

$ hledger bal
                  $1  assets:bank:saving
                 $-2  assets:cash
                  $1  expenses:food
                  $1  expenses:supplies
                 $-1  income:gifts
                 $-1  income:salary
                  $1  liabilities:debts

Accounts with a zero balance (and no non-zero subaccounts, in tree mode

  • see below) are hidden by default. Use -E/--empty to show them (revealing assets:bank:checking here):
$ hledger -f examples/sample.journal  bal  -E
                   0  assets:bank:checking
                  $1  assets:bank:saving
                 $-2  assets:cash
                  $1  expenses:food
                  $1  expenses:supplies
                 $-1  income:gifts
                 $-1  income:salary
                  $1  liabilities:debts

The total of the amounts displayed is shown as the last line, unless -N/--no-total is used.

Filtered balance report

You can show fewer accounts, a different time period, totals from cleared transactions only, etc. by using query arguments or options to limit the postings being matched. Eg:

$ hledger bal --cleared assets date:200806
                 $-2  assets:cash

List or tree mode

By default, or with -l/--flat, accounts are shown as a flat list with their full names visible, as in the examples above.

With -t/--tree, the account hierarchy is shown, with subaccounts' "leaf" names indented below their parent:

$ hledger balance
                 $-1  assets
                  $1    bank:saving
                 $-2    cash
                  $2  expenses
                  $1    food
                  $1    supplies
                 $-2  income
                 $-1    gifts
                 $-1    salary
                  $1  liabilities:debts


  • "Boring" accounts are combined with their subaccount for more compact output, unless --no-elide is used. Boring accounts have no balance of their own and just one subaccount (eg assets:bank and liabilities above).

  • All balances shown are "inclusive", ie including the balances from all subaccounts. Note this means some repetition in the output, which requires explanation when sharing reports with non-plaintextaccounting-users. A tree mode report's final total is the sum of the top-level balances shown, not of all the balances shown.

  • Each group of sibling accounts (ie, under a common parent) is sorted separately.

Depth limiting

With a depth:N query, or --depth N option, or just -N, balance reports will show accounts only to the specified depth, hiding the deeper subaccounts. Account balances at the depth limit always include the balances from any hidden subaccounts (even in list mode). This can be useful for getting an overview. Eg, limiting to depth 1:

$ hledger balance -N -1
                 $-1  assets
                  $2  expenses
                 $-2  income
                  $1  liabilities

You can also hide top-level account name parts, using --drop N. This can be useful for hiding repetitive top-level account names:

$ hledger bal expenses --drop 1
                  $1  food
                  $1  supplies

Multi-period balance report

With a report interval (set by the -D/--daily, -W/--weekly, -M/--monthly, -Q/--quarterly, -Y/--yearly, or -p/--period flag), balance shows a tabular report, with columns representing successive time periods (and a title):

$ hledger balance --quarterly income expenses -E
Balance changes in 2008:

                   ||  2008q1  2008q2  2008q3  2008q4 
 expenses:food     ||       0      $1       0       0 
 expenses:supplies ||       0      $1       0       0 
 income:gifts      ||       0     $-1       0       0 
 income:salary     ||     $-1       0       0       0 
                   ||     $-1      $1       0       0 


  • The report's start/end dates will be expanded, if necessary, to fully encompass the displayed subperiods (so that the first and last subperiods have the same duration as the others).
  • Leading and trailing periods (columns) containing all zeroes are not shown, unless -E/--empty is used.
  • Accounts (rows) containing all zeroes are not shown, unless -E/--empty is used.
  • Amounts with many commodities are shown in abbreviated form, unless --no-elide is used. (experimental)
  • Average and/or total columns can be added with the -A/--average and -T/--row-total flags.
  • The --transpose flag can be used to exchange rows and columns.
  • The --pivot FIELD option causes a different transaction field to be used as "account name". See PIVOTING.

Multi-period reports with many periods can be too wide for easy viewing in the terminal. Here are some ways to handle that:

  • Hide the totals row with -N/--no-total
  • Convert to a single currency with -V
  • Maximize the terminal window
  • Reduce the terminal's font size
  • View with a pager like less, eg: hledger bal -D --color=yes | less -RS
  • Output as CSV and use a CSV viewer like visidata (hledger bal -D -O csv | vd -f csv), Emacs' csv-mode (M-x csv-mode, C-c C-a), or a spreadsheet (hledger bal -D -o a.csv && open a.csv)
  • Output as HTML and view with a browser: hledger bal -D -o a.html && open a.html

Sorting by amount

With -S/--sort-amount, accounts with the largest (most positive) balances are shown first. Eg: hledger bal expenses -MAS shows your biggest averaged monthly expenses first.

Revenues and liability balances are typically negative, however, so -S shows these in reverse order. To work around this, you can add --invert to flip the signs. (Or, use one of the higher-level reports, which flip the sign automatically. Eg: hledger incomestatement -MAS).


With -%/--percent, balance reports show each account's value expressed as a percentage of the (column) total:

$ hledger bal expenses -Q -%
Balance changes in 2008:

                   || 2008Q1   2008Q2  2008Q3  2008Q4 
 expenses:food     ||      0   50.0 %       0       0 
 expenses:supplies ||      0   50.0 %       0       0 
                   ||      0  100.0 %       0       0 

Note it is not useful to calculate percentages if the amounts in a column have mixed signs. In this case, make a separate report for each sign, eg:

$ hledger bal -% amt:`>0`
$ hledger bal -% amt:`<0`

Similarly, if the amounts in a column have mixed commodities, convert them to one commodity with -B, -V, -X or --value, or make a separate report for each commodity:

$ hledger bal -% cur:\\$
$ hledger bal -% cur:€

Balance change, end balance

It's important to be clear on the meaning of the numbers shown in balance reports. Here is some terminology we use:

A balance change is the net amount added to, or removed from, an account during some period.

An end balance is the amount accumulated in an account as of some date (and some time, but hledger doesn't store that; assume end of day in your timezone). It is the sum of previous balance changes.

We call it a historical end balance if it includes all balance changes since the account was created. For a real world account, this means it will match the "historical record", eg the balances reported in your bank statements or bank web UI. (If they are correct!)

In general, balance changes are what you want to see when reviewing revenues and expenses, and historical end balances are what you want to see when reviewing or reconciling asset, liability and equity accounts.

balance shows balance changes by default. To see accurate historical end balances:

  1. Initialise account starting balances with an "opening balances" transaction (a transfer from equity to the account), unless the journal covers the account's full lifetime.

  2. Include all of of the account's prior postings in the report, by not specifying a report start date, or by using the -H/--historical flag. (-H causes report start date to be ignored when summing postings.)

Balance report types

For more flexible reporting, there are three important option groups:


The first two are the most important: calculation type selects the basic calculation to perform for each table cell, while accumulation type says which postings should be included in each cell's calculation. Typically one or both of these are selected by default, so you don't need to write them explicitly. A valuation type can be added if you want to convert the basic report to value or cost.

Calculation type:
The basic calculation to perform for each table cell. It is one of:

  • --sum : sum the posting amounts (default)
  • --budget : like --sum but also show a goal amount
  • --valuechange : show the change in period-end historical balance values

Accumulation type:
Which postings should be included in each cell's calculation. It is one of:

  • --change : postings from column start to column end, ie within the cell's period. Typically used to see revenues/expenses. (default for balance, incomestatement)

  • --cumulative : postings from report start to column end, eg to show changes accumulated since the report's start date. Rarely used.

  • --historical/-H : postings from journal start to column end, ie all postings from account creation to the end of the cell's period. Typically used to see historical end balances of assets/liabilities/equity. (default for balancesheet, balancesheetequity, cashflow)

Valuation type:
Which kind of valuation, valuation date(s) and optionally a target valuation commodity to use. It is one of:

  • no valuation, show amounts in their original commodities (default)
  • --value=cost[,COMM] : no valuation, show amounts converted to cost
  • --value=then[,COMM] : show value at transaction dates
  • --value=end[,COMM] : show value at period end date(s) (default with --valuechange)
  • --value=now[,COMM] : show value at today's date
  • --value=YYYY-MM-DD[,COMM] : show value at another date

or one of their aliases: --cost/-B, --market/-V or --exchange/-X.

Most combinations of these options should produce reasonable reports, but if you find any that seem wrong or misleading, let us know. The following restrictions are applied:

  • --valuechange implies --value=end
  • --valuechange makes --change the default when used with the balancesheet/balancesheetequity commands
  • --cumulative or --historical disables --row-total/-T

For reference, here is what the combinations of accumulation and valuation show:

Valuation: >
Accumulation: v
no valuation--value= then--value= end--value= YYYY-MM-DD /now
--changechange in periodsum of posting-date market values in periodperiod-end value of change in periodDATE-value of change in period
--cumulativechange from report start to period endsum of posting-date market values from report start to period endperiod-end value of change from report start to period endDATE-value of change from report start to period end
--historical /-Hchange from journal start to period end (historical end balance)sum of posting-date market values from journal start to period endperiod-end value of change from journal start to period endDATE-value of change from journal start to period end

Useful balance reports

Some frequently used balance options/reports are:

  • bal -M revenues expenses
    Show revenues/expenses in each month. Also available as the incomestatement command.

  • bal -M -H assets liabilities
    Show historical asset/liability balances at each month end. Also available as the balancesheet command.

  • bal -M -H assets liabilities equity
    Show historical asset/liability/equity balances at each month end. Also available as the balancesheetequity command.

  • bal -M assets not:receivable
    Show changes to liquid assets in each month. Also available as the cashflow command.


  • bal -M expenses -2 -SA
    Show monthly expenses summarised to depth 2 and sorted by average amount.

  • bal -M --budget expenses
    Show monthly expenses and budget goals.

  • bal -M --valuechange investments
    Show monthly change in market value of investment assets.

  • bal investments --valuechange -D date:lastweek amt:'>1000' -STA [--invert]
    Show top gainers [or losers] last week

Budget report

The --budget report type activates extra columns showing any budget goals for each account and period. The budget goals are defined by periodic transactions. This is very useful for comparing planned and actual income, expenses, time usage, etc.

For example, you can take average monthly expenses in the common expense categories to construct a minimal monthly budget:

;; Budget
~ monthly
  income  $2000
  expenses:food    $400
  expenses:bus     $50
  expenses:movies  $30

;; Two months worth of expenses
  income  $1950
  expenses:food    $396
  expenses:bus     $49
  expenses:movies  $30
  expenses:supplies  $20

  income  $2100
  expenses:food    $412
  expenses:bus     $53
  expenses:gifts   $100

You can now see a monthly budget report:

$ hledger balance -M --budget
Budget performance in 2017/11/01-2017/12/31:

                      ||                      Nov                       Dec 
 assets               || $-2445 [  99% of $-2480]  $-2665 [ 107% of $-2480] 
 assets:bank          || $-2445 [  99% of $-2480]  $-2665 [ 107% of $-2480] 
 assets:bank:checking || $-2445 [  99% of $-2480]  $-2665 [ 107% of $-2480] 
 expenses             ||   $495 [ 103% of   $480]    $565 [ 118% of   $480] 
 expenses:bus         ||    $49 [  98% of    $50]     $53 [ 106% of    $50] 
 expenses:food        ||   $396 [  99% of   $400]    $412 [ 103% of   $400] 
 expenses:movies      ||    $30 [ 100% of    $30]       0 [   0% of    $30] 
 income               ||  $1950 [  98% of  $2000]   $2100 [ 105% of  $2000] 
                      ||      0 [              0]       0 [              0] 

This is different from a normal balance report in several ways:

  • Only accounts with budget goals during the report period are shown, by default.

  • In each column, in square brackets after the actual amount, budget goal amounts are shown, and the actual/goal percentage. (Note: budget goals should be in the same commodity as the actual amount.)

  • All parent accounts are always shown, even in list mode. Eg assets, assets:bank, and expenses above.

  • Amounts always include all subaccounts, budgeted or unbudgeted, even in list mode.

This means that the numbers displayed will not always add up! Eg above, the expenses actual amount includes the gifts and supplies transactions, but the expenses:gifts and expenses:supplies accounts are not shown, as they have no budget amounts declared.

This can be confusing. When you need to make things clearer, use the -E/--empty flag, which will reveal all accounts including unbudgeted ones, giving the full picture. Eg:

$ hledger balance -M --budget --empty
Budget performance in 2017/11/01-2017/12/31:

                      ||                      Nov                       Dec 
 assets               || $-2445 [  99% of $-2480]  $-2665 [ 107% of $-2480] 
 assets:bank          || $-2445 [  99% of $-2480]  $-2665 [ 107% of $-2480] 
 assets:bank:checking || $-2445 [  99% of $-2480]  $-2665 [ 107% of $-2480] 
 expenses             ||   $495 [ 103% of   $480]    $565 [ 118% of   $480] 
 expenses:bus         ||    $49 [  98% of    $50]     $53 [ 106% of    $50] 
 expenses:food        ||   $396 [  99% of   $400]    $412 [ 103% of   $400] 
 expenses:gifts       ||      0                      $100                   
 expenses:movies      ||    $30 [ 100% of    $30]       0 [   0% of    $30] 
 expenses:supplies    ||    $20                         0                   
 income               ||  $1950 [  98% of  $2000]   $2100 [ 105% of  $2000] 
                      ||      0 [              0]       0 [              0] 

You can roll over unspent budgets to next period with --cumulative:

$ hledger balance -M --budget --cumulative
Budget performance in 2017/11/01-2017/12/31:

                      ||                      Nov                       Dec 
 assets               || $-2445 [  99% of $-2480]  $-5110 [ 103% of $-4960] 
 assets:bank          || $-2445 [  99% of $-2480]  $-5110 [ 103% of $-4960] 
 assets:bank:checking || $-2445 [  99% of $-2480]  $-5110 [ 103% of $-4960] 
 expenses             ||   $495 [ 103% of   $480]   $1060 [ 110% of   $960] 
 expenses:bus         ||    $49 [  98% of    $50]    $102 [ 102% of   $100] 
 expenses:food        ||   $396 [  99% of   $400]    $808 [ 101% of   $800] 
 expenses:movies      ||    $30 [ 100% of    $30]     $30 [  50% of    $60] 
 income               ||  $1950 [  98% of  $2000]   $4050 [ 101% of  $4000] 
                      ||      0 [              0]       0 [              0] 

For more examples and notes, see Budgeting.

Budget report start date

This might be a bug, but for now: when making budget reports, it's a good idea to explicitly set the report's start date to the first day of a reporting period, because a periodic rule like ~ monthly generates its transactions on the 1st of each month, and if your journal has no regular transactions on the 1st, the default report start date could exclude that budget goal, which can be a little surprising. Eg here the default report period is just the day of 2020-01-15:

~ monthly in 2020
  (expenses:food)  $500

  expenses:food    $400
$ hledger bal expenses --budget
Budget performance in 2020-01-15:

              || 2020-01-15 
 <unbudgeted> ||       $400 
              ||       $400 

To avoid this, specify the budget report's period, or at least the start date, with -b/-e/-p/date:, to ensure it includes the budget goal transactions (periodic transactions) that you want. Eg, adding -b 2020/1/1 to the above:

$ hledger bal expenses --budget -b 2020/1/1
Budget performance in 2020-01-01..2020-01-15:

               || 2020-01-01..2020-01-15 
 expenses:food ||     $400 [80% of $500] 
               ||     $400 [80% of $500] 
Nested budgets

You can add budgets to any account in your account hierarchy. If you have budgets on both parent account and some of its children, then budget(s) of the child account(s) would be added to the budget of their parent, much like account balances behave.

In the most simple case this means that once you add a budget to any account, all its parents would have budget as well.

To illustrate this, consider the following budget:

~ monthly from 2019/01
    expenses:personal             $1,000.00
    expenses:personal:electronics    $100.00

With this, monthly budget for electronics is defined to be $100 and budget for personal expenses is an additional $1000, which implicitly means that budget for both expenses:personal and expenses is $1100.

Transactions in expenses:personal:electronics will be counted both towards its $100 budget and $1100 of expenses:personal , and transactions in any other subaccount of expenses:personal would be counted towards only towards the budget of expenses:personal.

For example, let's consider these transactions:

~ monthly from 2019/01
    expenses:personal             $1,000.00
    expenses:personal:electronics    $100.00

2019/01/01 Google home hub
    expenses:personal:electronics          $90.00
    liabilities                           $-90.00

2019/01/02 Phone screen protector
    expenses:personal:electronics:upgrades          $10.00

2019/01/02 Weekly train ticket
    expenses:personal:train tickets       $153.00

2019/01/03 Flowers
    expenses:personal          $30.00

As you can see, we have transactions in expenses:personal:electronics:upgrades and expenses:personal:train tickets, and since both of these accounts are without explicitly defined budget, these transactions would be counted towards budgets of expenses:personal:electronics and expenses:personal accordingly:

$ hledger balance --budget -M
Budget performance in 2019/01:

                               ||                           Jan 
 expenses                      ||  $283.00 [  26% of  $1100.00] 
 expenses:personal             ||  $283.00 [  26% of  $1100.00] 
 expenses:personal:electronics ||  $100.00 [ 100% of   $100.00] 
 liabilities                   || $-283.00 [  26% of $-1100.00] 
                               ||        0 [                 0] 

And with --empty, we can get a better picture of budget allocation and consumption:

$ hledger balance --budget -M --empty
Budget performance in 2019/01:

                                        ||                           Jan 
 expenses                               ||  $283.00 [  26% of  $1100.00] 
 expenses:personal                      ||  $283.00 [  26% of  $1100.00] 
 expenses:personal:electronics          ||  $100.00 [ 100% of   $100.00] 
 expenses:personal:electronics:upgrades ||   $10.00                      
 expenses:personal:train tickets        ||  $153.00                      
 liabilities                            || $-283.00 [  26% of $-1100.00] 
                                        ||        0 [                 0] 

Customising single-period balance reports

For single-period balance reports displayed in the terminal (only), you can use --format FMT to customise the format and content of each line. Eg:

$ hledger balance --format "%20(account) %12(total)"
              assets          $-1
         bank:saving           $1
                cash          $-2
            expenses           $2
                food           $1
            supplies           $1
              income          $-2
               gifts          $-1
              salary          $-1
   liabilities:debts           $1

The FMT format string (plus a newline) specifies the formatting applied to each account/balance pair. It may contain any suitable text, with data fields interpolated like so:


  • MIN pads with spaces to at least this width (optional)

  • MAX truncates at this width (optional)

  • FIELDNAME must be enclosed in parentheses, and can be one of:

    • depth_spacer - a number of spaces equal to the account's depth, or if MIN is specified, MIN * depth spaces.
    • account - the account's name
    • total - the account's balance/posted total, right justified

Also, FMT can begin with an optional prefix to control how multi-commodity amounts are rendered:

  • %_ - render on multiple lines, bottom-aligned (the default)
  • %^ - render on multiple lines, top-aligned
  • %, - render on one line, comma-separated

There are some quirks. Eg in one-line mode, %(depth_spacer) has no effect, instead %(account) has indentation built in. Experimentation may be needed to get pleasing results.

Some example formats:

  • %(total) - the account's total
  • %-20.20(account) - the account's name, left justified, padded to 20 characters and clipped at 20 characters
  • %,%-50(account) %25(total) - account name padded to 50 characters, total padded to 20 characters, with multiple commodities rendered on one line
  • %20(total) %2(depth_spacer)%-(account) - the default format for the single-column balance report


balancesheet, bs
This command displays a balance sheet, showing historical ending balances of asset and liability accounts. (To see equity as well, use the balancesheetequity command.) Amounts are shown with normal positive sign, as in conventional financial statements.

The asset and liability accounts shown are those accounts declared with the Asset or Cash or Liability type, or otherwise all accounts under a top-level asset or liability account (case insensitive, plurals allowed).


$ hledger balancesheet
Balance Sheet

                 $-1  assets
                  $1    bank:saving
                 $-2    cash

                  $1  liabilities:debts


This command is a higher-level variant of the balance command, and supports many of that command's features, such as multi-period reports. It is similar to hledger balance -H assets liabilities, but with smarter account detection, and liabilities displayed with their sign flipped.

This command also supports the output destination and output format options The output formats supported are txt, csv, html, and (experimental) json.


balancesheetequity, bse
This command displays a balance sheet, showing historical ending balances of asset, liability and equity accounts. Amounts are shown with normal positive sign, as in conventional financial statements.

The asset, liability and equity accounts shown are those accounts declared with the Asset, Cash, Liability or Equity type, or otherwise all accounts under a top-level asset, liability or equity account (case insensitive, plurals allowed).


$ hledger balancesheetequity
Balance Sheet With Equity

                 $-2  assets
                  $1    bank:saving
                 $-3    cash

                  $1  liabilities:debts

          $1  equity:owner


This command is a higher-level variant of the balance command, and supports many of that command's features, such as multi-period reports. It is similar to hledger balance -H assets liabilities equity, but with smarter account detection, and liabilities/equity displayed with their sign flipped.

This command also supports the output destination and output format options The output formats supported are txt, csv, html, and (experimental) json.


cashflow, cf
This command displays a cashflow statement, showing the inflows and outflows affecting "cash" (ie, liquid) assets. Amounts are shown with normal positive sign, as in conventional financial statements.

The "cash" accounts shown are those accounts declared with the Cash type, or otherwise all accounts under a top-level asset account (case insensitive, plural allowed) which do not have fixed, investment, receivable or A/R in their name.


$ hledger cashflow
Cashflow Statement

Cash flows:
                 $-1  assets
                  $1    bank:saving
                 $-2    cash


This command is a higher-level variant of the balance command, and supports many of that command's features, such as multi-period reports. It is similar to hledger balance assets not:fixed not:investment not:receivable, but with smarter account detection.

This command also supports the output destination and output format options The output formats supported are txt, csv, html, and (experimental) json.


Check for various kinds of errors in your data.

hledger provides a number of built-in error checks to help prevent problems in your data. Some of these are run automatically; or, you can use this check command to run them on demand, with no output and a zero exit code if all is well. Specify their names (or a prefix) as argument(s).

Some examples:

hledger check      # basic checks
hledger check -s   # basic + strict checks
hledger check ordereddates payees  # basic + two other checks

Here are the checks currently available:

Basic checks

These checks are always run automatically, by (almost) all hledger commands, including check:

  • parseable - data files are well-formed and can be successfully parsed

  • autobalanced - all transactions are balanced, inferring missing amounts where necessary, and possibly converting commodities using transaction prices or automatically-inferred transaction prices

  • assertions - all balance assertions in the journal are passing. (This check can be disabled with -I/--ignore-assertions.)

Strict checks

These additional checks are run when the -s/--strict (strict mode) flag is used. Or, they can be run by giving their names as arguments to check:

Other checks

These checks can be run only by giving their names as arguments to check. They are more specialised and not desirable for everyone, therefore optional:

  • ordereddates - transactions are ordered by date in each file

  • payees - all payees used by transactions have been declared

  • uniqueleafnames - all account leaf names are unique

Custom checks

A few more checks are are available as separate add-on commands, in https://github.com/simonmichael/hledger/tree/master/bin:

  • hledger-check-tagfiles - all tag values containing / (a forward slash) exist as file paths

  • hledger-check-fancyassertions - more complex balance assertions are passing

You could make similar scripts to perform your own custom checks. See: Cookbook -> Scripting.


close, equity
Prints a "closing balances" transaction and an "opening balances" transaction that bring account balances to and from zero, respectively. These can be added to your journal file(s), eg to bring asset/liability balances forward into a new journal file, or to close out revenues/expenses to retained earnings at the end of a period.

You can print just one of these transactions by using the --close or --open flag. You can customise their descriptions with the --close-desc and --open-desc options.

One amountless posting to "equity:opening/closing balances" is added to balance the transactions, by default. You can customise this account name with --close-acct and --open-acct; if you specify only one of these, it will be used for both.

With --x/--explicit, the equity posting's amount will be shown. And if it involves multiple commodities, a posting for each commodity will be shown, as with the print command.

With --interleaved, the equity postings are shown next to the postings they balance, which makes troubleshooting easier.

By default, transaction prices in the journal are ignored when generating the closing/opening transactions. With --show-costs, this cost information is preserved (balance -B reports will be unchanged after the transition). Separate postings are generated for each cost in each commodity. Note this can generate very large journal entries, if you have many foreign currency or investment transactions.

close usage

If you split your journal files by time (eg yearly), you will typically run this command at the end of the year, and save the closing transaction as last entry of the old file, and the opening transaction as the first entry of the new file. This makes the files self contained, so that correct balances are reported no matter which of them are loaded. Ie, if you load just one file, the balances are initialised correctly; or if you load several files, the redundant closing/opening transactions cancel each other out. (They will show up in print or register reports; you can exclude them with a query like not:desc:'(opening|closing) balances'.)

If you're running a business, you might also use this command to "close the books" at the end of an accounting period, transferring income statement account balances to retained earnings. (You may want to change the equity account name to something like "equity:retained earnings".)

By default, the closing transaction is dated yesterday, the balances are calculated as of end of yesterday, and the opening transaction is dated today. To close on some other date, use: hledger close -e OPENINGDATE. Eg, to close/open on the 2018/2019 boundary, use -e 2019. You can also use -p or date:PERIOD (any starting date is ignored).

Both transactions will include balance assertions for the closed/reopened accounts. You probably shouldn't use status or realness filters (like -C or -R or status:) with this command, or the generated balance assertions will depend on these flags. Likewise, if you run this command with --auto, the balance assertions will probably always require --auto.


Carrying asset/liability balances into a new file for 2019:

$ hledger close -f 2018.journal -e 2019 assets liabilities --open
    # (copy/paste the output to the start of your 2019 journal file)
$ hledger close -f 2018.journal -e 2019 assets liabilities --close
    # (copy/paste the output to the end of your 2018 journal file)


$ hledger bs -f 2019.journal                   # one file - balances are correct
$ hledger bs -f 2018.journal -f 2019.journal   # two files - balances still correct
$ hledger bs -f 2018.journal not:desc:closing  # to see year-end balances, must exclude closing txn

Transactions spanning the closing date can complicate matters, breaking balance assertions:

2018/12/30 a purchase made in 2018, clearing the following year
    expenses:food          5
    assets:bank:checking  -5  ; [2019/1/2]

Here's one way to resolve that:

; in 2018.journal:
2018/12/30 a purchase made in 2018, clearing the following year
    expenses:food          5

; in 2019.journal:
2019/1/2 clearance of last year's pending transactions
    liabilities:pending    5 = 0


List the codes seen in transactions, in the order parsed.

This command prints the value of each transaction's code field, in the order transactions were parsed. The transaction code is an optional value written in parentheses between the date and description, often used to store a cheque number, order number or similar.

Transactions aren't required to have a code, and missing or empty codes will not be shown by default. With the -E/--empty flag, they will be printed as blank lines.

You can add a query to select a subset of transactions.


1/1 (123)
 (a)  1

1/1 ()
 (a)  1

 (a)  1

1/1 (126)
 (a)  1
$ hledger codes
$ hledger codes -E



List all commodity/currency symbols used or declared in the journal.


List the unique descriptions that appear in transactions.

This command lists the unique descriptions that appear in transactions, in alphabetic order. You can add a query to select a subset of transactions.


$ hledger descriptions
Store Name
Gas Station | Petrol
Person A


Compares a particular account's transactions in two input files. It shows any transactions to this account which are in one file but not in the other.

More precisely, for each posting affecting this account in either file, it looks for a corresponding posting in the other file which posts the same amount to the same account (ignoring date, description, etc.) Since postings not transactions are compared, this also works when multiple bank transactions have been combined into a single journal entry.

This is useful eg if you have downloaded an account's transactions from your bank (eg as CSV data). When hledger and your bank disagree about the account balance, you can compare the bank data with your journal to find out the cause.


$ hledger diff -f $LEDGER_FILE -f bank.csv assets:bank:giro 
These transactions are in the first file only:

2014/01/01 Opening Balances
    assets:bank:giro              EUR ...
    equity:opening balances       EUR -...

These transactions are in the second file only:


List all files included in the journal. With a REGEX argument, only file names matching the regular expression (case sensitive) are shown.


Show the hledger user manual in one of several formats, optionally positioned at a given TOPIC (if possible). TOPIC is any heading, or heading prefix, in the manual. Some examples: commands, print, 'auto postings', periodic.

This command shows the user manual built in to this hledger version. It can be useful if the correct version of the hledger manual, or the usual viewing tools, are not installed on your system.

By default it uses the best viewer it can find in $PATH, in this order: info, man, $PAGER (unless a topic is specified), less, or stdout. When run non-interactively, it always uses stdout. Or you can select a particular viewer with the -i (info), -m (man), or -p (pager) flags.


Read new transactions added to each FILE since last run, and add them to the main journal file. Or with --dry-run, just print the transactions that would be added. Or with --catchup, just mark all of the FILEs' transactions as imported, without actually importing any.

Unlike other hledger commands, with import the journal file is an output file, and will be modified, though only by appending (existing data will not be changed). The input files are specified as arguments, so to import one or more CSV files to your main journal, you will run hledger import bank.csv or perhaps hledger import *.csv.

Note you can import from any file format, though CSV files are the most common import source, and these docs focus on that case.


As a convenience import does deduplication while reading transactions. This does not mean "ignore transactions that look the same", but rather "ignore transactions that have been seen before". This is intended for when you are periodically importing foreign data which may contain already-imported transactions. So eg, if every day you download bank CSV files containing redundant data, you can safely run hledger import bank.csv and only new transactions will be imported. (import is idempotent.)

Since the items being read (CSV records, eg) often do not come with unique identifiers, hledger detects new transactions by date, assuming that:

  1. new items always have the newest dates
  2. item dates do not change across reads
  3. and items with the same date remain in the same relative order across reads.

These are often true of CSV files representing transactions, or true enough so that it works pretty well in practice. 1 is important, but violations of 2 and 3 amongst the old transactions won't matter (and if you import often, the new transactions will be few, so less likely to be the ones affected).

hledger remembers the latest date processed in each input file by saving a hidden ".latest" state file in the same directory. Eg when reading finance/bank.csv, it will look for and update the finance/.latest.bank.csv state file. The format is simple: one or more lines containing the same ISO-format date (YYYY-MM-DD), meaning "I have processed transactions up to this date, and this many of them on that date." Normally you won't see or manipulate these state files yourself. But if needed, you can delete them to reset the state (making all transactions "new"), or you can construct them to "catch up" to a certain date.

Note deduplication (and updating of state files) can also be done by print --new, but this is less often used.

Import testing

With --dry-run, the transactions that will be imported are printed to the terminal, without updating your journal or state files. The output is valid journal format, like the print command, so you can re-parse it. Eg, to see any importable transactions which CSV rules have not categorised:

$ hledger import --dry bank.csv | hledger -f- -I print unknown

or (live updating):

$ ls bank.csv* | entr bash -c 'echo ====; hledger import --dry bank.csv | hledger -f- -I print unknown'

Importing balance assignments

Entries added by import will have their posting amounts made explicit (like hledger print -x). This means that any balance assignments in imported files must be evaluated; but, imported files don't get to see the main file's account balances. As a result, importing entries with balance assignments (eg from an institution that provides only balances and not posting amounts) will probably generate incorrect posting amounts. To avoid this problem, use print instead of import:

$ hledger print IMPORTFILE [--new] >> $LEDGER_FILE

(If you think import should leave amounts implicit like print does, please test it and send a pull request.)

Commodity display styles

Imported amounts will be formatted according to the canonical commodity styles (declared or inferred) in the main journal file.


incomestatement, is

This command displays an income statement, showing revenues and expenses during one or more periods. Amounts are shown with normal positive sign, as in conventional financial statements.

The revenue and expense accounts shown are those accounts declared with the Revenue or Expense type, or otherwise all accounts under a top-level revenue or income or expense account (case insensitive, plurals allowed).


$ hledger incomestatement
Income Statement

                 $-2  income
                 $-1    gifts
                 $-1    salary

                  $2  expenses
                  $1    food
                  $1    supplies


This command is a higher-level variant of the balance command, and supports many of that command's features, such as multi-period reports. It is similar to hledger balance '(revenues|income)' expenses, but with smarter account detection, and revenues/income displayed with their sign flipped.

This command also supports the output destination and output format options The output formats supported are txt, csv, html, and (experimental) json.


List the unique notes that appear in transactions.

This command lists the unique notes that appear in transactions, in alphabetic order. You can add a query to select a subset of transactions. The note is the part of the transaction description after a | character (or if there is no |, the whole description).


$ hledger notes


List the unique payee/payer names that appear in transactions.

This command lists unique payee/payer names which have been declared with payee directives (--declared), used in transaction descriptions (--used), or both (the default).

The payee/payer is the part of the transaction description before a | character (or if there is no |, the whole description).

You can add query arguments to select a subset of transactions. This implies --used.


$ hledger payees
Store Name
Gas Station
Person A


Print market price directives from the journal. With --costs, also print synthetic market prices based on transaction prices. With --inverted-costs, also print inverse prices based on transaction prices. Prices (and postings providing prices) can be filtered by a query. Price amounts are always displayed with their full precision.


Show transaction journal entries, sorted by date.

The print command displays full journal entries (transactions) from the journal file, sorted by date (or with --date2, by secondary date).

Amounts are shown mostly normalised to commodity display style, eg the placement of commodity symbols will be consistent. All of their decimal places are shown, as in the original journal entry (with one alteration: in some cases trailing zeroes are added.)

Amounts are shown right-aligned within each transaction (but not across all transactions).

Directives and inter-transaction comments are not shown, currently. This means the print command is somewhat lossy, and if you are using it to reformat your journal you should take care to also copy over the directives and file-level comments.


$ hledger print
2008/01/01 income
    assets:bank:checking            $1
    income:salary                  $-1

2008/06/01 gift
    assets:bank:checking            $1
    income:gifts                   $-1

2008/06/02 save
    assets:bank:saving              $1
    assets:bank:checking           $-1

2008/06/03 * eat & shop
    expenses:food                $1
    expenses:supplies            $1
    assets:cash                 $-2

2008/12/31 * pay off
    liabilities:debts               $1
    assets:bank:checking           $-1

print's output is usually a valid hledger journal, and you can process it again with a second hledger command. This can be useful for certain kinds of search, eg:

# Show running total of food expenses paid from cash.
# -f- reads from stdin. -I/--ignore-assertions is sometimes needed.
$ hledger print assets:cash | hledger -f- -I reg expenses:food

There are some situations where print's output can become unparseable:

Normally, the journal entry's explicit or implicit amount style is preserved. For example, when an amount is omitted in the journal, it will not appear in the output. Similarly, when a transaction price is implied but not written, it will not appear in the output. You can use the -x/--explicit flag to make all amounts and transaction prices explicit, which can be useful for troubleshooting or for making your journal more readable and robust against data entry errors. -x is also implied by using any of -B,-V,-X,--value.

Note, -x/--explicit will cause postings with a multi-commodity amount (these can arise when a multi-commodity transaction has an implicit amount) to be split into multiple single-commodity postings, keeping the output parseable.

With -B/--cost, amounts with transaction prices are converted to cost using that price. This can be used for troubleshooting.

With -m/--match and a STR argument, print will show at most one transaction: the one one whose description is most similar to STR, and is most recent. STR should contain at least two characters. If there is no similar-enough match, no transaction will be shown.

With --new, hledger prints only transactions it has not seen on a previous run. This uses the same deduplication system as the import command. (See import's docs for details.)

This command also supports the output destination and output format options The output formats supported are txt, csv, and (experimental) json and sql.

Here's an example of print's CSV output:

$ hledger print -Ocsv
"4","2008/06/03","","*","","eat & shop","","expenses:food","1","$","","1","",""
"4","2008/06/03","","*","","eat & shop","","expenses:supplies","1","$","","1","",""
"4","2008/06/03","","*","","eat & shop","","assets:cash","-2","$","2","","",""
"5","2008/12/31","","*","","pay off","","liabilities:debts","1","$","","1","",""
"5","2008/12/31","","*","","pay off","","assets:bank:checking","-1","$","1","","",""
  • There is one CSV record per posting, with the parent transaction's fields repeated.
  • The "txnidx" (transaction index) field shows which postings belong to the same transaction. (This number might change if transactions are reordered within the file, files are parsed/included in a different order, etc.)
  • The amount is separated into "commodity" (the symbol) and "amount" (numeric quantity) fields.
  • The numeric amount is repeated in either the "credit" or "debit" column, for convenience. (Those names are not accurate in the accounting sense; it just puts negative amounts under credit and zero or greater amounts under debit.)


Print transactions which do not reuse an already-seen description.


$ cat unique.journal
1/1 test
 (acct:one)  1
2/2 test
 (acct:two)  2
$ LEDGER_FILE=unique.journal hledger print-unique
(-f option not supported)
2015/01/01 test
    (acct:one)             1


register, reg
Show postings and their running total.

The register command displays matched postings, across all accounts, in date order, with their running total or running historical balance. (See also the aregister command, which shows matched transactions in a specific account.)

register normally shows line per posting, but note that multi-commodity amounts will occupy multiple lines (one line per commodity).

It is typically used with a query selecting a particular account, to see that account's activity:

$ hledger register checking
2008/01/01 income               assets:bank:checking            $1           $1
2008/06/01 gift                 assets:bank:checking            $1           $2
2008/06/02 save                 assets:bank:checking           $-1           $1
2008/12/31 pay off              assets:bank:checking           $-1            0

With --date2, it shows and sorts by secondary date instead.

The --historical/-H flag adds the balance from any undisplayed prior postings to the running total. This is useful when you want to see only recent activity, with a historically accurate running balance:

$ hledger register checking -b 2008/6 --historical
2008/06/01 gift                 assets:bank:checking            $1           $2
2008/06/02 save                 assets:bank:checking           $-1           $1
2008/12/31 pay off              assets:bank:checking           $-1            0

The --depth option limits the amount of sub-account detail displayed.

The --average/-A flag shows the running average posting amount instead of the running total (so, the final number displayed is the average for the whole report period). This flag implies --empty (see below). It is affected by --historical. It works best when showing just one account and one commodity.

The --related/-r flag shows the other postings in the transactions of the postings which would normally be shown.

The --invert flag negates all amounts. For example, it can be used on an income account where amounts are normally displayed as negative numbers. It's also useful to show postings on the checking account together with the related account:

$ hledger register --related --invert assets:checking

With a reporting interval, register shows summary postings, one per interval, aggregating the postings to each account:

$ hledger register --monthly income
2008/01                 income:salary                          $-1          $-1
2008/06                 income:gifts                           $-1          $-2

Periods with no activity, and summary postings with a zero amount, are not shown by default; use the --empty/-E flag to see them:

$ hledger register --monthly income -E
2008/01                 income:salary                          $-1          $-1
2008/02                                                          0          $-1
2008/03                                                          0          $-1
2008/04                                                          0          $-1
2008/05                                                          0          $-1
2008/06                 income:gifts                           $-1          $-2
2008/07                                                          0          $-2
2008/08                                                          0          $-2
2008/09                                                          0          $-2
2008/10                                                          0          $-2
2008/11                                                          0          $-2
2008/12                                                          0          $-2

Often, you'll want to see just one line per interval. The --depth option helps with this, causing subaccounts to be aggregated:

$ hledger register --monthly assets --depth 1h
2008/01                 assets                                  $1           $1
2008/06                 assets                                 $-1            0
2008/12                 assets                                 $-1          $-1

Note when using report intervals, if you specify start/end dates these will be adjusted outward if necessary to contain a whole number of intervals. This ensures that the first and last intervals are full length and comparable to the others in the report.

Custom register output

register uses the full terminal width by default, except on windows. You can override this by setting the COLUMNS environment variable (not a bash shell variable) or by using the --width/-w option.

The description and account columns normally share the space equally (about half of (width - 40) each). You can adjust this by adding a description width as part of --width's argument, comma-separated: --width W,D . Here's a diagram (won't display correctly in --help):

<--------------------------------- width (W) ---------------------------------->
date (10)  description (D)       account (W-41-D)     amount (12)   balance (12)
DDDDDDDDDD dddddddddddddddddddd  aaaaaaaaaaaaaaaaaaa  AAAAAAAAAAAA  AAAAAAAAAAAA

and some examples:

$ hledger reg                     # use terminal width (or 80 on windows)
$ hledger reg -w 100              # use width 100
$ COLUMNS=100 hledger reg         # set with one-time environment variable
$ export COLUMNS=100; hledger reg # set till session end (or window resize)
$ hledger reg -w 100,40           # set overall width 100, description width 40
$ hledger reg -w $COLUMNS,40      # use terminal width, & description width 40

This command also supports the output destination and output format options The output formats supported are txt, csv, and (experimental) json.


Print the one posting whose transaction description is closest to DESC, in the style of the register command. If there are multiple equally good matches, it shows the most recent. Query options (options, not arguments) can be used to restrict the search space. Helps ledger-autosync detect already-seen transactions when importing.


Print all transactions, rewriting the postings of matched transactions. For now the only rewrite available is adding new postings, like print --auto.

This is a start at a generic rewriter of transaction entries. It reads the default journal and prints the transactions, like print, but adds one or more specified postings to any transactions matching QUERY. The posting amounts can be fixed, or a multiplier of the existing transaction's first posting amount.


$ hledger-rewrite.hs ^income --add-posting '(liabilities:tax)  *.33  ; income tax' --add-posting '(reserve:gifts)  $100'
$ hledger-rewrite.hs expenses:gifts --add-posting '(reserve:gifts)  *-1"'
$ hledger-rewrite.hs -f rewrites.hledger

rewrites.hledger may consist of entries like:

= ^income amt:<0 date:2017
  (liabilities:tax)  *0.33  ; tax on income
  (reserve:grocery)  *0.25  ; reserve 25% for grocery
  (reserve:)  *0.25  ; reserve 25% for grocery

Note the single quotes to protect the dollar sign from bash, and the two spaces between account and amount.


$ hledger rewrite -- [QUERY]        --add-posting "ACCT  AMTEXPR" ...
$ hledger rewrite -- ^income        --add-posting '(liabilities:tax)  *.33'
$ hledger rewrite -- expenses:gifts --add-posting '(budget:gifts)  *-1"'
$ hledger rewrite -- ^income        --add-posting '(budget:foreign currency)  *0.25 JPY; diversify'

Argument for --add-posting option is a usual posting of transaction with an exception for amount specification. More precisely, you can use '*' (star symbol) before the amount to indicate that that this is a factor for an amount of original matched posting. If the amount includes a commodity name, the new posting amount will be in the new commodity; otherwise, it will be in the matched posting amount's commodity.

Re-write rules in a file

During the run this tool will execute so called "Automated Transactions" found in any journal it process. I.e instead of specifying this operations in command line you can put them in a journal file.

$ rewrite-rules.journal

Make contents look like this:

= ^income
    (liabilities:tax)  *.33

= expenses:gifts
    budget:gifts  *-1
    assets:budget  *1

Note that '=' (equality symbol) that is used instead of date in transactions you usually write. It indicates the query by which you want to match the posting to add new ones.

$ hledger rewrite -- -f input.journal -f rewrite-rules.journal > rewritten-tidy-output.journal

This is something similar to the commands pipeline:

$ hledger rewrite -- -f input.journal '^income' --add-posting '(liabilities:tax)  *.33' \
  | hledger rewrite -- -f - expenses:gifts      --add-posting 'budget:gifts  *-1'       \
                                                --add-posting 'assets:budget  *1'       \
  > rewritten-tidy-output.journal

It is important to understand that relative order of such entries in journal is important. You can re-use result of previously added postings.

Diff output format

To use this tool for batch modification of your journal files you may find useful output in form of unified diff.

$ hledger rewrite -- --diff -f examples/sample.journal '^income' --add-posting '(liabilities:tax)  *.33'

Output might look like:

--- /tmp/examples/sample.journal
+++ /tmp/examples/sample.journal
@@ -18,3 +18,4 @@
 2008/01/01 income
-    assets:bank:checking  $1
+    assets:bank:checking            $1
+    (liabilities:tax)                0
@@ -22,3 +23,4 @@
 2008/06/01 gift
-    assets:bank:checking  $1
+    assets:bank:checking            $1
+    (liabilities:tax)                0

If you'll pass this through patch tool you'll get transactions containing the posting that matches your query be updated. Note that multiple files might be update according to list of input files specified via --file options and include directives inside of these files.

Be careful. Whole transaction being re-formatted in a style of output from hledger print.

See also:


rewrite vs. print --auto

This command predates print --auto, and currently does much the same thing, but with these differences:

  • with multiple files, rewrite lets rules in any file affect all other files. print --auto uses standard directive scoping; rules affect only child files.

  • rewrite's query limits which transactions can be rewritten; all are printed. print --auto's query limits which transactions are printed.

  • rewrite applies rules specified on command line or in the journal. print --auto applies rules specified in the journal.


Shows the time-weighted (TWR) and money-weighted (IRR) rate of return on your investments.

At a minimum, you need to supply a query (which could be just an account name) to select your investment(s) with --inv, and another query to identify your profit and loss transactions with --pnl.

If you do not record changes in the value of your investment manually, or do not require computation of time-weighted return (TWR), --pnl could be an empty query (--pnl "" or --pnl STR where STR does not match any of your accounts).

This command will compute and display the internalized rate of return (IRR) and time-weighted rate of return (TWR) for your investments for the time period requested. Both rates of return are annualized before display, regardless of the length of reporting interval.

Price directives will be taken into account if you supply appropriate --cost or --value flags (see VALUATION).

Note, in some cases this report can fail, for these reasons:

  • Error (NotBracketed): No solution for Internal Rate of Return (IRR). Possible causes: IRR is huge (>1000000%), balance of investment becomes negative at some point in time.
  • Error (SearchFailed): Failed to find solution for Internal Rate of Return (IRR). Either search does not converge to a solution, or converges too slowly.


  • Using roi to compute total return of investment in stocks: https://github.com/simonmichael/hledger/blob/master/examples/roi-unrealised.ledger

  • Cookbook -> Return on Investment

Semantics of --inv and --pnl

Query supplied to --inv has to match all transactions that are related to your investment. Transactions not matching --inv will be ignored.

In these transactions, ROI will conside postings that match --inv to be "investment postings" and other postings (not matching --inv) will be sorted into two categories: "cash flow" and "profit and loss", as ROI needs to know which part of the investment value is your contributions and which is due to the return on investment.

  • "Cash flow" is depositing or withdrawing money, buying or selling assets, or otherwise converting between your investment commodity and any other commodity. Example:

    2019-01-01 Investing in Snake Oil
      assets:cash          -$100
      investment:snake oil
    2020-01-01 Selling my Snake Oil
      assets:cash           $10
      investment:snake oil  = 0
  • "Profit and loss" is change in the value of your investment:

    2019-06-01 Snake Oil falls in value
      investment:snake oil  = $57
      equity:unrealized profit or loss

All non-investment postings are assumed to be "cash flow", unless they match --pnl query. Changes in value of your investment due to "profit and loss" postings will be considered as part of your investment return.

Example: if you use --inv snake --pnl equity:unrealized, then postings in the example below would be classifed as:

2019-01-01 Snake Oil #1
  assets:cash          -$100   ; cash flow posting
  investment:snake oil         ; investment posting

2019-03-01 Snake Oil #2
  equity:unrealized pnl  -$100 ; profit and loss posting
  snake oil                    ; investment posting

2019-07-01 Snake Oil #3
  equity:unrealized pnl        ; profit and loss posting
  cash          -$100          ; cash flow posting
  snake oil     $50            ; investment posting

IRR and TWR explained

"ROI" stands for "return on investment". Traditionally this was computed as a difference between current value of investment and its initial value, expressed in percentage of the initial value.

However, this approach is only practical in simple cases, where investments receives no in-flows or out-flows of money, and where rate of growth is fixed over time. For more complex scenarios you need different ways to compute rate of return, and this command implements two of them: IRR and TWR.

Internal rate of return, or "IRR" (also called "money-weighted rate of return") takes into account effects of in-flows and out-flows. Naively, if you are withdrawing from your investment, your future gains would be smaller (in absolute numbers), and will be a smaller percentage of your initial investment, and if you are adding to your investment, you will receive bigger absolute gains (but probably at the same rate of return). IRR is a way to compute rate of return for each period between in-flow or out-flow of money, and then combine them in a way that gives you a compound annual rate of return that investment is expected to generate.

As mentioned before, in-flows and out-flows would be any cash that you personally put in or withdraw, and for the "roi" command, these are the postings that match the query in the--inv argument and NOT match the query in the--pnl argument.

If you manually record changes in the value of your investment as transactions that balance them against "profit and loss" (or "unrealized gains") account or use price directives, then in order for IRR to compute the precise effect of your in-flows and out-flows on the rate of return, you will need to record the value of your investement on or close to the days when in- or out-flows occur.

In technical terms, IRR uses the same approach as computation of net present value, and tries to find a discount rate that makes net present value of all the cash flows of your investment to add up to zero. This could be hard to wrap your head around, especially if you haven't done discounted cash flow analysis before. Implementation of IRR in hledger should produce results that match the XIRR formula in Excel.

Second way to compute rate of return that roi command implements is called "time-weighted rate of return" or "TWR". Like IRR, it will also break the history of your investment into periods between in-flows, out-flows and value changes, to compute rate of return per each period and then a compound rate of return. However, internal workings of TWR are quite different.

TWR represents your investment as an imaginary "unit fund" where in-flows/ out-flows lead to buying or selling "units" of your investment and changes in its value change the value of "investment unit". Change in "unit price" over the reporting period gives you rate of return of your investment.

References: * Explanation of rate of return * Explanation of IRR * Explanation of TWR * Examples of computing IRR and TWR and discussion of the limitations of both metrics


Show some journal statistics.

The stats command displays summary information for the whole journal, or a matched part of it. With a reporting interval, it shows a report for each report period.


$ hledger stats
Main journal file        : /src/hledger/examples/sample.journal
Included journal files   : 
Transactions span        : 2008-01-01 to 2009-01-01 (366 days)
Last transaction         : 2008-12-31 (2333 days ago)
Transactions             : 5 (0.0 per day)
Transactions last 30 days: 0 (0.0 per day)
Transactions last 7 days : 0 (0.0 per day)
Payees/descriptions      : 5
Accounts                 : 8 (depth 3)
Commodities              : 1 ($)
Market prices            : 12 ($)

This command also supports output destination and output format selection.


List the unique tag names used in the journal. With a TAGREGEX argument, only tag names matching the regular expression (case insensitive) are shown. With QUERY arguments, only transactions matching the query are considered.

With the --values flag, the tags' unique values are listed instead.

With --parsed flag, all tags or values are shown in the order they are parsed from the input data, including duplicates.

With -E/--empty, any blank/empty values will also be shown, otherwise they are omitted.


Run built-in unit tests.

This command runs the unit tests built in to hledger and hledger-lib, printing the results on stdout. If any test fails, the exit code will be non-zero.

This is mainly used by hledger developers, but you can also use it to sanity-check the installed hledger executable on your platform. All tests are expected to pass - if you ever see a failure, please report as a bug!

This command also accepts tasty test runner options, written after a -- (double hyphen). Eg to run only the tests in Hledger.Data.Amount, with ANSI colour codes disabled:

$ hledger test -- -pData.Amount --color=never

For help on these, see https://github.com/feuerbach/tasty#options (-- --help currently doesn't show them).

About add-on commands

Add-on commands are programs or scripts in your PATH

  • whose name starts with hledger-
  • whose name ends with a recognised file extension: .bat,.com,.exe, .hs,.lhs,.pl,.py,.rb,.rkt,.sh or none
  • and (on unix, mac) which are executable by the current user.

Add-ons are a relatively easy way to add local features or experiment with new ideas. They can be written in any language, but haskell scripts have a big advantage: they can use the same hledger library functions that built-in commands use for command-line options, parsing and reporting. Some experimental/example add-on scripts can be found in the hledger repo's bin/ directory.

Note in a hledger command line, add-on command flags must have a double dash (--) preceding them. Eg you must write:

$ hledger web -- --serve

and not:

$ hledger web --serve

(because the --serve flag belongs to hledger-web, not hledger).

The -h/--help and --version flags don't require --.

If you have any trouble with this, remember you can always run the add-on program directly, eg:

$ hledger-web --serve


hledger's default file format, representing a General Journal.

hledger's usual data source is a plain text file containing journal entries in hledger journal format. This file represents a standard accounting general journal. I use file names ending in .journal, but that's not required. The journal file contains a number of transaction entries, each describing a transfer of money (or any commodity) between two or more named accounts, in a simple format readable by both hledger and humans.

hledger's journal format is a compatible subset, mostly, of ledger's journal format, so hledger can work with compatible ledger journal files as well. It's safe, and encouraged, to run both hledger and ledger on the same journal file, eg to validate the results you're getting.

You can use hledger without learning any more about this file; just use the add or web or import commands to create and update it.

Many users, though, edit the journal file with a text editor, and track changes with a version control system such as git. Editor addons such as ledger-mode or hledger-mode for Emacs, vim-ledger for Vim, and hledger-vscode for Visual Studio Code, make this easier, adding colour, formatting, tab completion, and useful commands. See Editor configuration at hledger.org for the full list.

Here's a description of each part of the file format (and hledger's data model). These are mostly in the order you'll use them, but in some cases related concepts have been grouped together for easy reference, or linked before they are introduced, so feel free to skip over anything that looks unnecessary right now.


Transactions are the main unit of information in a journal file. They represent events, typically a movement of some quantity of commodities between two or more named accounts.

Each transaction is recorded as a journal entry, beginning with a simple date in column 0. This can be followed by any of the following optional fields, separated by spaces:

  • a status character (empty, !, or *)
  • a code (any short number or text, enclosed in parentheses)
  • a description (any remaining text until end of line or a semicolon)
  • a comment (any remaining text following a semicolon until end of line, and any following indented lines beginning with a semicolon)
  • 0 or more indented posting lines, describing what was transferred and the accounts involved (indented comment lines are also allowed, but not blank lines or non-indented lines).

Here's a simple journal file containing one transaction:

2008/01/01 income
  assets:bank:checking   $1
  income:salary         $-1


Simple dates

Dates in the journal file use simple dates format: YYYY-MM-DD or YYYY/MM/DD or YYYY.MM.DD, with leading zeros optional. The year may be omitted, in which case it will be inferred from the context: the current transaction, the default year set with a default year directive, or the current date when the command is run. Some examples: 2010-01-31, 2010/01/31, 2010.1.31, 1/31.

(The UI also accepts simple dates, as well as the more flexible smart dates documented in the hledger manual.)

Secondary dates

Real-life transactions sometimes involve more than one date - eg the date you write a cheque, and the date it clears in your bank. When you want to model this, for more accurate daily balances, you can specify individual posting dates.

Or, you can use the older secondary date feature (Ledger calls it auxiliary date or effective date). Note: we support this for compatibility, but I usually recommend avoiding this feature; posting dates are almost always clearer and simpler.

A secondary date is written after the primary date, following an equals sign. If the year is omitted, the primary date's year is assumed. When running reports, the primary (left) date is used by default, but with the --date2 flag (or --aux-date or --effective), the secondary (right) date will be used instead.

The meaning of secondary dates is up to you, but it's best to follow a consistent rule. Eg "primary = the bank's clearing date, secondary = date the transaction was initiated, if different", as shown here:

2010/2/23=2/19 movie ticket
  expenses:cinema                   $10
$ hledger register checking
2010-02-23 movie ticket         assets:checking                $-10         $-10
$ hledger register checking --date2
2010-02-19 movie ticket         assets:checking                $-10         $-10

Posting dates

You can give individual postings a different date from their parent transaction, by adding a posting comment containing a tag (see below) like date:DATE. This is probably the best way to control posting dates precisely. Eg in this example the expense should appear in May reports, and the deduction from checking should be reported on 6/1 for easy bank reconciliation:

    expenses:food     $10  ; food purchased on saturday 5/30
    assets:checking        ; bank cleared it on monday, date:6/1
$ hledger -f t.j register food
2015-05-30                      expenses:food                  $10           $10
$ hledger -f t.j register checking
2015-06-01                      assets:checking               $-10          $-10

DATE should be a simple date; if the year is not specified it will use the year of the transaction's date. You can set the secondary date similarly, with date2:DATE2. The date: or date2: tags must have a valid simple date value if they are present, eg a date: tag with no value is not allowed.

Ledger's earlier, more compact bracketed date syntax is also supported: [DATE], [DATE=DATE2] or [=DATE2]. hledger will attempt to parse any square-bracketed sequence of the 0123456789/-.= characters in this way. With this syntax, DATE infers its year from the transaction and DATE2 infers its year from DATE.


Transactions, or individual postings within a transaction, can have a status mark, which is a single character before the transaction description or posting account name, separated from it by a space, indicating one of three statuses:

mark  status

When reporting, you can filter by status with the -U/--unmarked, -P/--pending, and -C/--cleared flags; or the status:, status:!, and status:* queries; or the U, P, C keys in hledger-ui.

Note, in Ledger and in older versions of hledger, the "unmarked" state is called "uncleared". As of hledger 1.3 we have renamed it to unmarked for clarity.

To replicate Ledger and old hledger's behaviour of also matching pending, combine -U and -P.

Status marks are optional, but can be helpful eg for reconciling with real-world accounts. Some editor modes provide highlighting and shortcuts for working with status. Eg in Emacs ledger-mode, you can toggle transaction status with C-c C-e, or posting status with C-c C-c.

What "uncleared", "pending", and "cleared" actually mean is up to you. Here's one suggestion:

unclearedrecorded but not yet reconciled; needs review
pendingtentatively reconciled (if needed, eg during a big reconciliation)
clearedcomplete, reconciled as far as possible, and considered correct

With this scheme, you would use -PC to see the current balance at your bank, -U to see things which will probably hit your bank soon (like uncashed checks), and no flags to see the most up-to-date state of your finances.


A transaction's description is the rest of the line following the date and status mark (or until a comment begins). Sometimes called the "narration" in traditional bookkeeping, it can be used for whatever you wish, or left blank. Transaction descriptions can be queried, unlike comments.

Payee and note

You can optionally include a | (pipe) character in descriptions to subdivide the description into separate fields for payee/payer name on the left (up to the first |) and an additional note field on the right (after the first |). This may be worthwhile if you need to do more precise querying and pivoting by payee or by note.


Lines in the journal beginning with a semicolon (;) or hash (#) or star (*) are comments, and will be ignored. (Star comments cause org-mode nodes to be ignored, allowing emacs users to fold and navigate their journals with org-mode or orgstruct-mode.)

You can attach comments to a transaction by writing them after the description and/or indented on the following lines (before the postings). Similarly, you can attach comments to an individual posting by writing them after the amount and/or indented on the following lines. Transaction and posting comments must begin with a semicolon (;).

Some examples:

# a file comment
; another file comment
* also a file comment, useful in org/orgstruct mode

A multiline file comment, which continues
until a line containing just "end comment"
(or end of file).
end comment

2012/5/14 something  ; a transaction comment
    ; the transaction comment, continued
    posting1  1  ; a comment for posting 1
    ; a comment for posting 2
    ; another comment line for posting 2
; a file comment (because not indented)

You can also comment larger regions of a file using comment and end comment directives.


Tags are a way to add extra labels or labelled data to postings and transactions, which you can then search or pivot on.

A simple tag is a word (which may contain hyphens) followed by a full colon, written inside a transaction or posting comment line:

2017/1/16 bought groceries  ; sometag:

Tags can have a value, which is the text after the colon, up to the next comma or end of line, with leading/trailing whitespace removed:

    expenses:food    $10 ; a-posting-tag: the tag value

Note this means hledger's tag values can not contain commas or newlines. Ending at commas means you can write multiple short tags on one line, comma separated:

    assets:checking  ; a comment containing tag1:, tag2: some value ...


  • "a comment containing" is just comment text, not a tag
  • "tag1" is a tag with no value
  • "tag2" is another tag, whose value is "some value ..."

Tags in a transaction comment affect the transaction and all of its postings, while tags in a posting comment affect only that posting. For example, the following transaction has three tags (A, TAG2, third-tag) and the posting has four (those plus posting-tag):

1/1 a transaction  ; A:, TAG2:
    ; third-tag: a third transaction tag, <- with a value
    (a)  $1  ; posting-tag:

Tags are like Ledger's metadata feature, except hledger's tag values are simple strings.


A posting is an addition of some amount to, or removal of some amount from, an account. Each posting line begins with at least one space or tab (2 or 4 spaces is common), followed by:

  • (optional) a status character (empty, !, or *), followed by a space
  • (required) an account name (any text, optionally containing single spaces, until end of line or a double space)
  • (optional) two or more spaces or tabs followed by an amount.

Positive amounts are being added to the account, negative amounts are being removed.

The amounts within a transaction must always sum up to zero. As a convenience, one amount may be left blank; it will be inferred so as to balance the transaction.

Be sure to note the unusual two-space delimiter between account name and amount. This makes it easy to write account names containing spaces. But if you accidentally leave only one space (or tab) before the amount, the amount will be considered part of the account name.

Virtual postings

A posting with a parenthesised account name is called a virtual posting or unbalanced posting, which means it is exempt from the usual rule that a transaction's postings must balance add up to zero.

This is not part of double entry accounting, so you might choose to avoid this feature. Or you can use it sparingly for certain special cases where it can be convenient. Eg, you could set opening balances without using a balancing equity account:

1/1 opening balances
  (assets:checking)   $1000
  (assets:savings)    $2000

A posting with a bracketed account name is called a balanced virtual posting. The balanced virtual postings in a transaction must add up to zero (separately from other postings). Eg:

1/1 buy food with cash, update budget envelope subaccounts, & something else
  assets:cash                    $-10 ; <- these balance
  expenses:food                    $7 ; <-
  expenses:food                    $3 ; <-
  [assets:checking:budget:food]  $-10    ; <- and these balance
  [assets:checking:available]     $10    ; <-
  (something:else)                 $5       ; <- not required to balance

Ordinary non-parenthesised, non-bracketed postings are called real postings. You can exclude virtual postings from reports with the -R/--real flag or real:1 query.

Account names

Account names typically have several parts separated by a full colon, from which hledger derives a hierarchical chart of accounts. They can be anything you like, but in finance there are traditionally five top-level accounts: assets, liabilities, revenue, expenses, and equity.

Account names may contain single spaces, eg: assets:accounts receivable. Because of this, they must always be followed by two or more spaces (or newline).

Account names can be aliased.


After the account name, there is usually an amount. (Important: between account name and amount, there must be two or more spaces.)

hledger's amount format is flexible, supporting several international formats. Here are some examples. Amounts have a number (the "quantity"):


..and usually a currency or commodity name (the "commodity"). This is a symbol, word, or phrase, to the left or right of the quantity, with or without a separating space:

4000 AAPL

If the commodity name contains spaces, numbers, or punctuation, it must be enclosed in double quotes:

3 "no. 42 green apples"

Amounts can be preceded by a minus sign (or a plus sign, though plus is the default), The sign can be written before or after a left-side commodity symbol:


One or more spaces between the sign and the number are acceptable when parsing (but they won't be displayed in output):

+ $1
$-      1

Scientific E notation is allowed:


Decimal marks, digit group marks

A decimal mark can be written as a period or a comma:


In the integer part of the quantity (left of the decimal mark), groups of digits can optionally be separated by a "digit group mark" - a space, comma, or period (different from the decimal mark):

  EUR 2.000.000,00
INR 9,99,99,999.00
      1 000 000.9455

Note, a number containing a single digit group mark and no decimal mark is ambiguous. Are these digit group marks or decimal marks ?


If you don't tell it otherwise, hledger will assume both of the above are decimal marks, parsing both numbers as 1. To prevent confusion and undetected typos, especially if your data contains digit group marks, we recommend you explicitly declare the decimal mark (and optionally a digit group mark), for each commodity, using commodity directives (described below):

# number formats for $, EUR, INR and the no-symbol commodity:
commodity $1,000.00
commodity EUR 1.000,00
commodity INR 9,99,99,999.00
commodity 1 000 000.9455

Note, commodity directives declare both the number format for parsing input, and the display style for showing output. For the former, they are position-sensitive, affecting only following amounts, so commodity directives should be at the top of your journal file. This is discussed more on #793.

Commodity display style

For the amounts in each commodity, hledger chooses a consistent display style to use in most reports. (Except for price amounts, which are always displayed as written). The display style is inferred as follows.

First, if a default commodity is declared with D, this commodity and its style is applied to any no-symbol amounts in the journal.

Then each commodity's style is inferred from one of the following, in order of preference:

  • The commodity directive for that commodity (including the no-symbol commodity), if any.
  • The amounts in that commodity seen in the journal's transactions. (Posting amounts only; prices and periodic or auto rules are ignored, currently.)
  • The built-in fallback style, which looks like this: $1000.00. (Symbol on the left, period decimal mark, two decimal places.)

A style is inferred from journal amounts as follows:

  • Use the general style (decimal mark, symbol placement) of the first amount
  • Use the first-seen digit group style (digit group mark, digit group sizes), if any
  • Use the maximum number of decimal places of all.

Transaction price amounts don't affect the commodity display style directly, but occasionally they can do so indirectly (eg when a posting's amount is inferred using a transaction price). If you find this causing problems, use a commodity directive to fix the display style.

To summarise: each commodity's amounts will be normalised to (a) the style declared by a commodity directive, or (b) the style of the first posting amount in the journal, with the first-seen digit group style and the maximum-seen number of decimal places. So if your reports are showing amounts in a way you don't like, eg with too many decimal places, use a commodity directive. Some examples:

# declare euro, dollar, bitcoin and no-symbol commodities and set their 
# input number formats and output display styles:
commodity EUR 1.000,
commodity $1000.00
commodity 1000.00000000 BTC
commodity 1 000.


Amounts are stored internally as decimal numbers with up to 255 decimal places, and displayed with the number of decimal places specified by the commodity display style. Note, hledger uses banker's rounding: it rounds to the nearest even number, eg 0.5 displayed with zero decimal places is "0"). (Guaranteed since hledger 1.17.1; in older versions this could vary if hledger was built with Decimal < 0.5.1.)

Transaction prices

Within a transaction, you can note an amount's price in another commodity. This can be used to document the cost (in a purchase) or selling price (in a sale). For example, transaction prices are useful to record purchases of a foreign currency. Note transaction prices are fixed at the time of the transaction, and do not change over time. See also market prices, which represent prevailing exchange rates on a certain date.

There are several ways to record a transaction price:

  1. Write the price per unit, as @ UNITPRICE after the amount:

      assets:euros     €100 @ $1.35  ; one hundred euros purchased at $1.35 each
      assets:dollars                 ; balancing amount is -$135.00
  2. Write the total price, as @@ TOTALPRICE after the amount:

      assets:euros     €100 @@ $135  ; one hundred euros purchased at $135 for the lot
  3. Specify amounts for all postings, using exactly two commodities, and let hledger infer the price that balances the transaction:

      assets:euros     €100          ; one hundred euros purchased
      assets:dollars  $-135          ; for $135
  4. Like 1, but the @ is parenthesised, i.e. (@); this is for compatibility with Ledger journals (Virtual posting costs), and is equivalent to 1 in hledger.

  5. Like 2, but as in 4 the @@ is parenthesised, i.e. (@@); in hledger, this is equivalent to 2.

Use the -B/--cost flag to convert amounts to their transaction price's commodity, if any. (mnemonic: "B" is from "cost Basis", as in Ledger). Eg here is how -B affects the balance report for the example above:

$ hledger bal -N --flat
               $-135  assets:dollars
                €100  assets:euros
$ hledger bal -N --flat -B
               $-135  assets:dollars
                $135  assets:euros    # <- the euros' cost

Note -B is sensitive to the order of postings when a transaction price is inferred: the inferred price will be in the commodity of the last amount. So if example 3's postings are reversed, while the transaction is equivalent, -B shows something different:

  assets:dollars  $-135              ; 135 dollars sold
  assets:euros     €100              ; for 100 euros
$ hledger bal -N --flat -B
               €-100  assets:dollars  # <- the dollars' selling price
                €100  assets:euros

Lot prices, lot dates

Ledger allows another kind of price, lot price (four variants: {UNITPRICE}, {{TOTALPRICE}}, {=FIXEDUNITPRICE}, {{=FIXEDTOTALPRICE}}), and/or a lot date ([DATE]) to be specified. These are normally used to select a lot when selling investments. hledger will parse these, for compatibility with Ledger journals, but currently ignores them. A transaction price, lot price and/or lot date may appear in any order, after the posting amount and before the balance assertion if any.

Balance assertions

hledger supports Ledger-style balance assertions in journal files. These look like, for example, = EXPECTEDBALANCE following a posting's amount. Eg here we assert the expected dollar balance in accounts a and b after each posting:

  a   $1  =$1
  b       =$-1

  a   $1  =$2
  b  $-1  =$-2

After reading a journal file, hledger will check all balance assertions and report an error if any of them fail. Balance assertions can protect you from, eg, inadvertently disrupting reconciled balances while cleaning up old entries. You can disable them temporarily with the -I/--ignore-assertions flag, which can be useful for troubleshooting or for reading Ledger files. (Note: this flag currently does not disable balance assignments, below).

Assertions and ordering

hledger sorts an account's postings and assertions first by date and then (for postings on the same day) by parse order. Note this is different from Ledger, which sorts assertions only by parse order. (Also, Ledger assertions do not see the accumulated effect of repeated postings to the same account within a transaction.)

So, hledger balance assertions keep working if you reorder differently-dated transactions within the journal. But if you reorder same-dated transactions or postings, assertions might break and require updating. This order dependence does bring an advantage: precise control over the order of postings and assertions within a day, so you can assert intra-day balances.

Assertions and included files

With included files, things are a little more complicated. Including preserves the ordering of postings and assertions. If you have multiple postings to an account on the same day, split across different files, and you also want to assert the account's balance on the same day, you'll have to put the assertion in the right file.

Assertions and multiple -f options

Balance assertions don't work well across files specified with multiple -f options. Use include or concatenate the files instead.

Assertions and commodities

The asserted balance must be a simple single-commodity amount, and in fact the assertion checks only this commodity's balance within the (possibly multi-commodity) account balance. This is how assertions work in Ledger also. We could call this a "partial" balance assertion.

To assert the balance of more than one commodity in an account, you can write multiple postings, each asserting one commodity's balance.

You can make a stronger "total" balance assertion by writing a double equals sign (== EXPECTEDBALANCE). This asserts that there are no other unasserted commodities in the account (or, that their balance is 0).

  a   $1
  a    1€
  b  $-1
  c   -1€

2013/1/2  ; These assertions succeed
  a    0  =  $1
  a    0  =   1€
  b    0 == $-1
  c    0 ==  -1€

2013/1/3  ; This assertion fails as 'a' also contains 1€
  a    0 ==  $1

It's not yet possible to make a complete assertion about a balance that has multiple commodities. One workaround is to isolate each commodity into its own subaccount:

  a:usd   $1
  a:euro   1€

  a        0 ==  0
  a:usd    0 == $1
  a:euro   0 ==  1€

Assertions and prices

Balance assertions ignore transaction prices, and should normally be written without one:

  (a)     $1 @ €1 = $1

We do allow prices to be written there, however, and print shows them, even though they don't affect whether the assertion passes or fails. This is for backward compatibility (hledger's close command used to generate balance assertions with prices), and because balance assignments do use them (see below).

Assertions and subaccounts

The balance assertions above (= and ==) do not count the balance from subaccounts; they check the account's exclusive balance only. You can assert the balance including subaccounts by writing =* or ==*, eg:

  equity:opening balances
  checking:a       5
  checking:b       5
  checking         1  ==* 11

Assertions and virtual postings

Balance assertions are checked against all postings, both real and virtual. They are not affected by the --real/-R flag or real: query.

Assertions and precision

Balance assertions compare the exactly calculated amounts, which are not always what is shown by reports. Eg a commodity directive may limit the display precision, but this will not affect balance assertions. Balance assertion failure messages show exact amounts.

Balance assignments

Ledger-style balance assignments are also supported. These are like balance assertions, but with no posting amount on the left side of the equals sign; instead it is calculated automatically so as to satisfy the assertion. This can be a convenience during data entry, eg when setting opening balances:

; starting a new journal, set asset account balances
2016/1/1 opening balances
  assets:checking            = $409.32
  assets:savings             = $735.24
  assets:cash                 = $42
  equity:opening balances

or when adjusting a balance to reality:

; no cash left; update balance, record any untracked spending as a generic expense
  assets:cash    = $0

The calculated amount depends on the account's balance in the commodity at that point (which depends on the previously-dated postings of the commodity to that account since the last balance assertion or assignment). Note that using balance assignments makes your journal a little less explicit; to know the exact amount posted, you have to run hledger or do the calculations yourself, instead of just reading it.

Balance assignments and prices

A transaction price in a balance assignment will cause the calculated amount to have that price attached:

  (a)             = $1 @ €2
$ hledger print --explicit
    (a)         $1 @ €2 = $1 @ €2


A directive is a line in the journal beginning with a special keyword, that influences how the journal is processed. hledger's directives are based on a subset of Ledger's, but there are many differences (and also some differences between hledger versions).

Directives' behaviour and interactions can get a little bit complex, so here is a table summarising the directives and their effects, with links to more detailed docs. Note part of this table is hidden when viewed in a web browser - scroll it sideways to see more.

directiveend directivesubdirectivespurposecan affect (as of 2018/06)
accountany textdocument account names, declare account types & display orderall entries in all files, before or after
aliasend aliasesrewrite account namesfollowing entries until end of current file or end directive
apply accountend apply accountprepend a common parent to account namesfollowing entries until end of current file or end directive
commentend commentignore part of journalfollowing entries until end of current file or end directive
commodityformatdeclare a commodity and its number notation & display stylenumber notation: following entries in that commodity in all files ;
display style: amounts of that commodity in reports
Ddeclare a commodity to be used for commodityless amounts, and its number notation & display styledefault commodity: following commodityless entries until end of current file;
number notation: following entries in that commodity until end of current file;
display style: amounts of that commodity in reports
includeinclude entries/directives from another filewhat the included directives affect
[payee]declare a payee namefollowing entries until end of current file
Pdeclare a market price for a commodityamounts of that commodity in reports, when -V is used
Ydeclare a year for yearless datesfollowing entries until end of current file
=declare an auto posting rule, adding postings to other transactionsall entries in parent/current/child files (but not sibling files, see #1212)

And some definitions:

subdirectiveoptional indented directive line immediately following a parent directive
number notationhow to interpret numbers when parsing journal entries (the identity of the decimal separator character). (Currently each commodity can have its own notation, even in the same file.)
display stylehow to display amounts of a commodity in reports (symbol side and spacing, digit groups, decimal separator, decimal places)
directive scopewhich entries and (when there are multiple files) which files are affected by a directive

As you can see, directives vary in which journal entries and files they affect, and whether they are focussed on input (parsing) or output (reports). Some directives have multiple effects.

Directives and multiple files

If you use multiple -f/--file options, or the include directive, hledger will process multiple input files. But note that directives which affect input (see above) typically last only until the end of the file in which they occur.

This may seem inconvenient, but it's intentional; it makes reports stable and deterministic, independent of the order of input. Otherwise you could see different numbers if you happened to write -f options in a different order, or if you moved includes around while cleaning up your files.

It can be surprising though; for example, it means that alias directives do not affect parent or sibling files (see below).

Comment blocks

A line containing just comment starts a commented region of the file, and a line containing just end comment (or the end of the current file) ends it. See also comments.

Including other files

You can pull in the content of additional files by writing an include directive, like this:

include FILEPATH

Only journal files can include, and only journal, timeclock or timedot files can be included (not CSV files, currently).

If the file path does not begin with a slash, it is relative to the current file's folder.

A tilde means home directory, eg: include ~/main.journal.

The path may contain glob patterns to match multiple files, eg: include *.journal.

There is limited support for recursive wildcards: **/ (the slash is required) matches 0 or more subdirectories. It's not super convenient since you have to avoid include cycles and including directories, but this can be done, eg: include */**/*.journal.

The path may also be prefixed to force a specific file format, overriding the file extension (as described in hledger.1 -> Input files): include timedot:~/notes/2020*.md.

Default year

You can set a default year to be used for subsequent dates which don't specify a year. This is a line beginning with Y followed by the year. Eg:

Y2009  ; set default year to 2009

12/15  ; equivalent to 2009/12/15
  expenses  1

Y2010  ; change default year to 2010

2009/1/30  ; specifies the year, not affected
  expenses  1

1/31   ; equivalent to 2010/1/31
  expenses  1

Declaring payees

The payee directive can be used to declare a limited set of payees which may appear in transaction descriptions. The "payees" check will report an error if any transaction refers to a payee that has not been declared. Eg:

payee Whole Foods

Declaring commodities

The commodity directive has several functions:

  1. It declares commodities which may be used in the journal. This is currently not enforced, but can serve as documentation.

  2. It declares what decimal mark character (period or comma) to expect when parsing input - useful to disambiguate international number formats in your data. (Without this, hledger will parse both 1,000 and 1.000 as 1).

  3. It declares a commodity's display style in output - decimal and digit group marks, number of decimal places, symbol placement etc.

You are likely to run into one of the problems solved by commodity directives, sooner or later, so it's a good idea to just always use them to declare your commodities.

A commodity directive is just the word commodity followed by an amount. It may be written on a single line, like this:


; display AAAA amounts with the symbol on the right, space-separated,
; using period as decimal point, with four decimal places, and
; separating thousands with comma.
commodity 1,000.0000 AAAA

or on multiple lines, using the "format" subdirective. (In this case the commodity symbol appears twice and should be the same in both places.):

; commodity SYMBOL

; display indian rupees with currency name on the left,
; thousands, lakhs and crores comma-separated,
; period as decimal point, and two decimal places.
commodity INR
  format INR 1,00,00,000.00

The quantity of the amount does not matter; only the format is significant. The number must include a decimal mark: either a period or a comma, followed by 0 or more decimal digits.

Note hledger normally uses banker's rounding, so 0.5 displayed with zero decimal digits is "0". (More at Commodity display style.)

Commodity error checking

In strict mode, enabled with the -s/--strict flag, hledger will report an error if a commodity symbol is used that has not been declared by a commodity directive. This works similarly to account error checking, see the notes there for more details.

Default commodity

The D directive sets a default commodity, to be used for amounts without a commodity symbol (ie, plain numbers). This commodity will be applied to all subsequent commodity-less amounts, or until the next D directive. (Note, this is different from Ledger's D.)

For compatibility/historical reasons, D also acts like a commodity directive, setting the commodity's display style (for output) and decimal mark (for parsing input). As with commodity, the amount must always be written with a decimal mark (period or comma). If both directives are used, commodity's style takes precedence.

The syntax is D AMOUNT. Eg:

; commodity-less amounts should be treated as dollars
; (and displayed with the dollar sign on the left, thousands separators and two decimal places)
D $1,000.00

  a     5  ; <- commodity-less amount, parsed as $5 and displayed as $5.00

Declaring market prices

The P directive declares a market price, which is an exchange rate between two commodities on a certain date. (In Ledger, they are called "historical prices".) These are often obtained from a stock exchange, cryptocurrency exchange, or the foreign exchange market.

Here is the format:

  • DATE is a simple date
  • COMMODITYA is the symbol of the commodity being priced
  • COMMODITYBAMOUNT is an amount (symbol and quantity) in a second commodity, giving the price in commodity B of one unit of commodity A.

These two market price directives say that one euro was worth 1.35 US dollars during 2009, and $1.40 from 2010 onward:

P 2009/1/1 € $1.35
P 2010/1/1 € $1.40

The -V, -X and --value flags use these market prices to show amount values in another commodity. See Valuation.

Declaring accounts

account directives can be used to declare accounts (ie, the places that amounts are transferred from and to). Though not required, these declarations can provide several benefits:

  • They can document your intended chart of accounts, providing a reference.
  • They can help hledger know your accounts' types (asset, liability, equity, revenue, expense), useful for reports like balancesheet and incomestatement.
  • They control account display order in reports, allowing non-alphabetic sorting (eg Revenues to appear above Expenses).
  • They can store extra information about accounts (account numbers, notes, etc.)
  • They help with account name completion in the add command, hledger-iadd, hledger-web, ledger-mode etc.
  • In strict mode, they restrict which accounts may be posted to by transactions, which helps detect typos.

The simplest form is just the word account followed by a hledger-style account name, eg this account directive declares the assets:bank:checking account:

account assets:bank:checking

Account error checking

By default, accounts come into existence when a transaction references them by name. This is convenient, but it means hledger can't warn you when you mis-spell an account name in the journal. Usually you'll find the error later, as an extra account in balance reports, or an incorrect balance when reconciling.

In strict mode, enabled with the -s/--strict flag, hledger will report an error if any transaction uses an account name that has not been declared by an account directive. Some notes:

  • The declaration is case-sensitive; transactions must use the correct account name capitalisation.
  • The account directive's scope is "whole file and below" (see directives). This means it affects all of the current file, and any files it includes, but not parent or sibling files. The position of account directives within the file does not matter, though it's usual to put them at the top.
  • Accounts can only be declared in journal files (but will affect included files in other formats).
  • It's currently not possible to declare "all possible subaccounts" with a wildcard; every account posted to must be declared.

Account comments

Comments, beginning with a semicolon, can be added:

  • on the same line, after two or more spaces (because ; is allowed in account names)
  • on the next lines, indented

An example of both:

account assets:bank:checking  ; same-line comment, note 2+ spaces before ;
  ; next-line comment
  ; another with tag, acctno:12345 (not used yet)

Same-line comments are not supported by Ledger, or hledger <1.13.

Account subdirectives

We also allow (and ignore) Ledger-style indented subdirectives, just for compatibility.:

account assets:bank:checking
  format blah blah  ; <- subdirective, ignored

Here is the full syntax of account directives:


Account types

hledger recognises five main types of account, corresponding to the account classes in the accounting equation:

Asset, Liability, Equity, Revenue, Expense.

These account types are important for controlling which accounts appear in the balancesheet, balancesheetequity, incomestatement reports (and probably for other things in future).

Additionally, we recognise the Cash type, which is also an Asset, and which causes accounts to appear in the cashflow report. ("Cash" here means liquid assets, eg bank balances but typically not investments or receivables.)

Declaring account types

Generally, to make these reports work you should declare your top-level accounts and their types, using account directives with type: tags.

The tag's value should be one of: Asset, Liability, Equity, Revenue, Expense, Cash, A, L, E, R, X, C (all case insensitive). The type is inherited by all subaccounts except where they override it. Here's a complete example:

account assets       ; type: Asset
account assets:bank  ; type: Cash
account assets:cash  ; type: Cash
account liabilities  ; type: Liability
account equity       ; type: Equity
account revenues     ; type: Revenue
account expenses     ; type: Expense
Auto-detected account types

If you happen to use common english top-level account names, you may not need to declare account types, as they will be detected automatically using the following rules:

If name matches regular expression:account type is:
If account type is Asset and name does not contain regular expression:account type is:

Even so, explicit declarations may be a good idea, for clarity and predictability.

Interference from auto-detected account types

If you assign any account type, it's a good idea to assign all of them, to prevent any confusion from mixing declared and auto-detected types. Although it's unlikely to happen in real life, here's an example: with the following journal, balancesheetequity shows "liabilities" in both Liabilities and Equity sections. Declaring another account as type:Liability would fix it:

account liabilities  ; type:Equity

  assets        1
  liabilities   1
  equity       -2
Old account type syntax

In some hledger journals you might instead see this old syntax (the letters ALERX, separated from the account name by two or more spaces); this is deprecated and may be removed soon:

account assets       A
account liabilities  L
account equity       E
account revenues     R
account expenses     X

Account display order

Account directives also set the order in which accounts are displayed, eg in reports, the hledger-ui accounts screen, and the hledger-web sidebar. By default accounts are listed in alphabetical order. But if you have these account directives in the journal:

account assets
account liabilities
account equity
account revenues
account expenses

you'll see those accounts displayed in declaration order, not alphabetically:

$ hledger accounts -1

Undeclared accounts, if any, are displayed last, in alphabetical order.

Note that sorting is done at each level of the account tree (within each group of sibling accounts under the same parent). And currently, this directive:

account other:zoo

would influence the position of zoo among other's subaccounts, but not the position of other among the top-level accounts. This means:

  • you will sometimes declare parent accounts (eg account other above) that you don't intend to post to, just to customize their display order
  • sibling accounts stay together (you couldn't display x:y in between a:b and a:c).

Rewriting accounts

You can define account alias rules which rewrite your account names, or parts of them, before generating reports. This can be useful for:

  • expanding shorthand account names to their full form, allowing easier data entry and a less verbose journal
  • adapting old journals to your current chart of accounts
  • experimenting with new account organisations, like a new hierarchy or combining two accounts into one
  • customising reports

Account aliases also rewrite account names in account directives. They do not affect account names being entered via hledger add or hledger-web.

See also Rewrite account names.

Basic aliases

To set an account alias, use the alias directive in your journal file. This affects all subsequent journal entries in the current file or its included files. The spaces around the = are optional:

alias OLD = NEW

Or, you can use the --alias 'OLD=NEW' option on the command line. This affects all entries. It's useful for trying out aliases interactively.

OLD and NEW are case sensitive full account names. hledger will replace any occurrence of the old account name with the new one. Subaccounts are also affected. Eg:

alias checking = assets:bank:wells fargo:checking
; rewrites "checking" to "assets:bank:wells fargo:checking", or "checking:a" to "assets:bank:wells fargo:checking:a"

Regex aliases

There is also a more powerful variant that uses a regular expression, indicated by the forward slashes:


or --alias '/REGEX/=REPLACEMENT'.

REGEX is a case-insensitive regular expression. Anywhere it matches inside an account name, the matched part will be replaced by REPLACEMENT. If REGEX contains parenthesised match groups, these can be referenced by the usual numeric backreferences in REPLACEMENT. Eg:

alias /^(.+):bank:([^:]+):(.*)/ = \1:\2 \3
; rewrites "assets:bank:wells fargo:checking" to  "assets:wells fargo checking"

Also note that REPLACEMENT continues to the end of line (or on command line, to end of option argument), so it can contain trailing whitespace.

Combining aliases

You can define as many aliases as you like, using journal directives and/or command line options.

Recursive aliases - where an account name is rewritten by one alias, then by another alias, and so on - are allowed. Each alias sees the effect of previously applied aliases.

In such cases it can be important to understand which aliases will be applied and in which order. For (each account name in) each journal entry, we apply:

  1. alias directives preceding the journal entry, most recently parsed first (ie, reading upward from the journal entry, bottom to top)
  2. --alias options, in the order they appeared on the command line (left to right).

In other words, for (an account name in) a given journal entry:

  • the nearest alias declaration before/above the entry is applied first
  • the next alias before/above that will be be applied next, and so on
  • aliases defined after/below the entry do not affect it.

This gives nearby aliases precedence over distant ones, and helps provide semantic stability - aliases will keep working the same way independent of which files are being read and in which order.

In case of trouble, adding --debug=6 to the command line will show which aliases are being applied when.

Aliases and multiple files

As explained at Directives and multiple files, alias directives do not affect parent or sibling files. Eg in this command,

hledger -f a.aliases -f b.journal

account aliases defined in a.aliases will not affect b.journal. Including the aliases doesn't work either:

include a.aliases

2020-01-01  ; not affected by a.aliases
  foo  1

This means that account aliases should usually be declared at the start of your top-most file, like this:

alias foo=Foo
alias bar=Bar

2020-01-01  ; affected by aliases above
  foo  1

include c.journal  ; also affected

end aliases

You can clear (forget) all currently defined aliases with the end aliases directive:

end aliases

Default parent account

You can specify a parent account which will be prepended to all accounts within a section of the journal. Use the apply account and end apply account directives like so:

apply account home

    food    $10

end apply account

which is equivalent to:

    home:food           $10
    home:cash          $-10

If end apply account is omitted, the effect lasts to the end of the file. Included files are also affected, eg:

apply account business
include biz.journal
end apply account
apply account personal
include personal.journal

Prior to hledger 1.0, legacy account and end spellings were also supported.

A default parent account also affects account directives. It does not affect account names being entered via hledger add or hledger-web. If account aliases are present, they are applied after the default parent account.

Periodic transactions

Periodic transaction rules describe transactions that recur. They allow hledger to generate temporary future transactions to help with forecasting, so you don't have to write out each one in the journal, and it's easy to try out different forecasts.

Periodic transactions can be a little tricky, so before you use them, read this whole section - or at least these tips:

  1. Two spaces accidentally added or omitted will cause you trouble - read about this below.

  2. For troubleshooting, show the generated transactions with hledger print --forecast tag:generated or hledger register --forecast tag:generated.

  3. Forecasted transactions will begin only after the last non-forecasted transaction's date.

  4. Forecasted transactions will end 6 months from today, by default. See below for the exact start/end rules.

  5. period expressions can be tricky. Their documentation needs improvement, but is worth studying.

  6. Some period expressions with a repeating interval must begin on a natural boundary of that interval. Eg in weekly from DATE, DATE must be a monday. ~ weekly from 2019/10/1 (a tuesday) will give an error.

  7. Other period expressions with an interval are automatically expanded to cover a whole number of that interval. (This is done to improve reports, but it also affects periodic transactions. Yes, it's a bit inconsistent with the above.) Eg:

    ~ every 10th day of month from 2020/01, which is equivalent to
    ~ every 10th day of month from 2020/01/01, will be adjusted to start on 2019/12/10.

Periodic transaction rules also have a second meaning: they are used to define budget goals, shown in budget reports.

Periodic rule syntax

A periodic transaction rule looks like a normal journal entry, with the date replaced by a tilde (~) followed by a period expression (mnemonic: ~ looks like a recurring sine wave.):

~ monthly
    expenses:rent          $2000

There is an additional constraint on the period expression: the start date must fall on a natural boundary of the interval. Eg monthly from 2018/1/1 is valid, but monthly from 2018/1/15 is not.

Partial or relative dates (M/D, D, tomorrow, last week) in the period expression can work (useful or not). They will be relative to today's date, unless a Y default year directive is in effect, in which case they will be relative to Y/1/1.

Two spaces between period expression and description!

If the period expression is followed by a transaction description, these must be separated by two or more spaces. This helps hledger know where the period expression ends, so that descriptions can not accidentally alter their meaning, as in this example:

; 2 or more spaces needed here, so the period is not understood as "every 2 months in 2020"
;               ||
;               vv
~ every 2 months  in 2020, we will review
    assets:bank:checking   $1500
    income:acme inc


  • Do write two spaces between your period expression and your transaction description, if any.
  • Don't accidentally write two spaces in the middle of your period expression.

Forecasting with periodic transactions

The --forecast flag activates any periodic transaction rules in the journal. They will generate temporary recurring transactions, which are not saved in the journal, but will appear in all reports (eg print). This can be useful for estimating balances into the future, or experimenting with different scenarios. Or, it can be used as a data entry aid: describe recurring transactions, and every so often copy the output of print --forecast into the journal.

These transactions will have an extra tag indicating which periodic rule generated them: generated-transaction:~ PERIODICEXPR. And a similar, hidden tag (beginning with an underscore) which, because it's never displayed by print, can be used to match transactions generated "just now": _generated-transaction:~ PERIODICEXPR.

Periodic transactions are generated within some forecast period. By default, this

  • begins on the later of
    • the report start date if specified with -b/-p/date:
    • the day after the latest normal (non-periodic) transaction in the journal, or today if there are no normal transactions.
  • ends on the report end date if specified with -e/-p/date:, or 6 months (180 days) from today.

This means that periodic transactions will begin only after the latest recorded transaction. And a recorded transaction dated in the future can prevent generation of periodic transactions. (You can avoid that by writing the future transaction as a one-time periodic rule instead - put tilde before the date, eg ~ YYYY-MM-DD ...).

Or, you can set your own arbitrary "forecast period", which can overlap recorded transactions, and need not be in the future, by providing an option argument, like --forecast=PERIODEXPR. Note the equals sign is required, a space won't work. PERIODEXPR is a period expression, which can specify the start date, end date, or both, like in a date: query. (See also hledger.1 -> Report start & end date). Some examples: --forecast=202001-202004, --forecast=jan-, --forecast=2020.

Budgeting with periodic transactions

With the --budget flag, currently supported by the balance command, each periodic transaction rule declares recurring budget goals for the specified accounts. Eg the first example above declares a goal of spending $2000 on rent (and also, a goal of depositing $2000 into checking) every month. Goals and actual performance can then be compared in budget reports.

See also: Budgeting and Forecasting.

Auto postings

"Automated postings" or "auto postings" are extra postings which get added automatically to transactions which match certain queries, defined by "auto posting rules", when you use the --auto flag.

An auto posting rule looks a bit like a transaction:


except the first line is an equals sign (mnemonic: = suggests matching), followed by a query (which matches existing postings), and each "posting" line describes a posting to be generated, and the posting amounts can be:

  • a normal amount with a commodity symbol, eg $2. This will be used as-is.
  • a number, eg 2. The commodity symbol (if any) from the matched posting will be added to this.
  • a numeric multiplier, eg *2 (a star followed by a number N). The matched posting's amount (and total price, if any) will be multiplied by N.
  • a multiplier with a commodity symbol, eg *$2 (a star, number N, and symbol S). The matched posting's amount will be multiplied by N, and its commodity symbol will be replaced with S.

Any query term containing spaces must be enclosed in single or double quotes, as on the command line. Eg, note the quotes around the second query term below:

= expenses:groceries 'expenses:dining out'
    (budget:funds:dining out)                 *-1

Some examples:

; every time I buy food, schedule a dollar donation
= expenses:food
    (liabilities:charity)   $-1

; when I buy a gift, also deduct that amount from a budget envelope subaccount
= expenses:gifts
    assets:checking:gifts  *-1
    assets:checking         *1

  expenses:food    $10

  expenses:gifts   $20
$ hledger print --auto
    expenses:food              $10
    (liabilities:charity)      $-1

    expenses:gifts             $20
    assets:checking:gifts     -$20
    assets:checking            $20

Auto postings and multiple files

An auto posting rule can affect any transaction in the current file, or in any parent file or child file. Note, currently it will not affect sibling files (when multiple -f/--file are used - see #1212).

Auto postings and dates

A posting date (or secondary date) in the matched posting, or (taking precedence) a posting date in the auto posting rule itself, will also be used in the generated posting.

Auto postings and transaction balancing / inferred amounts / balance assertions

Currently, auto postings are added:

Note this means that journal entries must be balanced both before and after auto postings are added. This changed in hledger 1.12+; see #893 for background.

Auto posting tags

Automated postings will have some extra tags:

  • generated-posting:= QUERY - shows this was generated by an auto posting rule, and the query
  • _generated-posting:= QUERY - a hidden tag, which does not appear in hledger's output. This can be used to match postings generated "just now", rather than generated in the past and saved to the journal.

Also, any transaction that has been changed by auto posting rules will have these tags added:

  • modified: - this transaction was modified
  • _modified: - a hidden tag not appearing in the comment; this transaction was modified "just now".


How hledger reads CSV data, and the CSV rules file format.

hledger can read CSV files (Character Separated Value - usually comma, semicolon, or tab) containing dated records as if they were journal files, automatically converting each CSV record into a transaction.

(To learn about writing CSV, see CSV output.)

We describe each CSV file's format with a corresponding rules file. By default this is named like the CSV file with a .rules extension added. Eg when reading FILE.csv, hledger also looks for FILE.csv.rules in the same directory as FILE.csv. You can specify a different rules file with the --rules-file option. If a rules file is not found, hledger will create a sample rules file, which you'll need to adjust.

This file contains rules describing the CSV data (header line, fields layout, date format etc.), and how to construct hledger journal entries (transactions) from it. Often there will also be a list of conditional rules for categorising transactions based on their descriptions. Here's an overview of the CSV rules; these are described more fully below, after the examples:

skipskip one or more header lines or matched CSV records
fieldsname CSV fields, assign them to hledger fields
field assignmentassign a value to one hledger field, with interpolation
separatora custom field separator
if blockapply some rules to CSV records matched by patterns
if tableapply some rules to CSV records matched by patterns, alternate syntax
endskip the remaining CSV records
date-formathow to parse dates in CSV records
decimal-markthe decimal mark used in CSV amounts, if ambiguous
newest-firstdisambiguate record order when there's only one date
includeinline another CSV rules file
balance-typechoose which type of balance assignments to use

Note, for best error messages when reading CSV files, use a .csv, .tsv or .ssv file extension or file prefix - see File Extension below.

There's an introductory Convert CSV files tutorial on hledger.org.


Here are some sample hledger CSV rules files. See also the full collection at:


At minimum, the rules file must identify the date and amount fields, and often it also specifies the date format and how many header lines there are. Here's a simple CSV file and a rules file for it:

Date, Description, Id, Amount
12/11/2019, Foo, 123, 10.23
# basic.csv.rules
skip         1
fields       date, description, _, amount
date-format  %d/%m/%Y
$ hledger print -f basic.csv
2019-11-12 Foo
    expenses:unknown           10.23
    income:unknown            -10.23

Default account names are chosen, since we didn't set them.

Bank of Ireland

Here's a CSV with two amount fields (Debit and Credit), and a balance field, which we can use to add balance assertions, which is not necessary but provides extra error checking:

07/12/2012,LODGMENT       529898,,10.0,131.21
# bankofireland-checking.csv.rules

# skip the header line

# name the csv fields, and assign some of them as journal entry fields
fields  date, description, amount-out, amount-in, balance

# We generate balance assertions by assigning to "balance"
# above, but you may sometimes need to remove these because:
# - the CSV balance differs from the true balance,
#   by up to 0.0000000000005 in my experience
# - it is sometimes calculated based on non-chronological ordering,
#   eg when multiple transactions clear on the same day

# date is in UK/Ireland format
date-format  %d/%m/%Y

# set the currency
currency  EUR

# set the base account for all txns
account1  assets:bank:boi:checking
$ hledger -f bankofireland-checking.csv print
2012-12-07 LODGMENT       529898
    assets:bank:boi:checking         EUR10.0 = EUR131.2
    income:unknown                  EUR-10.0

2012-12-07 PAYMENT
    assets:bank:boi:checking         EUR-5.0 = EUR126.0
    expenses:unknown                  EUR5.0

The balance assertions don't raise an error above, because we're reading directly from CSV, but they will be checked if these entries are imported into a journal file.


Here we convert amazon.com order history, and use an if block to generate a third posting if there's a fee. (In practice you'd probably get this data from your bank instead, but it's an example.)

"Date","Type","To/From","Name","Status","Amount","Fees","Transaction ID"
"Jul 29, 2012","Payment","To","Foo.","Completed","$20.00","$0.00","16000000000000DGLNJPI1P9B8DKPVHL"
"Jul 30, 2012","Payment","To","Adapteva, Inc.","Completed","$25.00","$1.00","17LA58JSKRD4HDGLNJPI1P9B8DKPVHL"
# amazon-orders.csv.rules

# skip one header line
skip 1

# name the csv fields, and assign the transaction's date, amount and code.
# Avoided the "status" and "amount" hledger field names to prevent confusion.
fields date, _, toorfrom, name, amzstatus, amzamount, fees, code

# how to parse the date
date-format %b %-d, %Y

# combine two fields to make the description
description %toorfrom %name

# save the status as a tag
comment     status:%amzstatus

# set the base account for all transactions
account1    assets:amazon
# leave amount1 blank so it can balance the other(s).
# I'm assuming amzamount excludes the fees, don't remember

# set a generic account2
account2    expenses:misc
amount2     %amzamount
# and maybe refine it further:
#include categorisation.rules

# add a third posting for fees, but only if they are non-zero.
if %fees [1-9]
 account3    expenses:fees
 amount3     %fees
$ hledger -f amazon-orders.csv print
2012-07-29 (16000000000000DGLNJPI1P9B8DKPVHL) To Foo.  ; status:Completed
    expenses:misc          $20.00

2012-07-30 (17LA58JSKRD4HDGLNJPI1P9B8DKPVHL) To Adapteva, Inc.  ; status:Completed
    expenses:misc          $25.00
    expenses:fees           $1.00


Here's a real-world rules file for (customised) Paypal CSV, with some Paypal-specific rules, and a second rules file included:

"Date","Time","TimeZone","Name","Type","Status","Currency","Gross","Fee","Net","From Email Address","To Email Address","Transaction ID","Item Title","Item ID","Reference Txn ID","Receipt ID","Balance","Note"
"10/01/2019","03:46:20","PDT","Calm Radio","Subscription Payment","Completed","USD","-6.99","0.00","-6.99","[email protected]","[email protected]","60P57143A8206782E","MONTHLY - $1 for the first 2 Months: Me - Order 99309. Item total: $1.00 USD first 2 months, then $6.99 / Month","","I-R8YLY094FJYR","","-6.99",""
"10/01/2019","03:46:20","PDT","","Bank Deposit to PP Account ","Pending","USD","6.99","0.00","6.99","","[email protected]","0TU1544T080463733","","","60P57143A8206782E","","0.00",""
"10/01/2019","08:57:01","PDT","Patreon","PreApproved Payment Bill User Payment","Completed","USD","-7.00","0.00","-7.00","[email protected]","[email protected]","2722394R5F586712G","Patreon* Membership","","B-0PG93074E7M86381M","","-7.00",""
"10/01/2019","08:57:01","PDT","","Bank Deposit to PP Account ","Pending","USD","7.00","0.00","7.00","","[email protected]","71854087RG994194F","Patreon* Membership","","2722394R5F586712G","","0.00",""
"10/19/2019","03:02:12","PDT","Wikimedia Foundation, Inc.","Subscription Payment","Completed","USD","-2.00","0.00","-2.00","[email protected]","[email protected]","K9U43044RY432050M","Monthly donation to the Wikimedia Foundation","","I-R5C3YUS3285L","","-2.00",""
"10/19/2019","03:02:12","PDT","","Bank Deposit to PP Account ","Pending","USD","2.00","0.00","2.00","","[email protected]","3XJ107139A851061F","","","K9U43044RY432050M","","0.00",""
"10/22/2019","05:07:06","PDT","Noble Benefactor","Subscription Payment","Completed","USD","10.00","-0.59","9.41","[email protected]","[email protected]","6L8L1662YP1334033","Joyful Systems","","I-KC9VBGY2GWDB","","9.41",""
# paypal-custom.csv.rules

# Tips:
# Export from Activity -> Statements -> Custom -> Activity download
# Suggested transaction type: "Balance affecting"
# Paypal's default fields in 2018 were:
# "Date","Time","TimeZone","Name","Type","Status","Currency","Gross","Fee","Net","From Email Address","To Email Address","Transaction ID","Shipping Address","Address Status","Item Title","Item ID","Shipping and Handling Amount","Insurance Amount","Sales Tax","Option 1 Name","Option 1 Value","Option 2 Name","Option 2 Value","Reference Txn ID","Invoice Number","Custom Number","Quantity","Receipt ID","Balance","Address Line 1","Address Line 2/District/Neighborhood","Town/City","State/Province/Region/County/Territory/Prefecture/Republic","Zip/Postal Code","Country","Contact Phone Number","Subject","Note","Country Code","Balance Impact"
# This rules file assumes the following more detailed fields, configured in "Customize report fields":
# "Date","Time","TimeZone","Name","Type","Status","Currency","Gross","Fee","Net","From Email Address","To Email Address","Transaction ID","Item Title","Item ID","Reference Txn ID","Receipt ID","Balance","Note"

fields date, time, timezone, description_, type, status_, currency, grossamount, feeamount, netamount, fromemail, toemail, code, itemtitle, itemid, referencetxnid, receiptid, balance, note

skip  1

date-format  %-m/%-d/%Y

# ignore some paypal events
In Progress
Temporary Hold
Update to

# add more fields to the description
description %description_ %itemtitle

# save some other fields as tags
comment  itemid:%itemid, fromemail:%fromemail, toemail:%toemail, time:%time, type:%type, status:%status_

# convert to short currency symbols
if %currency USD
 currency $
if %currency EUR
 currency E
if %currency GBP
 currency P

# generate postings

# the first posting will be the money leaving/entering my paypal account
# (negative means leaving my account, in all amount fields)
account1 assets:online:paypal
amount1  %netamount

# the second posting will be money sent to/received from other party
# (account2 is set below)
amount2  -%grossamount

# if there's a fee, add a third posting for the money taken by paypal.
if %feeamount [1-9]
 account3 expenses:banking:paypal
 amount3  -%feeamount
 comment3 business:

# choose an account for the second posting

# override the default account names:
# if the amount is positive, it's income (a debit)
if %grossamount ^[^-]
 account2 income:unknown
# if negative, it's an expense (a credit)
if %grossamount ^-
 account2 expenses:unknown

# apply common rules for setting account2 & other tweaks
include common.rules

# apply some overrides specific to this csv

# Transfers from/to bank. These are usually marked Pending,
# which can be disregarded in this case.
Bank Account
Bank Deposit to PP Account
 description %type for %referencetxnid %itemtitle
 account2 assets:bank:wf:pchecking
 account1 assets:online:paypal

# Currency conversions
if Currency Conversion
 account2 equity:currency conversion
# common.rules

noble benefactor
 account2 revenues:foss donations:darcshub
 comment2 business:

Calm Radio
 account2 expenses:online:apps

electronic frontier foundation
Advent of Code
 account2 expenses:dues

if Google
 account2 expenses:online:apps
 description google | music
$ hledger -f paypal-custom.csv  print
2019-10-01 (60P57143A8206782E) Calm Radio MONTHLY - $1 for the first 2 Months: Me - Order 99309. Item total: $1.00 USD first 2 months, then $6.99 / Month  ; itemid:, fromemail:[email protected], toemail:[email protected], time:03:46:20, type:Subscription Payment, status:Completed
    assets:online:paypal          $-6.99 = $-6.99
    expenses:online:apps           $6.99

2019-10-01 (0TU1544T080463733) Bank Deposit to PP Account for 60P57143A8206782E  ; itemid:, fromemail:, toemail:[email protected], time:03:46:20, type:Bank Deposit to PP Account, status:Pending
    assets:online:paypal               $6.99 = $0.00
    assets:bank:wf:pchecking          $-6.99

2019-10-01 (2722394R5F586712G) Patreon Patreon* Membership  ; itemid:, fromemail:[email protected], toemail:[email protected], time:08:57:01, type:PreApproved Payment Bill User Payment, status:Completed
    assets:online:paypal          $-7.00 = $-7.00
    expenses:dues                  $7.00

2019-10-01 (71854087RG994194F) Bank Deposit to PP Account for 2722394R5F586712G Patreon* Membership  ; itemid:, fromemail:, toemail:[email protected], time:08:57:01, type:Bank Deposit to PP Account, status:Pending
    assets:online:paypal               $7.00 = $0.00
    assets:bank:wf:pchecking          $-7.00

2019-10-19 (K9U43044RY432050M) Wikimedia Foundation, Inc. Monthly donation to the Wikimedia Foundation  ; itemid:, fromemail:[email protected], toemail:[email protected], time:03:02:12, type:Subscription Payment, status:Completed
    assets:online:paypal             $-2.00 = $-2.00
    expenses:dues                     $2.00
    expenses:banking:paypal      ; business:

2019-10-19 (3XJ107139A851061F) Bank Deposit to PP Account for K9U43044RY432050M  ; itemid:, fromemail:, toemail:[email protected], time:03:02:12, type:Bank Deposit to PP Account, status:Pending
    assets:online:paypal               $2.00 = $0.00
    assets:bank:wf:pchecking          $-2.00

2019-10-22 (6L8L1662YP1334033) Noble Benefactor Joyful Systems  ; itemid:, fromemail:[email protected], toemail:[email protected], time:05:07:06, type:Subscription Payment, status:Completed
    assets:online:paypal                       $9.41 = $9.41
    revenues:foss donations:darcshub         $-10.00  ; business:
    expenses:banking:paypal                    $0.59  ; business:

CSV rules

The following kinds of rule can appear in the rules file, in any order. Blank lines and lines beginning with # or ; are ignored.

skip N

The word "skip" followed by a number (or no number, meaning 1) tells hledger to ignore this many non-empty lines preceding the CSV data. (Empty/blank lines are skipped automatically.) You'll need this whenever your CSV data contains header lines.

It also has a second purpose: it can be used inside if blocks to ignore certain CSV records (described below).



A fields list (the word "fields" followed by comma-separated field names) is the quick way to assign CSV field values to hledger fields. It does two things:

  1. it names the CSV fields. This is optional, but can be convenient later for interpolating them.

  2. when you use a standard hledger field name, it assigns the CSV value to that part of the hledger transaction.

Here's an example that says "use the 1st, 2nd and 4th fields as the transaction's date, description and amount; name the last two fields for later reference; and ignore the others":

fields date, description, , amount, , , somefield, anotherfield

Field names may not contain whitespace. Fields you don't care about can be left unnamed. Currently there must be least two items (there must be at least one comma).

Note, always use comma in the fields list, even if your CSV uses another separator character.

Here are the standard hledger field/pseudo-field names. For more about the transaction parts they refer to, see the manual for hledger's journal format.

Transaction field names

date, date2, status, code, description, comment can be used to form the transaction's first line.

Posting field names

accountN, where N is 1 to 99, causes a posting to be generated, with that account name.

Most often there are two postings, so you'll want to set account1 and account2. Typically account1 is associated with the CSV file, and is set once with a top-level assignment, while account2 is set based on each transaction's description, and in conditional blocks.

If a posting's account name is left unset but its amount is set (see below), a default account name will be chosen (like "expenses:unknown" or "income:unknown").


amountN sets posting N's amount. If the CSV uses separate fields for inflows and outflows, you can use amountN-in and amountN-out instead. By assigning to amount1, amount2, ... etc. you can generate anywhere from 0 to 99 postings.

There is also an older, unnumbered form of these names, suitable for 2-posting transactions, which sets both posting 1's and (negated) posting 2's amount: amount, or amount-in and amount-out. This is still supported because it keeps pre-hledger-1.17 csv rules files working, and because it can be more succinct, and because it converts posting 2's amount to cost if there's a transaction price, which can be useful.

If you have an existing rules file using the unnumbered form, you might want to use the numbered form in certain conditional blocks, without having to update and retest all the old rules. To facilitate this, posting 1 ignores amount/amount-in/amount-out if any of amount1/amount1-in/amount1-out are assigned, and posting 2 ignores them if any of amount2/amount2-in/amount2-out are assigned, avoiding conflicts.


If the CSV has the currency symbol in a separate field (ie, not part of the amount field), you can use currencyN to prepend it to posting N's amount. Or, currency with no number affects all postings.


balanceN sets a balance assertion amount (or if the posting amount is left empty, a balance assignment) on posting N.

Also, for compatibility with hledger <1.17: balance with no number is equivalent to balance1.

You can adjust the type of assertion/assignment with the balance-type rule (see below).


Finally, commentN sets a comment on the Nth posting. Comments can also contain tags, as usual.

See TIPS below for more about setting amounts and currency.

field assignment


Instead of or in addition to a fields list, you can use a "field assignment" rule to set the value of a single hledger field, by writing its name (any of the standard hledger field names above) followed by a text value. The value may contain interpolated CSV fields, referenced by their 1-based position in the CSV record (%N), or by the name they were given in the fields list (%CSVFIELDNAME). Some examples:

# set the amount to the 4th CSV field, with " USD" appended
amount %4 USD

# combine three fields to make a comment, containing note: and date: tags
comment note: %somefield - %anotherfield, date: %1

Interpolation strips outer whitespace (so a CSV value like " 1 " becomes 1 when interpolated) (#1051). See TIPS below for more about referencing other fields.


You can use the separator rule to read other kinds of character-separated data. The argument is any single separator character, or the words tab or space (case insensitive). Eg, for comma-separated values (CSV):

separator ,

or for semicolon-separated values (SSV):

separator ;

or for tab-separated values (TSV):

separator TAB

If the input file has a .csv, .ssv or .tsv file extension (or a csv:, ssv:, tsv: prefix), the appropriate separator will be inferred automatically, and you won't need this rule.

if block



Conditional blocks ("if blocks") are a block of rules that are applied only to CSV records which match certain patterns. They are often used for customising account names based on transaction descriptions.

Matching the whole record

Each MATCHER can be a record matcher, which looks like this:


REGEX is a case-insensitive regular expression which tries to match anywhere within the CSV record. It is a POSIX ERE (extended regular expression) that also supports GNU word boundaries (\b, \B, \<, \>), and nothing else. If you have trouble, be sure to check our https://hledger.org/hledger.html#regular-expressions doc.

Important note: the record that is matched is not the original record, but a synthetic one, with any enclosing double quotes (but not enclosing whitespace) removed, and always comma-separated (which means that a field containing a comma will appear like two fields). Eg, if the original record is 2020-01-01; "Acme, Inc."; 1,000, the REGEX will actually see 2020-01-01,Acme, Inc., 1,000).

Matching individual fields

Or, MATCHER can be a field matcher, like this:


which matches just the content of a particular CSV field. CSVFIELD is a percent sign followed by the field's name or column number, like %date or %1.

Combining matchers

A single matcher can be written on the same line as the "if"; or multiple matchers can be written on the following lines, non-indented. Multiple matchers are OR'd (any one of them can match), unless one begins with an & symbol, in which case it is AND'ed with the previous matcher.

Rules applied on successful match

After the patterns there should be one or more rules to apply, all indented by at least one space. Three kinds of rule are allowed in conditional blocks:

  • field assignments (to set a hledger field)
  • skip (to skip the matched CSV record)
  • end (to skip all remaining CSV records).


# if the CSV record contains "groceries", set account2 to "expenses:groceries"
if groceries
 account2 expenses:groceries
# if the CSV record contains any of these patterns, set account2 and comment as shown
monthly service fee
atm transaction fee
banking thru software
 account2 expenses:business:banking
 comment  XXX deductible ? check it

if table

<empty line>

Conditional tables ("if tables") are a different syntax to specify field assignments that will be applied only to CSV records which match certain patterns.

MATCHER could be either field or record matcher, as described above. When MATCHER matches, values from that row would be assigned to the CSV fields named on the if line, in the same order.

Therefore if table is exactly equivalent to a sequence of of if blocks:




Each line starting with MATCHER should contain enough (possibly empty) values for all the listed fields.

Rules would be checked and applied in the order they are listed in the table and, like with if blocks, later rules (in the same or another table) or if blocks could override the effect of any rule.

Instead of ',' you can use a variety of other non-alphanumeric characters as a separator. First character after if is taken to be the separator for the rest of the table. It is the responsibility of the user to ensure that separator does not occur inside MATCHERs and values

  • there is no way to escape separator.


atm transaction fee,expenses:business:banking,deductible? check it
%description groceries,expenses:groceries,
2020/01/12.*Plumbing LLC,expenses:house:upkeep,emergency plumbing call-out


This rule can be used inside if blocks (only), to make hledger stop reading this CSV file and move on to the next input file, or to command execution. Eg:

# ignore everything following the first empty record
if ,,,,


date-format DATEFMT

This is a helper for the date (and date2) fields. If your CSV dates are not formatted like YYYY-MM-DD, YYYY/MM/DD or YYYY.MM.DD, you'll need to add a date-format rule describing them with a strptime date parsing pattern, which must parse the CSV date value completely. Some examples:

date-format %m/%d/%y
# The - makes leading zeros optional.
date-format %-d/%-m/%Y
date-format %Y-%h-%d
# M/D/YYYY HH:MM AM some other junk
# Note the time and junk must be fully parsed, though only the date is used.
date-format %-m/%-d/%Y %l:%M %p some other junk

For the supported strptime syntax, see:


decimal-mark .


decimal-mark ,

hledger automatically accepts either period or comma as a decimal mark when parsing numbers (cf Amounts). However if any numbers in the CSV contain digit group marks, such as thousand-separating commas, you should declare the decimal mark explicitly with this rule, to avoid misparsed numbers.


hledger always sorts the generated transactions by date. Transactions on the same date should appear in the same order as their CSV records, as hledger can usually auto-detect whether the CSV's normal order is oldest first or newest first. But if all of the following are true:

  • the CSV might sometimes contain just one day of data (all records having the same date)
  • the CSV records are normally in reverse chronological order (newest at the top)
  • and you care about preserving the order of same-day transactions

then, you should add the newest-first rule as a hint. Eg:

# tell hledger explicitly that the CSV is normally newest first



This includes the contents of another CSV rules file at this point. RULESFILE is an absolute file path or a path relative to the current file's directory. This can be useful for sharing common rules between several rules files, eg:

# someaccount.csv.rules

## someaccount-specific rules
fields   date,description,amount
account1 assets:someaccount
account2 expenses:misc

## common rules
include categorisation.rules


Balance assertions generated by assigning to balanceN are of the simple = type by default, which is a single-commodity, subaccount-excluding assertion. You may find the subaccount-including variants more useful, eg if you have created some virtual subaccounts of checking to help with budgeting. You can select a different type of assertion with the balance-type rule:

# balance assertions will consider all commodities and all subaccounts
balance-type ==*

Here are the balance assertion types for quick reference:

=    single commodity, exclude subaccounts
=*   single commodity, include subaccounts
==   multi commodity,  exclude subaccounts
==*  multi commodity,  include subaccounts


Rapid feedback

It's a good idea to get rapid feedback while creating/troubleshooting CSV rules. Here's a good way, using entr from http://eradman.com/entrproject :

$ ls foo.csv* | entr bash -c 'echo ----; hledger -f foo.csv print desc:SOMEDESC'

A desc: query (eg) is used to select just one, or a few, transactions of interest. "bash -c" is used to run multiple commands, so we can echo a separator each time the command re-runs, making it easier to read the output.

Valid CSV

hledger accepts CSV conforming to RFC 4180. When CSV values are enclosed in quotes, note:

  • they must be double quotes (not single quotes)
  • spaces outside the quotes are not allowed

File Extension

To help hledger identify the format and show the right error messages, CSV/SSV/TSV files should normally be named with a .csv, .ssv or .tsv filename extension. Or, the file path should be prefixed with csv:, ssv: or tsv:. Eg:

$ hledger -f foo.ssv print


$ cat foo | hledger -f ssv:- foo

You can override the file extension with a separator rule if needed. See also: Input files in the hledger manual.

Reading multiple CSV files

If you use multiple -f options to read multiple CSV files at once, hledger will look for a correspondingly-named rules file for each CSV file. But if you use the --rules-file option, that rules file will be used for all the CSV files.

Valid transactions

After reading a CSV file, hledger post-processes and validates the generated journal entries as it would for a journal file - balancing them, applying balance assignments, and canonicalising amount styles. Any errors at this stage will be reported in the usual way, displaying the problem entry.

There is one exception: balance assertions, if you have generated them, will not be checked, since normally these will work only when the CSV data is part of the main journal. If you do need to check balance assertions generated from CSV right away, pipe into another hledger:

$ hledger -f file.csv print | hledger -f- print

Deduplicating, importing

When you download a CSV file periodically, eg to get your latest bank transactions, the new file may overlap with the old one, containing some of the same records.

The import command will (a) detect the new transactions, and (b) append just those transactions to your main journal. It is idempotent, so you don't have to remember how many times you ran it or with which version of the CSV. (It keeps state in a hidden .latest.FILE.csv file.) This is the easiest way to import CSV data. Eg:

# download the latest CSV files, then run this command.
# Note, no -f flags needed here.
$ hledger import *.csv [--dry]

This method works for most CSV files. (Where records have a stable chronological order, and new records appear only at the new end.)

A number of other tools and workflows, hledger-specific and otherwise, exist for converting, deduplicating, classifying and managing CSV data. See:

Setting amounts

Some tips on using the amount-setting rules discussed above.

Here are the ways to set a posting's amount:

  1. If the CSV has a single amount field:
    Assign (via a fields list or a field assignment) to amountN. This sets the Nth posting's amount. N is usually 1 or 2 but can go up to 99.

  2. If the CSV has separate Debit and Credit amount fields:
    Assign to amountN-in and amountN-out. This sets posting N's amount to whichever of these has a non-zero value, guessing an appropriate sign.

    • If hledger guesses the wrong sign:
      Prepend a minus sign to flip it. Eg:

      fields date, description, amount-in, amount-out
      amount-out -%amount-out
    • If both fields contain a non-zero value:
      The amountN-in/amountN-out rules require that each CSV record has a non-zero value in exactly one of the two fields, so that hledger knows which to choose. So these would all be rejected:

      "",  ""
      "0", "0"
      "1", "none"

      If your CSV has amount values like this, use conditional rules instead. For example, to make hledger to choose the value containing non-zero digits:

      fields date, description, in, out
      if %in [1-9]
       amount1 %in
      if %out [1-9]
       amount1 %out
  3. Using the old numberless syntax:
    Assign to amount (or to amount-in and amount-out). This sets posting 1's and posting 2's amounts (and converts posting 2's amount to cost). This is supported for backwards compatibility (and occasional convenience).

  4. If the CSV has the balance instead of the transaction amount:
    Assign to balanceN, which sets posting N's amount indirectly via a balance assignment. (Old syntax: balance, equivalent to balance1.)

    • If hledger guesses the wrong default account name:
      When setting the amount via balance assertion, hledger may guess the wrong default account name. So, set the account name explicitly, eg:

      fields date, description, balance1
      account1 assets:checking

Amount signs

There is some special handling for amount signs, to simplify parsing and sign-flipping:

  • If an amount value begins with a plus sign:
    that will be removed: +AMT becomes AMT

  • If an amount value is parenthesised:
    it will be de-parenthesised and sign-flipped: (AMT) becomes -AMT

  • If an amount value has two minus signs (or two sets of parentheses, or a minus sign and parentheses):
    they cancel out and will be removed: --AMT or -(AMT) becomes AMT

  • If an amount value contains just a sign (or just a set of parentheses):
    that is removed, making it an empty value. "+" or "-" or "()" becomes "".

Setting currency/commodity

If the currency/commodity symbol is included in the CSV's amount field(s):


you don't have to do anything special for the commodity symbol, it will be assigned as part of the amount. Eg:

fields date,description,amount
2020-01-01 foo
    expenses:unknown         $123.00
    income:unknown          $-123.00

If the currency is provided as a separate CSV field:


You can assign that to the currency pseudo-field, which has the special effect of prepending itself to every amount in the transaction (on the left, with no separating space):

fields date,description,currency,amount
2020-01-01 foo
    expenses:unknown       USD123.00
    income:unknown        USD-123.00

Or, you can use a field assignment to construct the amount yourself, with more control. Eg to put the symbol on the right, and separated by a space:

fields date,description,cur,amt
amount %amt %cur
2020-01-01 foo
    expenses:unknown        123.00 USD
    income:unknown         -123.00 USD

Note we used a temporary field name (cur) that is not currency - that would trigger the prepending effect, which we don't want here.

Amount decimal places

Like amounts in a journal file, the amounts generated by CSV rules like amount1 influence commodity display styles, such as the number of decimal places displayed in reports.

The original amounts as written in the CSV file do not affect display style (because we don't yet reliably know their commodity).

Referencing other fields

In field assignments, you can interpolate only CSV fields, not hledger fields. In the example below, there's both a CSV field and a hledger field named amount1, but %amount1 always means the CSV field, not the hledger field:

# Name the third CSV field "amount1"
fields date,description,amount1

# Set hledger's amount1 to the CSV amount1 field followed by USD
amount1 %amount1 USD

# Set comment to the CSV amount1 (not the amount1 assigned above)
comment %amount1

Here, since there's no CSV amount1 field, %amount1 will produce a literal "amount1":

fields date,description,csvamount
amount1 %csvamount USD
# Can't interpolate amount1 here
comment %amount1

When there are multiple field assignments to the same hledger field, only the last one takes effect. Here, comment's value will be be B, or C if "something" is matched, but never A:

comment A
comment B
if something
 comment C

How CSV rules are evaluated

Here's how to think of CSV rules being evaluated (if you really need to). First,

  • include - all includes are inlined, from top to bottom, depth first. (At each include point the file is inlined and scanned for further includes, recursively, before proceeding.)

Then "global" rules are evaluated, top to bottom. If a rule is repeated, the last one wins:

  • skip (at top level)
  • date-format
  • newest-first
  • fields - names the CSV fields, optionally sets up initial assignments to hledger fields

Then for each CSV record in turn:

  • test all if blocks. If any of them contain a end rule, skip all remaining CSV records. Otherwise if any of them contain a skip rule, skip that many CSV records. If there are multiple matched skip rules, the first one wins.
  • collect all field assignments at top level and in matched if blocks. When there are multiple assignments for a field, keep only the last one.
  • compute a value for each hledger field - either the one that was assigned to it (and interpolate the %CSVFIELDNAME references), or a default
  • generate a synthetic hledger transaction from these values.

This is all part of the CSV reader, one of several readers hledger can use to parse input files. When all files have been read successfully, the transactions are passed as input to whichever hledger command the user specified.


The time logging format of timeclock.el, as read by hledger.

hledger can read time logs in timeclock format. As with Ledger, these are (a subset of) timeclock.el's format, containing clock-in and clock-out entries as in the example below. The date is a simple date. The time format is HH:MM[:SS][+-ZZZZ]. Seconds and timezone are optional. The timezone, if present, must be four digits and is ignored (currently the time is always interpreted as a local time).

i 2015/03/30 09:00:00 some:account name  optional description after two spaces
o 2015/03/30 09:20:00
i 2015/03/31 22:21:45 another account
o 2015/04/01 02:00:34

hledger treats each clock-in/clock-out pair as a transaction posting some number of hours to an account. Or if the session spans more than one day, it is split into several transactions, one for each day. For the above time log, hledger print generates these journal entries:

$ hledger -f t.timeclock print
2015-03-30 * optional description after two spaces
    (some:account name)         0.33h

2015-03-31 * 22:21-23:59
    (another account)         1.64h

2015-04-01 * 00:00-02:00
    (another account)         2.01h

Here is a sample.timeclock to download and some queries to try:

$ hledger -f sample.timeclock balance                               # current time balances
$ hledger -f sample.timeclock register -p 2009/3                    # sessions in march 2009
$ hledger -f sample.timeclock register -p weekly --depth 1 --empty  # time summary by week

To generate time logs, ie to clock in and clock out, you could:

  • use emacs and the built-in timeclock.el, or the extended timeclock-x.el and perhaps the extras in ledgerutils.el

  • at the command line, use these bash aliases: shell alias ti="echo i date '+%Y-%m-%d %H:%M:%S'\$* >>$TIMELOG" alias to="echo odate '+%Y-%m-%d %H:%M:%S' >>$TIMELOG"

  • or use the old ti and to scripts in the ledger 2.x repository. These rely on a "timeclock" executable which I think is just the ledger 2 executable renamed.


hledger's human-friendly time logging format.

Timedot is a plain text format for logging dated, categorised quantities (of time, usually), supported by hledger. It is convenient for approximate and retroactive time logging, eg when the real-time clock-in/out required with a timeclock file is too precise or too interruptive. It can be formatted like a bar chart, making clear at a glance where time was spent.

Though called "timedot", this format is read by hledger as commodityless quantities, so it could be used to represent dated quantities other than time. In the docs below we'll assume it's time.

A timedot file contains a series of day entries. A day entry begins with a non-indented hledger-style simple date (Y-M-D, Y/M/D, Y.M.D..) Any additional text on the same line is used as a transaction description for this day.

This is followed by optionally-indented timelog items for that day, one per line. Each timelog item is a note, usually a hledger:style:account:name representing a time category, followed by two or more spaces, and a quantity. Each timelog item generates a hledger transaction.

Quantities can be written as:

  • dots: a sequence of dots (.) representing quarter hours. Spaces may optionally be used for grouping. Eg: .... ..

  • an integral or decimal number, representing hours. Eg: 1.5

  • an integral or decimal number immediately followed by a unit symbol s, m, h, d, w, mo, or y, representing seconds, minutes, hours, days weeks, months or years respectively. Eg: 90m. The following equivalencies are assumed, currently: 1m = 60s, 1h = 60m, 1d = 24h, 1w = 7d, 1mo = 30d, 1y=365d.

There is some flexibility allowing notes and todo lists to be kept right in the time log, if needed:

  • Blank lines and lines beginning with # or ; are ignored.

  • Lines not ending with a double-space and quantity are parsed as items taking no time, which will not appear in balance reports by default. (Add -E to see them.)

  • Org mode headlines (lines beginning with one or more * followed by a space) can be used as date lines or timelog items (the stars are ignored). Also all org headlines before the first date line are ignored. This means org users can manage their timelog as an org outline (eg using org-mode/orgstruct-mode in Emacs), for organisation, faster navigation, controlling visibility etc.


# on this day, 6h was spent on client work, 1.5h on haskell FOSS work, etc.
inc:client1   .... .... .... .... .... ....
fos:haskell   .... ..
biz:research  .

inc:client1   .... ....
biz:research  .
inc:client1   4
fos:hledger   3
biz:research  1
* Time log
** 2020-01-01
*** adm:time  .
*** adm:finance  .
* 2020 Work Diary
** Q1
*** 2020-02-29
**** DONE
0700 yoga
**** BEGUN
 cleaning  ...
 water plants
  outdoor - one full watering can
  indoor - light watering
**** TODO
adm:planning: trip


$ hledger -f t.timedot print date:2016/2/2
2016-02-02 *
    (inc:client1)          2.00

2016-02-02 *
    (biz:research)          0.25
$ hledger -f t.timedot bal --daily --tree
Balance changes in 2016-02-01-2016-02-03:

            ||  2016-02-01d  2016-02-02d  2016-02-03d 
 biz        ||         0.25         0.25         1.00 
   research ||         0.25         0.25         1.00 
 fos        ||         1.50            0         3.00 
   haskell  ||         1.50            0            0 
   hledger  ||            0            0         3.00 
 inc        ||         6.00         2.00         4.00 
   client1  ||         6.00         2.00         4.00 
            ||         7.75         2.25         8.00 

I prefer to use period for separating account components. We can make this work with an account alias:

fos.hledger.timedot  4
fos.ledger           ..
$ hledger -f t.timedot --alias /\\./=: bal date:2016/2/4 --tree
                4.50  fos
                4.00    hledger:timedot
                0.50    ledger

Here is a sample.timedot.


Here are some quick examples of how to do some basic tasks with hledger. For more details, see the reference section below, the hledger_journal(5) manual, or the more extensive docs at https://hledger.org.

Getting help

$ hledger                 # show available commands
$ hledger --help          # show common options
$ hledger CMD --help      # show common and command options, and command help
$ hledger help            # show available manuals/topics
$ hledger help hledger    # show hledger manual as info/man/text (auto-chosen)
$ hledger help journal --man  # show the journal manual as a man page
$ hledger help --help     # show more detailed help for the help command

Find more docs, chat, mail list, reddit, issue tracker: https://hledger.org#help-feedback

Constructing command lines

hledger has an extensive and powerful command line interface. We strive to keep it simple and ergonomic, but you may run into one of the confusing real world details described in OPTIONS, below. If that happens, here are some tips that may help:

  • command-specific options must go after the command (it's fine to put all options there) (hledger CMD OPTS ARGS)
  • running add-on executables directly simplifies command line parsing (hledger-ui OPTS ARGS)
  • enclose "problematic" args in single quotes
  • if needed, also add a backslash to hide regular expression metacharacters from the shell
  • to see how a misbehaving command is being parsed, add --debug=2.

Starting a journal file

hledger looks for your accounting data in a journal file, $HOME/.hledger.journal by default:

$ hledger stats
The hledger journal file "/Users/simon/.hledger.journal" was not found.
Please create it first, eg with "hledger add" or a text editor.
Or, specify an existing journal file with -f or LEDGER_FILE.

You can override this by setting the LEDGER_FILE environment variable. It's a good practice to keep this important file under version control, and to start a new file each year. So you could do something like this:

$ mkdir ~/finance
$ cd ~/finance
$ git init
Initialized empty Git repository in /Users/simon/finance/.git/
$ touch 2020.journal
$ echo "export LEDGER_FILE=$HOME/finance/2020.journal" >> ~/.bashrc
$ source ~/.bashrc
$ hledger stats
Main file                : /Users/simon/finance/2020.journal
Included files           : 
Transactions span        :  to  (0 days)
Last transaction         : none
Transactions             : 0 (0.0 per day)
Transactions last 30 days: 0 (0.0 per day)
Transactions last 7 days : 0 (0.0 per day)
Payees/descriptions      : 0
Accounts                 : 0 (depth 0)
Commodities              : 0 ()
Market prices            : 0 ()

Setting opening balances

Pick a starting date for which you can look up the balances of some real-world assets (bank accounts, wallet..) and liabilities (credit cards..).

To avoid a lot of data entry, you may want to start with just one or two accounts, like your checking account or cash wallet; and pick a recent starting date, like today or the start of the week. You can always come back later and add more accounts and older transactions, eg going back to january 1st.

Add an opening balances transaction to the journal, declaring the balances on this date. Here are two ways to do it:

  • The first way: open the journal in any text editor and save an entry like this:

    2020-01-01 * opening balances
        assets:bank:checking                $1000   = $1000
        assets:bank:savings                 $2000   = $2000
        assets:cash                          $100   = $100
        liabilities:creditcard               $-50   = $-50
        equity:opening/closing balances

    These are start-of-day balances, ie whatever was in the account at the end of the previous day.

    The * after the date is an optional status flag. Here it means "cleared & confirmed".

    The currency symbols are optional, but usually a good idea as you'll be dealing with multiple currencies sooner or later.

    The = amounts are optional balance assertions, providing extra error checking.

  • The second way: run hledger add and follow the prompts to record a similar transaction:

    $ hledger add
    Adding transactions to journal file /Users/simon/finance/2020.journal
    Any command line arguments will be used as defaults.
    Use tab key to complete, readline keys to edit, enter to accept defaults.
    An optional (CODE) may follow transaction dates.
    An optional ; COMMENT may follow descriptions or amounts.
    If you make a mistake, enter < at any prompt to go one step backward.
    To end a transaction, enter . when prompted.
    To quit, enter . at a date prompt or press control-d or control-c.
    Date [2020-02-07]: 2020-01-01
    Description: * opening balances
    Account 1: assets:bank:checking
    Amount  1: $1000
    Account 2: assets:bank:savings
    Amount  2 [$-1000]: $2000
    Account 3: assets:cash
    Amount  3 [$-3000]: $100
    Account 4: liabilities:creditcard
    Amount  4 [$-3100]: $-50
    Account 5: equity:opening/closing balances
    Amount  5 [$-3050]: 
    Account 6 (or . or enter to finish this transaction): .
    2020-01-01 * opening balances
        assets:bank:checking                      $1000
        assets:bank:savings                       $2000
        assets:cash                                $100
        liabilities:creditcard                     $-50
        equity:opening/closing balances          $-3050
    Save this transaction to the journal ? [y]: 
    Starting the next transaction (. or ctrl-D/ctrl-C to quit)
    Date [2020-01-01]: .

If you're using version control, this could be a good time to commit the journal. Eg:

$ git commit -m 'initial balances' 2020.journal

Recording transactions

As you spend or receive money, you can record these transactions using one of the methods above (text editor, hledger add) or by using the hledger-iadd or hledger-web add-ons, or by using the import command to convert CSV data downloaded from your bank.

Here are some simple transactions, see the hledger_journal(5) manual and hledger.org for more ideas:

2020/1/10 * gift received
  assets:cash   $20

2020.1.12 * farmers market
  expenses:food    $13

2020-01-15 paycheck
  assets:bank:checking    $1000


Periodically you should reconcile - compare your hledger-reported balances against external sources of truth, like bank statements or your bank's website - to be sure that your ledger accurately represents the real-world balances (and, that the real-world institutions have not made a mistake!). This gets easy and fast with (1) practice and (2) frequency. If you do it daily, it can take 2-10 minutes. If you let it pile up, expect it to take longer as you hunt down errors and discrepancies.

A typical workflow:

  1. Reconcile cash. Count what's in your wallet. Compare with what hledger reports (hledger bal cash). If they are different, try to remember the missing transaction, or look for the error in the already-recorded transactions. A register report can be helpful (hledger reg cash). If you can't find the error, add an adjustment transaction. Eg if you have $105 after the above, and can't explain the missing $2, it could be:

    2020-01-16 * adjust cash
        assets:cash    $-2 = $105
  2. Reconcile checking. Log in to your bank's website. Compare today's (cleared) balance with hledger's cleared balance (hledger bal checking -C). If they are different, track down the error or record the missing transaction(s) or add an adjustment transaction, similar to the above. Unlike the cash case, you can usually compare the transaction history and running balance from your bank with the one reported by hledger reg checking -C. This will be easier if you generally record transaction dates quite similar to your bank's clearing dates.

  3. Repeat for other asset/liability accounts.

Tip: instead of the register command, use hledger-ui to see a live-updating register while you edit the journal: hledger-ui --watch --register checking -C

After reconciling, it could be a good time to mark the reconciled transactions' status as "cleared and confirmed", if you want to track that, by adding the * marker. Eg in the paycheck transaction above, insert * between 2020-01-15 and paycheck

If you're using version control, this can be another good time to commit:

$ git commit -m 'txns' 2020.journal


Here are some basic reports.

Show all transactions:

$ hledger print
2020-01-01 * opening balances
    assets:bank:checking                      $1000
    assets:bank:savings                       $2000
    assets:cash                                $100
    liabilities:creditcard                     $-50
    equity:opening/closing balances          $-3050

2020-01-10 * gift received
    assets:cash              $20

2020-01-12 * farmers market
    expenses:food             $13

2020-01-15 * paycheck
    assets:bank:checking           $1000

2020-01-16 * adjust cash
    assets:cash               $-2 = $105

Show account names, and their hierarchy:

$ hledger accounts --tree
  opening/closing balances

Show all account totals:

$ hledger balance
               $4105  assets
               $4000    bank
               $2000      checking
               $2000      savings
                $105    cash
              $-3050  equity:opening/closing balances
                 $15  expenses
                 $13    food
                  $2    misc
              $-1020  income
                $-20    gifts
              $-1000    salary
                $-50  liabilities:creditcard

Show only asset and liability balances, as a flat list, limited to depth 2:

$ hledger bal assets liabilities --flat -2
               $4000  assets:bank
                $105  assets:cash
                $-50  liabilities:creditcard

Show the same thing without negative numbers, formatted as a simple balance sheet:

$ hledger bs --flat -2
Balance Sheet 2020-01-16

                        || 2020-01-16 
 Assets                 ||            
 assets:bank            ||      $4000 
 assets:cash            ||       $105 
                        ||      $4105 
 Liabilities            ||            
 liabilities:creditcard ||        $50 
                        ||        $50 
 Net:                   ||      $4055 

The final total is your "net worth" on the end date. (Or use bse for a full balance sheet with equity.)

Show income and expense totals, formatted as an income statement:

hledger is 
Income Statement 2020-01-01-2020-01-16

               || 2020-01-01-2020-01-16 
 Revenues      ||                       
 income:gifts  ||                   $20 
 income:salary ||                 $1000 
               ||                 $1020 
 Expenses      ||                       
 expenses:food ||                   $13 
 expenses:misc ||                    $2 
               ||                   $15 
 Net:          ||                 $1005 

The final total is your net income during this period.

Show transactions affecting your wallet, with running total:

$ hledger register cash
2020-01-01 opening balances     assets:cash                   $100          $100
2020-01-10 gift received        assets:cash                    $20          $120
2020-01-12 farmers market       assets:cash                   $-13          $107
2020-01-16 adjust cash          assets:cash                    $-2          $105

Show weekly posting counts as a bar chart:

$ hledger activity -W
2019-12-30 *****
2020-01-06 ****
2020-01-13 ****

Migrating to a new file

At the end of the year, you may want to continue your journal in a new file, so that old transactions don't slow down or clutter your reports, and to help ensure the integrity of your accounting history. See the close command.

If using version control, don't forget to git add the new file.


The need to precede add-on command options with -- when invoked from hledger is awkward.

When input data contains non-ascii characters, a suitable system locale must be configured (or there will be an unhelpful error). Eg on POSIX, set LANG to something other than C.

In a Microsoft Windows CMD window, non-ascii characters and colours are not supported.

On Windows, non-ascii characters may not display correctly when running a hledger built in CMD in MSYS/CYGWIN, or vice-versa.

In a Cygwin/MSYS/Mintty window, the tab key is not supported in hledger add.

Not all of Ledger's journal file syntax is supported. See file format differences.

On large data files, hledger is slower and uses more memory than Ledger.


Here are some issues you might encounter when you run hledger (and remember you can also seek help from the IRC channel, mail list or bug tracker):

Successfully installed, but "No command 'hledger' found"
stack and cabal install binaries into a special directory, which should be added to your PATH environment variable. Eg on unix-like systems, that is ~/.local/bin and ~/.cabal/bin respectively.

I set a custom LEDGER_FILE, but hledger is still using the default file
LEDGER_FILE should be a real environment variable, not just a shell variable. The command env | grep LEDGER_FILE should show it. You may need to use export. Here's an explanation.

Getting errors like "Illegal byte sequence" or "Invalid or incomplete multibyte or wide character" or "commitAndReleaseBuffer: invalid argument (invalid character)"
Programs compiled with GHC (hledger, haskell build tools, etc.) need to have a UTF-8-aware locale configured in the environment, otherwise they will fail with these kinds of errors when they encounter non-ascii characters.

To fix it, set the LANG environment variable to some locale which supports UTF-8. The locale you choose must be installed on your system.

Here's an example of setting LANG temporarily, on Ubuntu GNU/Linux:

$ file my.journal
my.journal: UTF-8 Unicode text         # the file is UTF8-encoded
$ echo $LANG
C                                      # LANG is set to the default locale, which does not support UTF8
$ locale -a                            # which locales are installed ?
en_US.utf8                             # here's a UTF8-aware one we can use
$ LANG=en_US.utf8 hledger -f my.journal print   # ensure it is used for this command

If available, C.UTF-8 will also work. If your preferred locale isn't listed by locale -a, you might need to install it. Eg on Ubuntu/Debian:

$ apt-get install language-pack-fr
$ locale -a
$ LANG=fr_FR.utf8 hledger -f my.journal print

Here's how you could set it permanently, if you use a bash shell:

$ echo "export LANG=en_US.utf8" >>~/.bash_profile
$ bash --login

Exact spelling and capitalisation may be important. Note the difference on MacOS (UTF-8, not utf8). Some platforms (eg ubuntu) allow variant spellings, but others (eg macos) require it to be exact:

$ locale -a | grep -iE en_us.*utf
$ LANG=en_US.UTF-8 hledger -f my.journal print


hledger-ui is a terminal interface (TUI) for the hledger accounting tool. This manual is for hledger-ui 1.21.

hledger-ui [OPTIONS] [QUERYARGS]
hledger ui -- [OPTIONS] [QUERYARGS]

hledger is a reliable, cross-platform set of programs for tracking money, time, or any other commodity, using double-entry accounting and a simple, editable file format. hledger is inspired by and largely compatible with ledger(1).

hledger-ui is hledger's terminal interface, providing an efficient full-window text UI for viewing accounts and transactions, and some limited data entry capability. It is easier than hledger's command-line interface, and sometimes quicker and more convenient than the web interface.

Like hledger, it reads data from one or more files in hledger journal, timeclock, timedot, or CSV format specified with -f, or $LEDGER_FILE, or $HOME/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.journal). For more about this see hledger(1), hledger_journal(5) etc.

Unlike hledger, hledger-ui hides all future-dated transactions by default. They can be revealed, along with any rule-generated periodic transactions, by pressing the F key (or starting with --forecast) to enable "forecast mode".


Note: if invoking hledger-ui as a hledger subcommand, write -- before options as shown above.

Any QUERYARGS are interpreted as a hledger search query which filters the data.

--watch : watch for data and date changes and reload automatically

--theme=default|terminal|greenterm : use this custom display theme

--register=ACCTREGEX : start in the (first) matched account's register screen

--change : show period balances (changes) at startup instead of historical balances

-l --flat : show accounts as a flat list (default)

-t --tree : show accounts as a tree

hledger input options:

-f FILE --file=FILE : use a different input file. For stdin, use - (default: $LEDGER_FILE or $HOME/.hledger.journal)

--rules-file=RULESFILE : Conversion rules file to use when reading CSV (default: FILE.rules)

--separator=CHAR : Field separator to expect when reading CSV (default: ',')

--alias=OLD=NEW : rename accounts named OLD to NEW

--anon : anonymize accounts and payees

--pivot FIELDNAME : use some other field or tag for the account name

-I --ignore-assertions : disable balance assertion checks (note: does not disable balance assignments)

-s --strict : do extra error checking (check that all posted accounts are declared)

hledger reporting options:

-b --begin=DATE : include postings/txns on or after this date

-e --end=DATE : include postings/txns before this date

-D --daily : multiperiod/multicolumn report by day

-W --weekly : multiperiod/multicolumn report by week

-M --monthly : multiperiod/multicolumn report by month

-Q --quarterly : multiperiod/multicolumn report by quarter

-Y --yearly : multiperiod/multicolumn report by year

-p --period=PERIODEXP : set start date, end date, and/or reporting interval all at once using period expressions syntax

--date2 : match the secondary date instead (see command help for other effects)

-U --unmarked : include only unmarked postings/txns (can combine with -P or -C)

-P --pending : include only pending postings/txns

-C --cleared : include only cleared postings/txns

-R --real : include only non-virtual postings

-NUM --depth=NUM : hide/aggregate accounts or postings more than NUM levels deep

-E --empty : show items with zero amount, normally hidden (and vice-versa in hledger-ui/hledger-web)

-B --cost : convert amounts to their cost/selling amount at transaction time

-V --market : convert amounts to their market value in default valuation commodities

-X --exchange=COMM : convert amounts to their market value in commodity COMM

--value : convert amounts to cost or market value, more flexibly than -B/-V/-X

--infer-market-prices : use transaction prices (recorded with @ or @@) as additional market prices, as if they were P directives

--auto : apply automated posting rules to modify transactions.

--forecast : generate future transactions from periodic transaction rules, for the next 6 months or till report end date. In hledger-ui, also make ordinary future transactions visible.

--color=WHEN (or --colour=WHEN) : Should color-supporting commands use ANSI color codes in text output. : 'auto' (default): whenever stdout seems to be a color-supporting terminal. : 'always' or 'yes': always, useful eg when piping output into 'less -R'. : 'never' or 'no': never. : A NO_COLOR environment variable overrides this.

When a reporting option appears more than once in the command line, the last one takes precedence.

Some reporting options can also be written as query arguments.

hledger help options:

-h --help : show general or COMMAND help

--man : show general or COMMAND user manual with man

--info : show general or COMMAND user manual with info

--version : show general or ADDONCMD version

--debug[=N] : show debug output (levels 1-9, default: 1)

A @FILE argument will be expanded to the contents of FILE, which should contain one command line option/argument per line. (To prevent this, insert a -- argument before.)


? shows a help dialog listing all keys. (Some of these also appear in the quick help at the bottom of each screen.) Press ? again (or ESCAPE, or LEFT, or q) to close it. The following keys work on most screens:

The cursor keys navigate: right (or enter) goes deeper, left returns to the previous screen, up/down/page up/page down/home/end move up and down through lists. Emacs-style (ctrl-p/ctrl-n/ctrl-f/ctrl-b) movement keys are also supported (but not vi-style keys, since hledger-1.19, sorry!). A tip: movement speed is limited by your keyboard repeat rate, to move faster you may want to adjust it. (If you're on a mac, the karabiner app is one way to do that.)

With shift pressed, the cursor keys adjust the report period, limiting the transactions to be shown (by default, all are shown). shift-down/up steps downward and upward through these standard report period durations: year, quarter, month, week, day. Then, shift-left/right moves to the previous/next period. T sets the report period to today. With the --watch option, when viewing a "current" period (the current day, week, month, quarter, or year), the period will move automatically to track the current date. To set a non-standard period, you can use / and a date: query.

/ lets you set a general filter query limiting the data shown, using the same query terms as in hledger and hledger-web. While editing the query, you can use CTRL-a/e/d/k, BS, cursor keys; press ENTER to set it, or ESCAPEto cancel. There are also keys for quickly adjusting some common filters like account depth and transaction status (see below). BACKSPACE or DELETE removes all filters, showing all transactions.

As mentioned above, by default hledger-ui hides future transactions - both ordinary transactions recorded in the journal, and periodic transactions generated by rule. F toggles forecast mode, in which future/forecasted transactions are shown.

ESCAPE resets the UI state and jumps back to the top screen, restoring the app's initial state at startup. Or, it cancels minibuffer data entry or the help dialog.

CTRL-l redraws the screen and centers the selection if possible (selections near the top won't be centered, since we don't scroll above the top).

g reloads from the data file(s) and updates the current screen and any previous screens. (With large files, this could cause a noticeable pause.)

I toggles balance assertion checking. Disabling balance assertions temporarily can be useful for troubleshooting.

a runs command-line hledger's add command, and reloads the updated file. This allows some basic data entry.

A is like a, but runs the hledger-iadd tool, which provides a terminal interface. This key will be available if hledger-iadd is installed in $path.

E runs $HLEDGER_UI_EDITOR, or $EDITOR, or a default (emacsclient -a "" -nw) on the journal file. With some editors (emacs, vi), the cursor will be positioned at the current transaction when invoked from the register and transaction screens, and at the error location (if possible) when invoked from the error screen.

B toggles cost mode, showing amounts in their transaction price's commodity (like toggling the -B/--cost flag).

V toggles value mode, showing amounts' current market value in their default valuation commodity (like toggling the -V/--market flag). Note, "current market value" means the value on the report end date if specified, otherwise today. To see the value on another date, you can temporarily set that as the report end date. Eg: to see a transaction as it was valued on july 30, go to the accounts or register screen, press /, and add date:-7/30 to the query.

At most one of cost or value mode can be active at once.

There's not yet any visual reminder when cost or value mode is active; for now pressing b b v should reliably reset to normal mode.

With --watch active, if you save an edit to the journal file while viewing the transaction screen in cost or value mode, the B/V keys will stop working. To work around, press g to force a manual reload, or exit the transaction screen.

q quits the application.

Additional screen-specific keys are described below.


Accounts screen

This is normally the first screen displayed. It lists accounts and their balances, like hledger's balance command. By default, it shows all accounts and their latest ending balances (including the balances of subaccounts). If you specify a query on the command line, it shows just the matched accounts and the balances from matched transactions.

Account names are shown as a flat list by default; press t to toggle tree mode. In list mode, account balances are exclusive of subaccounts, except where subaccounts are hidden by a depth limit (see below). In tree mode, all account balances are inclusive of subaccounts.

To see less detail, press a number key, 1 to 9, to set a depth limit. Or use - to decrease and +/= to increase the depth limit. 0 shows even less detail, collapsing all accounts to a single total. To remove the depth limit, set it higher than the maximum account depth, or press ESCAPE.

H toggles between showing historical balances or period balances. Historical balances (the default) are ending balances at the end of the report period, taking into account all transactions before that date (filtered by the filter query if any), including transactions before the start of the report period. In other words, historical balances are what you would see on a bank statement for that account (unless disturbed by a filter query). Period balances ignore transactions before the report start date, so they show the change in balance during the report period. They are more useful eg when viewing a time log.

U toggles filtering by unmarked status, including or excluding unmarked postings in the balances. Similarly, P toggles pending postings, and C toggles cleared postings. (By default, balances include all postings; if you activate one or two status filters, only those postings are included; and if you activate all three, the filter is removed.)

R toggles real mode, in which virtual postings are ignored.

Z toggles nonzero mode, in which only accounts with nonzero balances are shown (hledger-ui shows zero items by default, unlike command-line hledger).

Press right or enter to view an account's transactions register.

Register screen

This screen shows the transactions affecting a particular account, like a check register. Each line represents one transaction and shows:

  • the other account(s) involved, in abbreviated form. (If there are both real and virtual postings, it shows only the accounts affected by real postings.)

  • the overall change to the current account's balance; positive for an inflow to this account, negative for an outflow.

  • the running historical total or period total for the current account, after the transaction. This can be toggled with H. Similar to the accounts screen, the historical total is affected by transactions (filtered by the filter query) before the report start date, while the period total is not. If the historical total is not disturbed by a filter query, it will be the running historical balance you would see on a bank register for the current account.

Transactions affecting this account's subaccounts will be included in the register if the accounts screen is in tree mode, or if it's in list mode but this account has subaccounts which are not shown due to a depth limit. In other words, the register always shows the transactions contributing to the balance shown on the accounts screen. Tree mode/list mode can be toggled with t here also.

U toggles filtering by unmarked status, showing or hiding unmarked transactions. Similarly, P toggles pending transactions, and C toggles cleared transactions. (By default, transactions with all statuses are shown; if you activate one or two status filters, only those transactions are shown; and if you activate all three, the filter is removed.)

R toggles real mode, in which virtual postings are ignored.

Z toggles nonzero mode, in which only transactions posting a nonzero change are shown (hledger-ui shows zero items by default, unlike command-line hledger).

Press right (or enter) to view the selected transaction in detail.

Transaction screen

This screen shows a single transaction, as a general journal entry, similar to hledger's print command and journal format (hledger_journal(5)).

The transaction's date(s) and any cleared flag, transaction code, description, comments, along with all of its account postings are shown. Simple transactions have two postings, but there can be more (or in certain cases, fewer).

up and down will step through all transactions listed in the previous account register screen. In the title bar, the numbers in parentheses show your position within that account register. They will vary depending on which account register you came from (remember most transactions appear in multiple account registers). The #N number preceding them is the transaction's position within the complete unfiltered journal, which is a more stable id (at least until the next reload).

Error screen

This screen will appear if there is a problem, such as a parse error, when you press g to reload. Once you have fixed the problem, press g again to reload and resume normal operation. (Or, you can press escape to cancel the reload attempt.)


COLUMNS The screen width to use. Default: the full terminal width.

LEDGER_FILE The journal file path when not specified with -f. Default: ~/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.journal).

A typical value is ~/DIR/YYYY.journal, where DIR is a version-controlled finance directory and YYYY is the current year. Or ~/DIR/current.journal, where current.journal is a symbolic link to YYYY.journal.

On Mac computers, you can set this and other environment variables in a more thorough way that also affects applications started from the GUI (say, an Emacs dock icon). Eg on MacOS Catalina I have a ~/.MacOSX/environment.plist file containing

  "LEDGER_FILE" : "~/finance/current.journal"

To see the effect you may need to killall Dock, or reboot.


Reads data from one or more files in hledger journal, timeclock, timedot, or CSV format specified with -f, or $LEDGER_FILE, or $HOME/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.journal).


The need to precede options with -- when invoked from hledger is awkward.

-f- doesn't work (hledger-ui can't read from stdin).

-V affects only the accounts screen.

When you press g, the current and all previous screens are regenerated, which may cause a noticeable pause with large files. Also there is no visual indication that this is in progress.

--watch is not yet fully robust. It works well for normal usage, but many file changes in a short time (eg saving the file thousands of times with an editor macro) can cause problems at least on OSX. Symptoms include: unresponsive UI, periodic resetting of the cursor position, momentary display of parse errors, high CPU usage eventually subsiding, and possibly a small but persistent build-up of CPU usage until the program is restarted.

Also, if you are viewing files mounted from another machine, --watch requires that both machine clocks are roughly in step.


hledger-web is a web interface (WUI) for the hledger accounting tool. This manual is for hledger-web 1.21.

hledger-web [OPTIONS]
hledger web -- [OPTIONS]

hledger is a reliable, cross-platform set of programs for tracking money, time, or any other commodity, using double-entry accounting and a simple, editable file format. hledger is inspired by and largely compatible with ledger(1).

hledger-web is hledger's web interface. It starts a simple web application for browsing and adding transactions, and optionally opens it in a web browser window if possible. It provides a more user-friendly UI than the hledger CLI or hledger-ui interface, showing more at once (accounts, the current account register, balance charts) and allowing history-aware data entry, interactive searching, and bookmarking.

hledger-web also lets you share a ledger with multiple users, or even the public web. There is no access control, so if you need that you should put it behind a suitable web proxy. As a small protection against data loss when running an unprotected instance, it writes a numbered backup of the main journal file (only ?) on every edit.

Like hledger, it reads data from one or more files in hledger journal, timeclock, timedot, or CSV format specified with -f, or $LEDGER_FILE, or $HOME/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.journal). For more about this see hledger(1), hledger_journal(5) etc.


Command-line options and arguments may be used to set an initial filter on the data. These filter options are not shown in the web UI, but it will be applied in addition to any search query entered there.

Note: if invoking hledger-web as a hledger subcommand, write -- before options, as shown in the synopsis above.

--serve : serve and log requests, don't browse or auto-exit

--serve-api : like --serve, but serve only the JSON web API, without the server-side web UI

--host=IPADDR : listen on this IP address (default:

--port=PORT : listen on this TCP port (default: 5000)

--socket=SOCKETFILE : use a unix domain socket file to listen for requests instead of a TCP socket. Implies --serve. It can only be used if the operating system can provide this type of socket.

--base-url=URL : set the base url (default: http://IPADDR:PORT). You would change this when sharing over the network, or integrating within a larger website.

--file-url=URL : set the static files url (default: BASEURL/static). hledger-web normally serves static files itself, but if you wanted to serve them from another server for efficiency, you would set the url with this.

--capabilities=CAP[,CAP..] : enable the view, add, and/or manage capabilities (default: view,add)

--capabilities-header=HTTPHEADER : read capabilities to enable from a HTTP header, like X-Sandstorm-Permissions (default: disabled)

--test : run hledger-web's tests and exit. hspec test runner args may follow a --, eg: hledger-web --test -- --help

hledger input options:

-f FILE --file=FILE : use a different input file. For stdin, use - (default: $LEDGER_FILE or $HOME/.hledger.journal)

--rules-file=RULESFILE : Conversion rules file to use when reading CSV (default: FILE.rules)

--separator=CHAR : Field separator to expect when reading CSV (default: ',')

--alias=OLD=NEW : rename accounts named OLD to NEW

--anon : anonymize accounts and payees

--pivot FIELDNAME : use some other field or tag for the account name

-I --ignore-assertions : disable balance assertion checks (note: does not disable balance assignments)

-s --strict : do extra error checking (check that all posted accounts are declared)

hledger reporting options:

-b --begin=DATE : include postings/txns on or after this date

-e --end=DATE : include postings/txns before this date

-D --daily : multiperiod/multicolumn report by day

-W --weekly : multiperiod/multicolumn report by week

-M --monthly : multiperiod/multicolumn report by month

-Q --quarterly : multiperiod/multicolumn report by quarter

-Y --yearly : multiperiod/multicolumn report by year

-p --period=PERIODEXP : set start date, end date, and/or reporting interval all at once using period expressions syntax

--date2 : match the secondary date instead (see command help for other effects)

-U --unmarked : include only unmarked postings/txns (can combine with -P or -C)

-P --pending : include only pending postings/txns

-C --cleared : include only cleared postings/txns

-R --real : include only non-virtual postings

-NUM --depth=NUM : hide/aggregate accounts or postings more than NUM levels deep

-E --empty : show items with zero amount, normally hidden (and vice-versa in hledger-ui/hledger-web)

-B --cost : convert amounts to their cost/selling amount at transaction time

-V --market : convert amounts to their market value in default valuation commodities

-X --exchange=COMM : convert amounts to their market value in commodity COMM

--value : convert amounts to cost or market value, more flexibly than -B/-V/-X

--infer-market-prices : use transaction prices (recorded with @ or @@) as additional market prices, as if they were P directives

--auto : apply automated posting rules to modify transactions.

--forecast : generate future transactions from periodic transaction rules, for the next 6 months or till report end date. In hledger-ui, also make ordinary future transactions visible.

--color=WHEN (or --colour=WHEN) : Should color-supporting commands use ANSI color codes in text output. : 'auto' (default): whenever stdout seems to be a color-supporting terminal. : 'always' or 'yes': always, useful eg when piping output into 'less -R'. : 'never' or 'no': never. : A NO_COLOR environment variable overrides this.

When a reporting option appears more than once in the command line, the last one takes precedence.

Some reporting options can also be written as query arguments.

hledger help options:

-h --help : show general or COMMAND help

--man : show general or COMMAND user manual with man

--info : show general or COMMAND user manual with info

--version : show general or ADDONCMD version

--debug[=N] : show debug output (levels 1-9, default: 1)

A @FILE argument will be expanded to the contents of FILE, which should contain one command line option/argument per line. (To prevent this, insert a -- argument before.)

By default, hledger-web starts the web app in "transient mode" and also opens it in your default web browser if possible. In this mode the web app will keep running for as long as you have it open in a browser window, and will exit after two minutes of inactivity (no requests and no browser windows viewing it). With --serve, it just runs the web app without exiting, and logs requests to the console. With --serve-api, only the JSON web api (see below) is served, with the usual HTML server-side web UI disabled.

By default the server listens on IP address, accessible only to local requests. You can use --host to change this, eg --host to listen on all configured addresses.

Similarly, use --port to set a TCP port other than 5000, eg if you are running multiple hledger-web instances.

Both of these options are ignored when --socket is used. In this case, it creates an AF_UNIX socket file at the supplied path and uses that for communication. This is an alternative way of running multiple hledger-web instances behind a reverse proxy that handles authentication for different users. The path can be derived in a predictable way, eg by using the username within the path. As an example, nginx as reverse proxy can use the variable $remote_user to derive a path from the username used in a HTTP basic authentication. The following proxy_pass directive allows access to all hledger-web instances that created a socket in /tmp/hledger/:

  proxy_pass http://unix:/tmp/hledger/${remote_user}.socket;

You can use --base-url to change the protocol, hostname, port and path that appear in hyperlinks, useful eg for integrating hledger-web within a larger website. The default is http://HOST:PORT/ using the server's configured host address and TCP port (or http://HOST if PORT is 80).

With --file-url you can set a different base url for static files, eg for better caching or cookie-less serving on high performance websites.


By default, hledger-web allows anyone who can reach it to view the journal and to add new transactions, but not to change existing data.

You can restrict who can reach it by

  • setting the IP address it listens on (see --host above). By default it listens on, accessible to all users on the local machine.
  • putting it behind an authenticating proxy, using eg apache or nginx
  • custom firewall rules

You can restrict what the users who reach it can do, by

  • using the --capabilities=CAP[,CAP..] flag when you start it, enabling one or more of the following capabilities. The default value is view,add:
    • view - allows viewing the journal file and all included files
    • add - allows adding new transactions to the main journal file
    • manage - allows editing, uploading or downloading the main or included files
  • using the --capabilities-header=HTTPHEADER flag to specify a HTTP header from which it will read capabilities to enable. hledger-web on Sandstorm uses the X-Sandstorm-Permissions header to integrate with Sandstorm's permissions. This is disabled by default.


If you enable the manage capability mentioned above, you'll see a new "spanner" button to the right of the search form. Clicking this will let you edit, upload, or download the journal file or any files it includes.

Note, unlike any other hledger command, in this mode you (or any visitor) can alter or wipe the data files.

Normally whenever a file is changed in this way, hledger-web saves a numbered backup (assuming file permissions allow it, the disk is not full, etc.) hledger-web is not aware of version control systems, currently; if you use one, you'll have to arrange to commit the changes yourself (eg with a cron job or a file watcher like entr).

Changes which would leave the journal file(s) unparseable or non-valid (eg with failing balance assertions) are prevented. (Probably. This needs re-testing.)


hledger-web detects changes made to the files by other means (eg if you edit it directly, outside of hledger-web), and it will show the new data when you reload the page or navigate to a new page. If a change makes a file unparseable, hledger-web will display an error message until the file has been fixed.

(Note: if you are viewing files mounted from another machine, make sure that both machine clocks are roughly in step.)


In addition to the web UI, hledger-web also serves a JSON API that can be used to get data or add new transactions. If you want the JSON API only, you can use the --serve-api flag. Eg:

$ hledger-web -f examples/sample.journal --serve-api

You can get JSON data from these routes:


Eg, all account names in the journal (similar to the accounts command). (hledger-web's JSON does not include newlines, here we use python to prettify it):

$ curl -s | python -m json.tool

Or all transactions:

$ curl -s | python -m json.tool
        "tcode": "",
        "tcomment": "",
        "tdate": "2008-01-01",
        "tdate2": null,
        "tdescription": "income",
        "tindex": 1,
        "tpostings": [
                "paccount": "assets:bank:checking",
                "pamount": [
                        "acommodity": "$",
                        "aismultiplier": false,
                        "aprice": null,

Most of the JSON corresponds to hledger's data types; for details of what the fields mean, see the Hledger.Data.Json haddock docs and click on the various data types, eg Transaction. And for a higher level understanding, see the journal manual.

In some cases there is outer JSON corresponding to a "Report" type. To understand that, go to the Hledger.Web.Handler.MiscR haddock and look at the source for the appropriate handler to see what it returns. Eg for /accounttransactions it's getAccounttransactionsR, returning a "accountTransactionsReport ...". Looking up the haddock for that we can see that /accounttransactions returns an AccountTransactionsReport, which consists of a report title and a list of AccountTransactionsReportItem (etc).

You can add a new transaction to the journal with a PUT request to /add, if hledger-web was started with the add capability (enabled by default). The payload must be the full, exact JSON representation of a hledger transaction (partial data won't do). You can get sample JSON from hledger-web's /transactions or /accounttransactions, or you can export it with hledger-lib, eg like so:

.../hledger$ stack ghci hledger-lib
>>> writeJsonFile "txn.json" (head $ jtxns samplejournal)
>>> :q

Here's how it looks as of hledger-1.17 (remember, this JSON corresponds to hledger's Transaction and related data types):

    "tcomment": "",
    "tpostings": [
            "pbalanceassertion": null,
            "pstatus": "Unmarked",
            "pamount": [
                    "aprice": null,
                    "acommodity": "$",
                    "aquantity": {
                        "floatingPoint": 1,
                        "decimalPlaces": 10,
                        "decimalMantissa": 10000000000
                    "aismultiplier": false,
                    "astyle": {
                        "ascommodityside": "L",
                        "asdigitgroups": null,
                        "ascommodityspaced": false,
                        "asprecision": 2,
                        "asdecimalpoint": "."
            "ptransaction_": "1",
            "paccount": "assets:bank:checking",
            "pdate": null,
            "ptype": "RegularPosting",
            "pcomment": "",
            "pdate2": null,
            "ptags": [],
            "poriginal": null
            "pbalanceassertion": null,
            "pstatus": "Unmarked",
            "pamount": [
                    "aprice": null,
                    "acommodity": "$",
                    "aquantity": {
                        "floatingPoint": -1,
                        "decimalPlaces": 10,
                        "decimalMantissa": -10000000000
                    "aismultiplier": false,
                    "astyle": {
                        "ascommodityside": "L",
                        "asdigitgroups": null,
                        "ascommodityspaced": false,
                        "asprecision": 2,
                        "asdecimalpoint": "."
            "ptransaction_": "1",
            "paccount": "income:salary",
            "pdate": null,
            "ptype": "RegularPosting",
            "pcomment": "",
            "pdate2": null,
            "ptags": [],
            "poriginal": null
    "ttags": [],
    "tsourcepos": {
        "tag": "JournalSourcePos",
        "contents": [
    "tdate": "2008-01-01",
    "tcode": "",
    "tindex": 1,
    "tprecedingcomment": "",
    "tdate2": null,
    "tdescription": "income",
    "tstatus": "Unmarked"

And here's how to test adding it with curl. This should add a new entry to your journal:

$ curl -X PUT -H 'Content-Type: application/json' --data-binary @txn.json


LEDGER_FILE The journal file path when not specified with -f. Default: ~/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.journal).

A typical value is ~/DIR/YYYY.journal, where DIR is a version-controlled finance directory and YYYY is the current year. Or ~/DIR/current.journal, where current.journal is a symbolic link to YYYY.journal.

On Mac computers, you can set this and other environment variables in a more thorough way that also affects applications started from the GUI (say, an Emacs dock icon). Eg on MacOS Catalina I have a ~/.MacOSX/environment.plist file containing

  "LEDGER_FILE" : "~/finance/current.journal"

To see the effect you may need to killall Dock, or reboot.


Reads data from one or more files in hledger journal, timeclock, timedot, or CSV format specified with -f, or $LEDGER_FILE, or $HOME/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.journal).


The need to precede options with -- when invoked from hledger is awkward.

-f- doesn't work (hledger-web can't read from stdin).

Query arguments and some hledger options are ignored.

Does not work in text-mode browsers.

Does not work well on small screens.

Create a journal

There are lots of ways to start and update a journal file:

with touch

The simplest possible journal is just an empty file:

touch 2018.journal

The name doesn't matter much and can be changed later. One file per year is common, and so is a .journal or .hledger extension.

with cat

$ cat >>2018.journal
  expenses:food     $10

Account names can be anything and you can change them later by search and replace. If you don't know what to choose, start with these five:
expenses, income, assets, liabilities, and equity,
perhaps with one extra subcategory as above.

with a text editor

Write transactions in a text editor, optionally using an editor mode, and save the file.

with hledger add

Use the interactive add command to enter one or more transactions:

hledger add -f 2018.journal

To avoid typing -f FILE every time, set the LEDGER_FILE environment variable. Eg:

echo "export LEDGER_FILE=~/finance/2018.journal" >> ~/.bash_profile && source ~/.bash_profile

Then it's just

hledger add

with hledger-iadd

  • ensure $LEDGER_FILE exists
  • hledger iadd
  • enter one or more transactions

with hledger-web

  • ensure $LEDGER_FILE exists
  • hledger web
  • wait for web browser to open
  • click "add transaction" or press "a"
  • enter a transaction, click ok or press enter

Change account name separator

Timedot format makes me want to use dots (.) for separating account components, instead of colon (:). For example, instead of fos:hledger:timedot I'd like to write fos.hledger.timedot. We can use the powerful account aliases feature to rewrite account names before hledger's account name parser sees them.

In journal files, we can use an alias directive. Note the backslash which tells the regular expression engine it's a literal . not a wildcard:

alias /\./=:

2008/01/01 income
    assets.bank.checking  $1

Check that subaccounts are recognised:

$ hledger -f t.journal bal --no-elide
                  $1  assets
                  $1    bank
                  $1      checking
                 $-1  income
                 $-1    salary

Alias directives aren't supported in the timedot format,

fos.hledger.timedot  2
fos.ledger           1

so we would use the --alias command line option instead. The second backslash tells the shell that's a literal backslash, not a shell escape sequence:

$ hledger --alias /\\./=: -f t.timedot bal --no-elide
                3.00  fos
                2.00    hledger
                2.00      timedot
                1.00    ledger

Track changes with version control

You don't need to do this, but it's a nice way to keep track of changes to your data.


Start tracking changes:
git init && git add 2017.journal && git commit 2017.journal -m "first commit"

View uncommitted changes: git status, git diff

Commit changes: git commit 2017.journal -m "updates"

View past commits: git log


darcs init && darcs add 2017.journal && darcs record 2017.journal -m "first commit"

darcs whatsnew, darcs diff

darcs record 2017.journal -m "updates"

darcs log


Importing CSV data

hledger has a powerful CSV converter built in. After saving a few declarations in a "CSV rules file", it can read transactions from almost any CSV file. This is described in detail in the csv format manual, but here are some quick examples.

Say you have downloaded this checking.csv file from a bank for the first time:

"2012/3/23","TRANSFER TO SAVINGS","-10.00"

Create a rules file named checking.csv.rules in the same directory. This tells hledger how to read this CSV file. Eg:

# skip the headings line:
skip 1

# use the first three CSV fields for hledger's transaction date, description and amount:
fields date, description, amount

# specify the date field's format - not needed here since date is Y/M/D
# date-format %-d/%-m/%Y
# date-format %-m/%-d/%Y
# date-format %Y-%h-%d

# since the CSV amounts have no currency symbol, add one:
currency $

# set the base account that this CSV file corresponds to
account1 assets:bank:checking

# the other account will default to expenses:unknown or income:unknown;
# we can optionally refine it by matching patterns in the CSV record:
  account2 assets:bank:savings

  account2 expenses:food

You can print the resulting transactions in any of hledger's output formats:

$ hledger -f checking.csv print
2012-03-22 DEPOSIT
    assets:bank:checking          $50.00
    income:unknown               $-50.00

    assets:bank:checking         $-10.00
    assets:bank:savings           $10.00

Or run reports directly from the CSV:

$ hledger -f checking.csv bal
              $40.00  assets:bank:checking
              $10.00  assets:bank:savings
             $-50.00  income:unknown

Or import any new transactions, saving them into your main journal:

$ hledger import checking.csv --dry-run 
; would import 2 new transactions from checking.csv:

2012-03-22 DEPOSIT
    assets:bank:checking          $50.00
    income:unknown               $-50.00

    assets:bank:checking         $-10.00
    assets:bank:savings           $10.00

$ hledger import checking.csv
imported 2 new transactions from checking.csv

hledger import ignores transactions it has seen before, so it's safe to run it repeatedly. (It creates a hidden .latest.checking.csv file in the same directory. If you need to forget the state and start over, delete this.)

Customize the default "unknown" accounts

When converting CSV, hledger uses the account names income:unknown and expenses:unknown as defaults. Normally when you see these, you will want to add CSV rules to set a more specific account name. But you may want to change these defaults, eg into your language.

Method 1: You can add rules something like these, as the first account2 rules:

# set account2 to this:
account2 Revenues:Misc

# change it to Expenses:Misc if the csv "amount" field contains a minus sign:
if %amount -
 account2 Expenses:Misc

# override it with more specific rules below...

Method 2: You can use --alias options to rewrite those account names. With hledger 1.20+:

$ hledger -f checking.csv --alias income:unknown=Income:Misc --alias expenses:unknown=Expenses:Misc print
2012-03-22 DEPOSIT
    assets:bank:checking          $50.00
    Income:Misc                  $-50.00

    assets:bank:checking         $-10.00
    assets:bank:savings           $10.00

(Before hledger 1.20, --alias only worked with journal format so you had to pipe it like this:)

$ hledger -f checking.csv print | hledger -f- --alias income:unknown=Income:Misc --alias expenses:unknown=Expenses:Misc print

See also

Full documentation of CSV conversion, and more rules examples, can be found in the csv format manual and in examples/csv/ in the hledger repo.

There are many other CSV conversion tools (nine CSV->*ledger tools at last count), linked at plaintextaccounting.org -> data import/conversion.

Exporting from hledger

Many finance apps have a way to import CSV files from financial institutions. You can produce similar CSV with hledger's register command. Export one account and one currency at a time. This helps keep the CSV simple and importable. Eg:

$ hledger -f examples/sample.journal reg -O csv checking cur:'\$'
"5","2008-12-31","","pay off","assets:bank:checking","$-1","0"

The new aregister command (currently in master) is best for this, since it guarantees one record per transaction even with complex multi-posting transactions, and provides the (abbreviated) other account names, making categorisation easier when importing:

$ hledger -f examples/sample.journal areg checking -O csv cur:'\$'
"5","2008-12-31","","pay off","li:debts","$-1","0"

hledger supports other output formats, including HTML, JSON and SQL. Not all formats are supported by all commands/reports though. For a given report, you can check the --help or just try an output format to see if it has been added.

$ hledger -f examples/sample.journal reg checking -O sql
hledger: Sorry, output format "sql" is unrecognised or not yet implemented for this report or report mode.
$ hledger -f examples/sample.journal print checking -O sql
create table if not exists postings(id serial,txnidx int,date1 date,date2 date,status text,code text,description text,comment text,account text,amount numeric,commodity text,credit numeric,debit numeric,posting_status text,posting_comment text);
insert into postings(txnidx,date1,date2,status,code,description,comment,account,amount,commodity,credit,debit,posting_status,posting_comment) values
,('5','2008-12-31',NULL,'*',NULL,'pay off',NULL,'liabilities:debts','1','$',NULL,'1',NULL,NULL)
,('5','2008-12-31',NULL,'*',NULL,'pay off',NULL,'assets:bank:checking','-1','$','1',NULL,NULL,NULL)

Checking for errors

hledger can check your data in various ways. Here are some checks/desirable properties, with short names for convenience:

These checks are run always (with all hledger commands, eg hledger stats >/dev/null):

  • parseable - data files are well-formed and can be successfully parsed
  • autobalanced - all transactions are balanced, inferring missing amounts where necessary, and possibly converting commodities using transaction prices or automatically-inferred transaction prices
  • assertions - all balance assertions are passing (except with -I/--ignore-assertions)

These checks are run only in hledger-1.19.99's strict mode (eg hledger stats --strict):

These checks are run by special commands (for now):

  • dates - transactions are ordered by date (command: hledger check-dates)
  • leafnames - all account leaf names are unique (command: hledger check-leafnames)
  • tagfiles - all tag values containing / (a forward slash) exist as file paths (addon command: hledger-check-tagfiles.hs)
  • fancyassertions - more complex balance assertions are passing (addon command: hledger-check-fancyassertions.hs)

The addon commands are available in https://github.com/simonmichael/hledger/tree/master/bin (cf Scripting).

Todo / maybe

These are some checks we might add in future:

  • accountsactive - for each account used, if there is posting with an open: tag, it must have a corresponding posting with a close: tag, and all other postings must be chronologically between (and if on the same date, textually between) open and close postings. ("Accounts are posted to only within their declared active period.")
  • payees - all payee names used have been declared
  • pricebalanced - transactions are balanced, possibly using explicit transaction prices but not auto-inferred ones
  • fullybalanced - transactions are balanced in each commodity, without needing any conversions
  • explicitamounts - all transaction amounts have been recorded explicitly

Comparing report output

Commit some hledger report output into your version control system. Then you can detect any changes, eg:

$ hledger COMMAND > report.txt; git diff -- report.txt

Pre-commit hook

Version control systems often support a "pre-commit hook", a script which is run and required to succeed before each commit. Eg:

set -e
hledger stats -s


Here's another way to check for undeclared accounts, that works with older hledger versions, showing some diff tricks:

$ diff -U0 --label "Unused Accounts" --label "Undeclared Accounts" <(hledger accounts --declared) <(hledger accounts --used)

Report examples

Some simple reports on the essentials page.

Some example reports with an adapted copy of this example journal from beancount:

A yearly income statement, summarised to depth 3, sorted by amount:

$ hledger -f examples/bcexample.hledger is -Y -3 -S
Income Statement 2012-01-01..2014-10-11

                                ||                                     2012                                      2013                                      2014 
 Revenues                       ||                                                                                                                              
 Income:US:Federal              ||                          17000.00 IRAUSD                           17500.00 IRAUSD                           17500.00 IRAUSD 
 Income:US:Hoogle               ||              129132.20 USD, 120.12 VACHR               129382.20 USD, 120.12 VACHR                106183.70 USD, 97.02 VACHR 
 Income:US:ETrade               ||                                        0                                114.42 USD                                258.92 USD 
                                || 17000.00 IRAUSD, 129132.20 USD, 1 more..  17500.00 IRAUSD, 129496.62 USD, 1 more..  17500.00 IRAUSD, 106442.62 USD, 1 more.. 
 Expenses                       ||                                                                                                                              
 Expenses:Home:Rent             ||                             28800.00 USD                              28800.00 USD                              21600.00 USD 
 Expenses:Food:Restaurant       ||                              4517.54 USD                               4286.23 USD                               4164.76 USD 
 Expenses:Food:Groceries        ||                              2188.90 USD                               2222.97 USD                               1602.51 USD 
 Expenses:Transport:Tram        ||                              1320.00 USD                               1320.00 USD                               1080.00 USD 
 Expenses:Health:Vision         ||                              1099.80 USD                               1099.80 USD                                888.30 USD 
 Expenses:Home:Internet         ||                               960.48 USD                                959.82 USD                                720.50 USD 
 Expenses:Home:Electricity      ||                               780.00 USD                                780.00 USD                                585.00 USD 
 Expenses:Health:Medical        ||                               711.88 USD                                711.88 USD                                574.98 USD 
 Expenses:Health:Life           ||                               632.32 USD                                632.32 USD                                510.72 USD 
 Expenses:Financial:Commissions ||                                35.80 USD                                214.80 USD                                 89.50 USD 
 Expenses:Health:Dental         ||                                75.40 USD                                 75.40 USD                                 60.90 USD 
 Expenses:Financial:Fees        ||                                48.00 USD                                 48.00 USD                                 40.00 USD 
 Expenses:Food:Coffee           ||                                16.28 USD                                 19.79 USD                                 47.65 USD 
 Expenses:Food:Alcohol          ||                                        0                                 22.35 USD                                         0 
 Expenses:Taxes:Y2013           ||                                        0             17500.00 IRAUSD, 51477.20 USD                                859.09 USD 
 Expenses:Taxes:Y2014           ||                                        0                                         0             17500.00 IRAUSD, 41836.20 USD 
 Expenses:Taxes:Y2012           ||            17000.00 IRAUSD, 51477.20 USD                                917.43 USD                                         0 
                                ||            17000.00 IRAUSD, 92663.60 USD             17500.00 IRAUSD, 93587.99 USD             17500.00 IRAUSD, 74660.11 USD 
 Net:                           ||               36468.60 USD, 120.12 VACHR                35908.63 USD, 120.12 VACHR                 31782.51 USD, 97.02 VACHR 

Yearly balance sheets, with commodities converted to their year-end value in USD where possible:

$ hledger -f examples/bcexample.hledger bs -Y -V --infer-value
Balance Sheet 2012-12-31..2014-12-31, valued at period ends

                            ||                 2012-12-31                  2013-12-31                   2014-12-31 
 Assets                     ||                                                                                     
 Assets:US:BofA:Checking    ||                7448.62 USD                 7247.12 USD                   596.05 USD 
 Assets:US:ETrade:Cash      ||                 337.18 USD                  239.06 USD                  5120.50 USD 
 Assets:US:ETrade:GLD       ||                          0                 6213.90 USD                  6592.60 USD 
 Assets:US:ETrade:ITOT      ||                1364.90 USD                 4021.92 USD                  2648.26 USD 
 Assets:US:ETrade:VEA       ||                1411.56 USD                 2414.28 USD                  4290.84 USD 
 Assets:US:ETrade:VHT       ||                4695.80 USD                 5456.00 USD                 14782.32 USD 
 Assets:US:Hoogle:Vacation  ||               120.12 VACHR                240.24 VACHR                 337.26 VACHR 
 Assets:US:Vanguard:Cash    ||                  -0.04 USD                           0                    -0.02 USD 
 Assets:US:Vanguard:RGAGX   ||               15595.79 USD                28532.66 USD                 41391.57 USD 
 Assets:US:Vanguard:VBMPX   ||               10037.03 USD                22449.53 USD                 33769.05 USD 
                            || 40890.84 USD, 120.12 VACHR  76574.46 USD, 240.24 VACHR  109191.17 USD, 337.26 VACHR 
 Liabilities                ||                                                                                     
 Liabilities:US:Chase:Slate ||                1366.52 USD                 1906.01 USD                  2891.85 USD 
                            ||                1366.52 USD                 1906.01 USD                  2891.85 USD 
 Net:                       || 39524.32 USD, 120.12 VACHR  74668.45 USD, 240.24 VACHR  106299.32 USD, 337.26 VACHR 

The same reports as HTML:

$ hledger -f examples/bcexample.hledger is -Y -3 -S -o is.html
$ hledger -f examples/bcexample.hledger bs -Y -V --infer-value -o bs.html
$ open is.html bs.html

Income Statement 2012-01-01..2014-10-11

17000.00 IRAUSD17500.00 IRAUSD17500.00 IRAUSD
129132.20 USD, 120.12 VACHR129382.20 USD, 120.12 VACHR106183.70 USD, 97.02 VACHR
0114.42 USD258.92 USD
Total:17000.00 IRAUSD, 129132.20 USD, 120.12 VACHR17500.00 IRAUSD, 129496.62 USD, 120.12 VACHR17500.00 IRAUSD, 106442.62 USD, 97.02 VACHR
28800.00 USD28800.00 USD21600.00 USD
4517.54 USD4286.23 USD4164.76 USD
2188.90 USD2222.97 USD1602.51 USD
1320.00 USD1320.00 USD1080.00 USD
1099.80 USD1099.80 USD888.30 USD
960.48 USD959.82 USD720.50 USD
780.00 USD780.00 USD585.00 USD
711.88 USD711.88 USD574.98 USD
632.32 USD632.32 USD510.72 USD
35.80 USD214.80 USD89.50 USD
75.40 USD75.40 USD60.90 USD
48.00 USD48.00 USD40.00 USD
16.28 USD19.79 USD47.65 USD
022.35 USD0
017500.00 IRAUSD, 51477.20 USD859.09 USD
0017500.00 IRAUSD, 41836.20 USD
17000.00 IRAUSD, 51477.20 USD917.43 USD0
Total:17000.00 IRAUSD, 92663.60 USD17500.00 IRAUSD, 93587.99 USD17500.00 IRAUSD, 74660.11 USD
Net:36468.60 USD, 120.12 VACHR35908.63 USD, 120.12 VACHR31782.51 USD, 97.02 VACHR

Balance Sheet 2012-12-31..2014-12-31, valued at period ends

7448.62 USD7247.12 USD596.05 USD
337.18 USD239.06 USD5120.50 USD
06213.90 USD6592.60 USD
1364.90 USD4021.92 USD2648.26 USD
1411.56 USD2414.28 USD4290.84 USD
4695.80 USD5456.00 USD14782.32 USD
120.12 VACHR240.24 VACHR337.26 VACHR
-0.04 USD0-0.02 USD
15595.79 USD28532.66 USD41391.57 USD
10037.03 USD22449.53 USD33769.05 USD
Total:40890.84 USD, 120.12 VACHR76574.46 USD, 240.24 VACHR109191.17 USD, 337.26 VACHR
1366.52 USD1906.01 USD2891.85 USD
Total:1366.52 USD1906.01 USD2891.85 USD
Net:39524.32 USD, 120.12 VACHR74668.45 USD, 240.24 VACHR106299.32 USD, 337.26 VACHR


From the hledger manual's QUERIES section:

One of hledger’s strengths is being able to quickly report on precise subsets of your data. Most commands accept an optional query expression, written as arguments after the command name, to filter the data by date, account name or other criteria.

Here are some more notes on this subject:

  • https://github.com/simonmichael/hledger/issues/203#issuecomment-369972593
    When query expressions aren't expressive enough, it's common practice to pipe two hledger commands together in a pipe. First a print command to select a subset of transactions, then a second command operating on the first command's output. (The output of print is always valid journal format.)

    Eg, which part of salary income went to checking ?

    $ hledger print income:salary | hledger -f- balance assets:checking

Rewrite account names

Here's an example of using account aliases.

Say a sole proprietor has a personal.journal:

    expenses:food  $1

and a business.journal:

    expenses:office supplies  $1
    assets:business checking

So each entity (the business owner, and the business) has their own file with its own simple chart of accounts. However, at tax reporting time we need to view these as a single entity (at least in the US). In unified.journal, we include both files, and rewrite the personal account names to fit into the business chart of accounts,

alias expenses    = equity:draw:personal
alias assets:cash = assets:personal cash
include personal.journal
end aliases

include business.journal

Now we can see the data from both files at once, and the personal account names have changed:

$ hledger -f unified.journal print
2014/01/01                                    # from business.journal - no aliases applied
    expenses:office supplies            $1
    assets:business checking           $-1

2014/01/02                                    # from personal.journal
    equity:draw:personal:food            $1   # <- was expenses:food
    assets:personal cash                $-1   # <- was assets:cash

You can also specify aliases on the command line. This could be useful to quickly rewrite account names when sharing a report with someone else, such as your accountant:

$ hledger --alias 'my earning=income:business' ...

See also Change account name separator.

Rewrite commodity symbols

Three ways to temporarily change a commodity symbol, eg to show "$" as "USD" in a report:


Simplest. Here, we use sed to replace all $ with USD in the output:

$ hledger bal | sed 's/\$/USD /g'


Most powerful. We rewrite the journal file before hledger processes it. You can merge multiple symbols into one this way, eg if inconsistent symbols have been used for a currency:

$ cat $LEDGER_FILE | sed 's/\$/USD /g' | hledger -f- bal

Value conversion

Most portable, requires only hledger. We create a dummy one-to-one market price between the old and new commodity symbols, and use market value reports to convert. This assumes your other market prices, if any, don't interfere.

You can add the market price in the main journal:

$ echo 'P 2000-01-01 $ 1 USD' >> $LEDGER_FILE  # once
$ hledger bal -V

Or in a separate file that you include only when needed:

$ echo 'P 2000-01-01 $ 1 USD' >> rewrite-symbols.j  # once
$ hledger bal -V -f $LEDGER_FILE -f rewrite-symbols.j

Tags tutorial

Author: Robert Nielsen (YouTube: hledger fan) with invaluable input from Simon Michael

Don’t be scared away from using tags in your hledger accounting this Halloween, or any other time for that matter. This is a tutorial on using tags with hledger, so if the idea of using tags has been haunting you, but you are not really sure how to use them, read on. Let’s start with a file showing someone’s Halloween hledger accounting:

; ================ Begin File ================

2016/09/25 ACME Costume
   Expenses:Entertainment     $45.99 ;Amoeba Man

2016/10/31 Smiths
   Income:Treat   -1 candy

2016/10/31 Johnsons
   Income:Treat   -2 candy

2016/10/31 Ms. Miller
   Income:Treat   -1 candy

2016/10/31 Reids
   Income:Treat   -1 candy

2016/10/31 Mr. Freeman
   Income:Treat   -3 candy

2016/10/31 Potters
   Income:Treat   -1 candy

2016/11/01 Medical Associates
   Expenses:Medicial  $80.00 ;dyspepsia

2016/12/15 West End Dentistry
   Expenses:Dental  $160.00 ;two fillings

; ================ End File ================

We want to categorize all the above transactions as having to do with Halloween. One option would be to create a new category of expenses for Halloween, but what if we want to also keep the other categories, such as Entertainment, Medical, and Dental? For example, we want to have the visit to the dentist categorized both as a dental expense, but at the same time we should be able to find it under expenses related to Halloween. One solution is to add a tag for Halloween.

Mechanics of Writing a Tag

One of the simplest tags is a word, which is inside a comment, immediately followed by a colon. In the file below, you will see all the transactions have a tag, named Halloween, added to them:

; ================ begin file ================

2016/09/25 ACME Costume ; Halloween:
   Expenses:Entertainment     $45.99 ;Amoeba Man

2016/10/31 Smiths ; Halloween:
   Income:Treat   -1 candy

2016/10/31 Johnsons ; Halloween:
   Income:Treat   -2 candy

2016/10/31 Ms. Miller ; Halloween:
   Income:Treat   -1 candy
2016/10/31 Reids ; Halloween:
   Income:Treat   -1 candy
2016/10/31 Mr. Freeman ; Halloween:
   Income:Treat   -3 candy
2016/10/31 Potters ; Halloween:
   Income:Treat   -1 candy
2016/11/01 Medical Associates ; Halloween:
   Expenses:Medicial  $80.00 ;dyspepsia
2016/12/15 West End Dentistry ; Halloween:
   Expenses:Dental  $160.00 ;two fillings
; ================ end file ================

Using Tags

Once we incorporate tags in our hledger file, we can use them in a variety of ways. Here, we will look at one way, which is to use the tags to filter a command.

This command:

$ hledger register Expenses tag:Halloween

Produces something like the following output (all the Expenses tagged “Halloween”):

2016/09/25     ACME Costume           Liabilities:CreditCard   -$45.99          -$45.99
2016/11/01     Medical Associates     Liabilities:CreditCard   -$80.00          -$125.99
2016/12/15     West End Dentistry     Liabilities:CreditCard  -$160.00          -$285.99

Of course, if you have only the contents of the above file, you will get the same output whether or not you specify a tag in the command, but in real life you will have a multitude of transactions, only a few of which will be tagged Halloween. At that point, it becomes highly useful to narrow the scope of a command such as register, to apply to just those transactions with a specific tag.

And because we chose to use a tag, as opposed to creating a new expense category, we can still find out what we spent on dentistry, doctor’s bills, and so on.

Adding Values to Tags

Next, we will add values to the tags, so that we can narrow our reports to the actual value of the tag.

For the example we will be using below, a person wishes to rate the quality of the candy they receive from their neighbors on Halloween. (A ghoulish idea, I know, even for Halloween.)

The Mechanics

To add a value to a tag, you add one or more words to the tag. For example, the transaction below has the tag “quality” and the value is “very good”:

2016/10/31 Johnsons
   Income:Treat   -2 candy ; quality:very good

Note that you can optionally put a space after the colon, as shown immediately below:

2016/10/31 Johnsons
   Income:Treat   -2 candy ; quality: very good

Here is the file that we will be working with:

; ================ begin file ================
2016/10/31 Smiths ; Halloween:
   Income:Treat   -1 candy ; quality: OK
2016/10/31 Johnsons ; Halloween:
   Income:Treat   -2 candy ; quality:very good
2016/10/31 Ms. Miller ; Halloween:
   Income:Treat   -1 candy ; quality: OK
2016/10/31 Reids ; Halloween:
   Income:Treat   -1 candy ; quality:excellent
2016/10/31 Mr. Freeman ; Halloween:
   Income:Treat   -3 candy ; quality:OK
2016/10/31 Potters ; Halloween:
   Income:Treat   -1 candy ; quality: very good
2016/11/01 Medical Associates ; Halloween:
   Expenses:Medicial  $80.00 ;dyspepsia
2016/12/15 West End Dentistry ; Halloween:
   Expenses:Dental  $160.00 ;two fillings
; ================ end file ================

In the above file, we have added values for the quality of each of the treats, specifically,

very good

Let’s say we want to list all the treats that were rated “OK.”

We could use the command:

$ hledger register tag:quality=OK

The above would output something like the following:

2016/10/31 Smiths         Income:Treat     -1 candy     -1 candy
2016/10/31 Ms. Miller     Income:Treat     -1 candy     -2 candy
2016/10/31 Mr. Freeman    Income:Treat     -3 candy     -5 candy

On the other hand, if we wanted to list only the treats that were rated as excellent, we could use the command:

$ hledger register tag:quality=excellent

The above command, lists the following:

2016/10/31 Reids          Income:Treat     -1 candy     -1 candy

Watch Out for the Spaces

Now, let’s look for every treat rated as “very good.” If we try,

$ hledger register tag:quality=very good

We get exactly nothing. What happened? As you most likely guessed from the title of this section, the space between “very” and “good” is throwing things off.

Therefore, you will need something like the following, to find all the treats rated “very good”:

$ hledger register tag:quality=”very good”

Combining Tags and Comments

What if you want both a tag and a comment in the same line? A tag goes in a comment, but what are the restrictions? Glad you asked that question!

Answer: hledger tags go inside a comment, and there are two options. First, the tag can go at the end of the comment.

For example, you make a purchase of an inflatable turkey and want to include that fact in a comment. However, in the same line you also want to tag it as holiday:Thanksgiving. One option is to write the comment followed by the tag:

2016/09/26 ACME Holiday Supplies 
  Expenses:Entertainment    $58.99 ; inflatable pumpkin holiday:Halloween

The second option is to end the tag with a comma. You can then put a comment after the comma.

For example, we have the tag first (holiday:Halloween) followed by a comma. After the comma is a comment (inflatable pumpkin).

2016/09/26 ACME Holiday Supplies 
  Expenses:Entertainment    $58.99 ; holiday:Halloween, inflatable pumpkin

Note that if you put a comment after the tag without separating the tag and comment with a comma, the comment becomes part of the tag value, and this can cause unwanted results. You do not want unwanted results when working with important data!

Finally, it's possible to have a comment, followed by a tag, and as long as you end the tag in a comma, you can have additional comment after the tag.

Multiple Tags

Sometimes things get frighteningly complicated, as in the case of the mummy, a monster popular in fiction and film.

Our mummy in question likes to have a variety of changes of wrappings, and so it has made several purchases from its favorite online store:

2020/05/13 AcmeWrappings.com
    Expenses:Clothing                         $28.00

2020/05/15 AcmeWrappings.com
    Expenses:Clothing                         $44.90  

2020/05/17 AcmeWrappings.com
    Expenses:Clothing                         $36.50  

2020/05/20 AcmeWrappings.com
    Expenses:Clothing                         $58.99  

2020/05/28 AcmeWrappings.com
    Expenses:Clothing                         $39.00  

In addition to recording the above, our mummy wants to track which type of wrappings it buys. Specifically, it needs to track the type of fabric, the width of the cloth, and the color of the material. How does it do this?

Fortunately, hledger allows multiple tags, with each tag separated by a comma. Here is an example:

; fabric:cotton, width:15, color:parchment

The mummy now adds the tags to each purchase, resulting in:

2020/05/13 AcmeWrappings.com  Expenses:Clothing  $28.00  ; fabric:cotton, width:15, color:parchment

2020/05/15 AcmeWrappings.com  Expenses:Clothing  $44.90  ; fabric:nylon, width:20, color:ancient white

2020/05/17 AcmeWrappings.com  Expenses:Clothing  $36.50  ; fabric:wool, width:15, color:dust

2020/05/20 AcmeWrappings.com  Expenses:Clothing  $58.99  ; fabric:wool, width:20, color:ancient white

2020/05/28 AcmeWrappings.com  Expenses:Clothing  $39.00  ; fabric:cotton, width:30, color:parchment

To see everything purchased made of wool, the mummy types:

$ hledger register tag:fabric=wool

The result is:

2020/05/17 AcmeWrappings.com  Expenses:Clothing  $36.50  $36.50
2020/05/20 AcmeWrappings.com  Expenses:Clothing  $58.99  $95.49

Or to see which wrappings had a width of 20:

$ hledger register tag:width=20

which outputs...

2020/05/15 AcmeWrappings.com  Expenses:Clothing  $44.90   $44.90
2020/05/20 AcmeWrappings.com  Expenses:Clothing  $58.99  $103.89

Multiple Tags in a Query

Multiple tags can be used in the same query. For example, our monster friend wants to find out how much parchment colored cotton it has purchased:

$ hledger register tag:fabric=cotton tag:color=parchment

Notice that there is no comma between the two tags in the above query. You use the comma to separate tags in the data file, but not the query.

By the way, the above command outputs:

2020/05/13 AcmeWrappings.com   Expenses:Clothing  $28.00  $28.00
2020/05/28 AcmeWrappings.com   Expenses:Clothing  $39.00  $67.00

Each wrapping purchased above is both made of cotton AND has the color of parchment.

If you wish to find all wrappings, say, made of wool OR colored parchment, you can run two queries. First, we find all the wool wrappings:

$ hledger register tag:fabric=wool

Then we find all the parchment colored ones:

$ hledger register tag:color=parchment

Expenses per Category

One common question is how much did we spend in total for each tag value? For example, how much did the mummy spend on each color of wrapping? To find out, we can run a balance report for our expenses pivoted by color:

$ hledger balance expenses --pivot color

The result is:

             $103.89  ancient white
              $36.50  dust
              $67.00  parchment

Aha, we see a lot of spending on “ancient white.”

Similarly, if we want to see the expenses by type of fabric, we type:

$ hledger balance expenses --pivot fabric

And we get:

              $67.00  cotton
              $44.90  nylon
              $95.49  wool

From the above, we observe that the mummy has spent the more on wool wrappings than on either cotton or nylon ones.


This tutorial has shown how to:

  •  Add a tag to an hledger transaction
  •  Use tags with the register command to list only those expenses with a given tag
  •  Add values to tags
  •  Use tag values with the register command to list only those expenses a given tag with a given value
  •  Combine comments and tags in the same line
  •  Use multiple tags in the same line
  •  Use the --pivot option to total expenses by tag value

Hledger Tag Summary with Examples

Add a tag to an hledger transaction

Inside a comment write a word immediately followed by a colon:

2016/09/25 ACME Costume ; Halloween:
   Expenses:Entertainment     $45.99

A tag can apply to a whole transaction, as in the above, or just one of the lines as in:

2016/09/25 ACME Costume
   Expenses:Entertainment     $45.99 ; Halloween:

Use tags with the register command to list only the expenses for a given tag

$ hledger -f Halloween2.hledger register Expenses tag:Halloween

Add values to tags

To add a value to a tag, add one or more words to the tag:

2016/10/31 Johnsons
   Income:Treat   -2 candy ; quality: very good

Limit a command to tags with specified values

$ hledger -f Halloween3.hledger register tag:quality=”very good”

Note that if your tag value has a space in it, you must do something such as put quotation marks around the entire tag value. If there are no spaces in your tag value, the quotation marks are optional.

Combine comments and tags

Two options. The first is to write comments first, and put tags at the end:

2016/10/31 Grocery Store   
   Expenses     $3.52   ;  on sale today item:candy

The second option is to put a comma after the tag, and then add a comment after the comma:

2016/10/31 Grocery Store   
   Expenses     $3.52   ;  item:candy, on sale today

You can even have comment, tag, comma, and comment.

Use multiple tags on the same line

Separate multiple tags with a comma:

2020/05/20 AcmeWrappings.com  
    Expenses:Clothing  $58.99  ; fabric:wool, width:20, color:ancient white

Use the --pivot option to total expenses by tag value

$ hledger balance expenses --pivot color


$ hledger balance expenses --pivot fabric


Tag, you’re it.


Here is a start at gathering budgeting-related resources.

PTA budgeting notes

 <sm> two commands that are roughly equivalent: ledger budget --add-budget expenses, hledger balance --budget -E expenses
 <sm> they show both budgeted and unbudgeted accounts            
--budget has no effect on single-column reports, it requires a reporting interval
--budget INTERVAL enables all periodic transactions with that interval; these can be date-limited
--budget hides all non-budgeted subaccounts; can be depth-limited more
<sm> there's different ways to do budgeting                     [16:46]
<sm> let me try to count them                                   [16:50]
<sm> "envelope budgeting" is analogous to having a set of envelopes containing cash for different purposes. 
You can model the "envelopes" with 
a. real-world accounts (eg your bank lets you create arbitrary savings accounts), 
b. virtual (imaginary) subaccounts of a real-world account (eg your checking account), 
c. virtual accounts "off to the side" (budget:*)
<sm> also you can do the transfers to and from these manually, or generate them with automated posting rules
<sm> "goal budgeting" (best name I can come up with) involves setting some inflow/outflow goals per account per period, 
and then measuring how the actual flows compare with the goals. balance --budget provides this report
<sm> I think that's 7 ways

From https://www.reddit.com/r/plaintextaccounting/comments/doq9p5/new_to_ledger_budgeting_question:

Also search for budgeting links at http://plaintextaccounting.org . You'll see two main approaches discussed:

  1. "envelope budgeting" - sounds more like what you've been doing. Based around explicitly allocating money for each purpose. Good for managing > cashflow. Requires more journal entries. Can be done entirely manually (1a) but many docs advise using automatic posting rules to assist (1b). Many > different ways to handle the details. Requires more thinking.

  2. the other kind ("report-based budgeting" ?). Based around a special budget report provided by Ledger/hledger, which uses periodic transaction > rules to set budget goals. Automatic posting rules might be useful here too, I'm not sure. Provides less enforcement, requires less work. Fewer ways > to do it, perhaps provides simpler/clearer reports.

I often find "budgeting" covers/touches on quite a lot of topics:

  • setting earning/spending goals,
  • reviewing performance against those goals,
  • controlling earning/spending based on the goals,
  • allocating funds for short term expenses,
  • allocating funds towards savings goals,
  • updating allocated funds as transactions occur,
  • reallocating funds/balancing the budget,
  • end of period actions (roll over ? reset ?),
  • forecasting cash balances and managing cashflow,
  • forecasting income/expenses...

How to set up a time budget

  • create a time.journal which includes your (timedot or timeclock) time log file (assuming you're not tracking time in journal format)
    # time.journal
    include time.timedot
  • choose a budget interval, eg daily, weekly or monthly
  • if you have some historical timelog data, review average spending on that interval to get a baseline
    $ hledger -f time.journal date:thisyear bal -WA
  • in time.journal add a periodic transaction rule to allocate budget amounts, similar to baseline, on that interval
    ~ weekly
        (adm:time)       1h
        (ser:some:proj)  4h
  • run a budget report, using the same interval:
    $ hledger -f time.journal bal --budget -W
    Balance changes in 2017/11/27w48:
                ||     2017/11/27w48 
    adm:time      || 0.25h [25% of 1h] 
    ser:some:proj || 0.75h [19% of 4h] 
                ||             1.00h 

A time budgeting workflow

From https://news.ycombinator.com/item?id=19203521, summary of my 2018-2019 time budgeting workflow:

I keep a hledger timedot file[1] open in a hot-key drop-down iTerm window. Each 15-minute chunk is logged with a dot. I group dots into hours for > quick visual scanning.

fos.hledger.sup  .
adm.email  ..
adm.finance  .... .... ..
fos.plaintextaccounting  .
fos.hledger.issues.941  .... .
has-res  ...
biz-res  ..

I've trained myself to update this often while at the computer, and before walking away. Delayed retroactive logging is also pretty easy. Working in quarter/half/whole hour chunks, and in rhythm with the clock, and having a pane showing recent sleep/wake/timelog-saved events, all help. Not every day is the same; this system has been quick and flexible enough to suit a range of conditions. I can set daily/weekly/monthly time budgets if I want. Some more details at [2].

Budgeting and forecasting

Note: this is a cookbook doc, written for hledger 1.5 in 2018; still useful but in need of update. For more and fresher docs, see also Budgeting.

Budgeting and forecasting allows you to keep better track of your expenses and future financial situation. If you write down your expectations of what your income/expenses/investment yields/etc should be, you can use them to:

  • check how far off are your expectations from reality (budgeting)
  • project your future account activity or balances (forecasting)

(This section uses examples/bcexample.hledger from hledger source repository).

Periodic budget

To start budgeting, you need to know what your average yearly or weekly expenditures are. Hledger could help you with that. Usually the interval for which you compute budget figures will be the same as the interval between your paychecks -- monthly or weekly.

Lets create monthly (-M) report for years 2013-2014 (-b 2013) of all top-level expense categories (--depth 2 Expenses), looking for average figures (-A), limiting ourselves to USD transactions only, to save screen space:

$ hledger balance -f bcexample.hledger -MA -b 2013 --depth 2 Expenses cur:USD
Balance changes in 2013/01/01-2014/10/31:

                    ||     2013/01      2013/02      2013/03  ...      2014/07      2014/08      2014/09      2014/10      Average 
 Expenses:Financial ||    4.00 USD    12.95 USD    39.80 USD  ...    30.85 USD    21.90 USD    12.95 USD     4.00 USD    17.83 USD 
 Expenses:Food      ||  396.46 USD   481.48 USD   603.32 USD  ...   871.20 USD   768.23 USD   466.72 USD    83.00 USD   562.10 USD 
 Expenses:Health    ||  290.70 USD   193.80 USD   193.80 USD  ...   290.70 USD   193.80 USD   193.80 USD    96.90 USD   207.01 USD 
 Expenses:Home      || 2544.98 USD  2545.02 USD  2544.97 USD  ...  2545.12 USD  2545.01 USD  2545.10 USD            0  2429.33 USD 
 Expenses:Taxes     || 5976.60 USD  3984.40 USD  4901.83 USD  ...  5976.60 USD  3984.40 USD  3984.40 USD  1992.20 USD  4322.27 USD 
 Expenses:Transport ||  120.00 USD   120.00 USD   120.00 USD  ...            0   120.00 USD   120.00 USD   120.00 USD   109.09 USD 
                    || 9332.74 USD  7337.65 USD  8403.72 USD  ...  9714.47 USD  7633.34 USD  7322.97 USD  2296.10 USD  7647.64 USD 

This report is rather wide and portion of it had been cut out for brevity. Most interesting column is the last one, it shows average monthly expenses for each category. Expenses in Food, Health, Home and Transport categories seem to roughly similar month to month, so lets create a budget for them.

Budgets are described with periodic transactions. Periodic transaction has ~ instead of date and period expression instead of description. In this case we want to create a monthly budget that will come into effect starting from January 2013, which will include income of 10000 USD that is partially spent on Food, Health, Home and Transport and the rest becomes our Assets:

~ monthly from 2013/01
  Expenses:Food    500 USD
  Expenses:Health  200 USD
  Expenses:Home    2545 USD
  Expenses:Transport   120 USD
  Income:US        -10700 USD ;; Taken as monthy average of Income account group

This transaction could be put into separate file (budget.journal) or could be kept in the main journal. Normally hledger will ignore it and will not include it in any computations or reports.

To put it into action, you need to add --budget switch to your balance invocation. If you do that, you would be able to see how your past expenses aligned with the budget that you just created. This time, lets not limit accounts in any way:

$ hledger balance -f bcexample.hledger -f budget.journal -MB -b 2013 --budget cur:USD
Balance changes in 2013/01/01-2014/10/31:

                          ||                            2013/01                            2013/02                             2013/03 
 <unbudgeted>:Expenses    ||                        5980.60 USD                        3997.35 USD                         4941.63 USD 
 <unbudgeted>:Liabilities ||                         293.09 USD                        -147.51 USD                          -66.01 USD 
 Assets:US                ||      1893.32 USD [26% of 7335 USD]      2929.77 USD [40% of 7335 USD]     -3898.89 USD [-53% of 7335 USD] 
 Expenses:Food            ||        396.46 USD [79% of 500 USD]        481.48 USD [96% of 500 USD]        603.32 USD [121% of 500 USD] 
 Expenses:Health          ||       290.70 USD [145% of 200 USD]        193.80 USD [97% of 200 USD]         193.80 USD [97% of 200 USD] 
 Expenses:Home            ||     2544.98 USD [100% of 2545 USD]     2545.02 USD [100% of 2545 USD]      2544.97 USD [100% of 2545 USD] 
 Expenses:Transport       ||       120.00 USD [100% of 120 USD]       120.00 USD [100% of 120 USD]        120.00 USD [100% of 120 USD] 
 Income:US                || -15119.10 USD [141% of -10700 USD]  -10331.21 USD [97% of -10700 USD]  -11079.40 USD [104% of -10700 USD] 
                          ||                       -3599.95 USD                        -211.30 USD                        -6640.58 USD 

Numbers in square brackets give you your budget estimate and percentage of it used by your real expenses. Numbers below 100% mean that you have some of your budget left, numbers over 100% mean that you went over your budget.

You can notice that actual numbers for Assets:US seem to be well below computed budget of 7335 USD. Why? Answer to this is in the first row of the report: we have quite a lot of unbudgeted Expenses!

Notice that even though we have not limited accounts in any way, report includes just those mentioned in the budget. This is on purpose, assumption is that when you are checking your budgets you probably do not want unbudgeted accounts getting in your way. Another thing to note is that budget numbers have been allocated to top-level expense subcategories (like Expenses:Food). Journal has subaccounts under Food, but to compute budget report they have all been rolled up into a nearest parent with budget number associated with it. Accounts that do not have such parent went into <unbudgeted> row.

Allright, it seems that for Jan 2013 we have ~3000 USD of budgeted expenses and almost twice as much unbudgeted. Lets figure out what they are. We can see more details if we add -E/--empty switch:

$ hledger balance -f bcexample.hledger -f budget.journal -M -b 2013-01 -e 2013-02 --budget cur:USD -E
Balance changes in 2013/01:

                                  ||                            2013/01 
 Assets:US                        ||      1893.32 USD [26% of 7335 USD] 
 Expenses:Financial:Fees          ||                           4.00 USD 
 Expenses:Food                    ||        396.46 USD [79% of 500 USD] 
 Expenses:Health                  ||       290.70 USD [145% of 200 USD] 
 Expenses:Home                    ||     2544.98 USD [100% of 2545 USD] 
 Expenses:Taxes:Y2013:US:CityNYC  ||                         524.76 USD 
 Expenses:Taxes:Y2013:US:Federal  ||                        3188.76 USD 
 Expenses:Taxes:Y2013:US:Medicare ||                         319.86 USD 
 Expenses:Taxes:Y2013:US:SDI      ||                           3.36 USD 
 Expenses:Taxes:Y2013:US:SocSec   ||                         844.62 USD 
 Expenses:Taxes:Y2013:US:State    ||                        1095.24 USD 
 Expenses:Transport               ||       120.00 USD [100% of 120 USD] 
 Income:US                        || -15119.10 USD [141% of -10700 USD] 
 Liabilities:US:Chase:Slate       ||                         293.09 USD 
                                  ||                       -3599.95 USD 

All the accounts that were rolled up into <unbudgeted> category are now shown with their original name, but budgeted accounts are still rolled up. It is easy to see now that we forgot taxes. Lets add them to our budget:

~ monthly from 2013/01
  Expenses:Food    500 USD
  Expenses:Health  200 USD
  Expenses:Home    2545 USD
  Expenses:Transport   120 USD
  Expenses:Taxes   4300 USD ;; Taken from monthly average report
  Income:US        -10700 USD

Lets try again for a couple of month with this updated budget:

$ hledger balance -f bcexample.hledger -f budget.journal -M -b 2013-01 -e 2013-04 --budget cur:USD 
Balance changes in 2013q1:

                          ||                            2013/01                            2013/02                             2013/03 
 <unbudgeted>:Expenses    ||                           4.00 USD                          12.95 USD                           39.80 USD 
 <unbudgeted>:Liabilities ||                         293.09 USD                        -147.51 USD                          -66.01 USD 
 Assets:US                ||      1893.32 USD [62% of 3035 USD]      2929.77 USD [97% of 3035 USD]    -3898.89 USD [-128% of 3035 USD] 
 Expenses:Food            ||        396.46 USD [79% of 500 USD]        481.48 USD [96% of 500 USD]        603.32 USD [121% of 500 USD] 
 Expenses:Health          ||       290.70 USD [145% of 200 USD]        193.80 USD [97% of 200 USD]         193.80 USD [97% of 200 USD] 
 Expenses:Home            ||     2544.98 USD [100% of 2545 USD]     2545.02 USD [100% of 2545 USD]      2544.97 USD [100% of 2545 USD] 
 Expenses:Taxes           ||     5976.60 USD [139% of 4300 USD]      3984.40 USD [93% of 4300 USD]      4901.83 USD [114% of 4300 USD] 
 Expenses:Transport       ||       120.00 USD [100% of 120 USD]       120.00 USD [100% of 120 USD]        120.00 USD [100% of 120 USD] 
 Income:US                || -15119.10 USD [141% of -10700 USD]  -10331.21 USD [97% of -10700 USD]  -11079.40 USD [104% of -10700 USD] 
                          ||                       -3599.95 USD                        -211.30 USD                        -6640.58 USD 

Now unbudgeted amounts are much smaller and some of them could be dismissed as noise, and we can see that budget created is actually close enough to the real numbers, meaning that they are usually close to average that we put in our budget.

Envelope budget

Budget report that we have used so far assumes that any unused budget amount for a given (monthly) period will not contribute to the budget of the next period. Alternative popular "envelope budget" strategy assumes that you put a certain amount of money into an envelope each month, and any unused amount stays there for future expenses. This is easy to simulate by adding --cumulative switch. Lets redo the last report with it:

$ hledger balance -f bcexample.hledger -f budget.journal -M -b 2013-01 -e 2013-04 --cumulative --budget cur:USD
Ending balances (cumulative) in 2013q1:

                          ||                         2013/01/31                          2013/02/28                          2013/03/31 
 <unbudgeted>:Expenses    ||                           4.00 USD                           16.95 USD                           56.75 USD 
 <unbudgeted>:Liabilities ||                         293.09 USD                          145.58 USD                           79.57 USD 
 Assets:US                ||      1893.32 USD [62% of 3035 USD]       4823.09 USD [79% of 6070 USD]        924.20 USD [10% of 9105 USD] 
 Expenses:Food            ||        396.46 USD [79% of 500 USD]        877.94 USD [88% of 1000 USD]       1481.26 USD [99% of 1500 USD] 
 Expenses:Health          ||       290.70 USD [145% of 200 USD]        484.50 USD [121% of 400 USD]        678.30 USD [113% of 600 USD] 
 Expenses:Home            ||     2544.98 USD [100% of 2545 USD]      5090.00 USD [100% of 5090 USD]      7634.97 USD [100% of 7635 USD] 
 Expenses:Taxes           ||     5976.60 USD [139% of 4300 USD]      9961.00 USD [116% of 8600 USD]    14862.83 USD [115% of 12900 USD] 
 Expenses:Transport       ||       120.00 USD [100% of 120 USD]        240.00 USD [100% of 240 USD]        360.00 USD [100% of 360 USD] 
 Income:US                || -15119.10 USD [141% of -10700 USD]  -25450.31 USD [119% of -21400 USD]  -36529.71 USD [114% of -32100 USD] 
                          ||                       -3599.95 USD                        -3811.25 USD                       -10451.83 USD 

If you look at Expenses:Food category, you will see that every month budget is increased by 500 USD, and by March total amount budgeted is 1500 USD, of which 1481.26 USD is spent. If you look back at the previous non-cumulative monthly budget report, you will see that in March food expenses were 121% of the budgeted amount, but cumulative report shows that taking into account budget carry-over from Jan and Feb we are well within planned numbers.


Budget transaction that was created could be used to predict what would be our financial situation in the future. If you add --forecast switch, you will see how budgeted income and expense affects you past the last transaction in the journal. Since journal ends in Oct 2014, lets see next two month:

$ hledger balance -f bcexample.hledger -f budget.journal -M -b 2014-10 -e 2015 --forecast cur:USD
Balance changes in 2014q4:

                                    ||      2014/10     2014/11     2014/12 
 Assets:US                          ||            0    3035 USD    3035 USD 
 Assets:US:BofA:Checking            || -2453.40 USD           0           0 
 Assets:US:ETrade:Cash              ||  5000.00 USD           0           0 
 Expenses:Financial:Fees            ||     4.00 USD           0           0 
 Expenses:Food                      ||            0     500 USD     500 USD 
 Expenses:Food:Restaurant           ||    83.00 USD           0           0 
 Expenses:Health                    ||            0     200 USD     200 USD 
 Expenses:Health:Dental:Insurance   ||     2.90 USD           0           0 
 Expenses:Health:Life:GroupTermLife ||    24.32 USD           0           0 
 Expenses:Health:Medical:Insurance  ||    27.38 USD           0           0 
 Expenses:Health:Vision:Insurance   ||    42.30 USD           0           0 
 Expenses:Home                      ||            0    2545 USD    2545 USD 
 Expenses:Taxes                     ||            0    4300 USD    4300 USD 
 Expenses:Taxes:Y2014:US:CityNYC    ||   174.92 USD           0           0 
 Expenses:Taxes:Y2014:US:Federal    ||  1062.92 USD           0           0 
 Expenses:Taxes:Y2014:US:Medicare   ||   106.62 USD           0           0 
 Expenses:Taxes:Y2014:US:SDI        ||     1.12 USD           0           0 
 Expenses:Taxes:Y2014:US:SocSec     ||   281.54 USD           0           0 
 Expenses:Taxes:Y2014:US:State      ||   365.08 USD           0           0 
 Expenses:Transport                 ||            0     120 USD     120 USD 
 Expenses:Transport:Tram            ||   120.00 USD           0           0 
 Income:US                          ||            0  -10700 USD  -10700 USD 
 Income:US:Hoogle:GroupTermLife     ||   -24.32 USD           0           0 
 Income:US:Hoogle:Salary            || -4615.38 USD           0           0 
 Liabilities:US:Chase:Slate         ||  -203.00 USD           0           0 
                                    ||            0           0           0 

Note that this time there is no roll-up of accounts. Unlike --budget, which could be used with balance command only, --forecast could be used with any report. Forecast transactions would be added to your real journal and would appear in the report you requested as if you have entered them on the scheduled dates.

Since quite a lot of accounts do not have any budgeted transactions, lets limit the depth of the report to avoid seeing lots of zeroes:

$ hledger balance -f bcexample.hledger -f budget.journal -M -b 2014-10 -e 2015 --forecast cur:USD --depth 2
Balance changes in 2014q4:

                    ||      2014/10     2014/11     2014/12 
 Assets:US          ||  2546.60 USD    3035 USD    3035 USD 
 Expenses:Financial ||     4.00 USD           0           0 
 Expenses:Food      ||    83.00 USD     500 USD     500 USD 
 Expenses:Health    ||    96.90 USD     200 USD     200 USD 
 Expenses:Home      ||            0    2545 USD    2545 USD 
 Expenses:Taxes     ||  1992.20 USD    4300 USD    4300 USD 
 Expenses:Transport ||   120.00 USD     120 USD     120 USD 
 Income:US          || -4639.70 USD  -10700 USD  -10700 USD 
 Liabilities:US     ||  -203.00 USD           0           0 
                    ||            0           0           0 

As you can see, we should expect 3035 USD to be added into Assets:US each month. It is quite easy to see how overal amount of Assets will change with time if you use --cumulative switch:

$ hledger balance -f bcexample.hledger -f budget.journal -M -b 2014-10 -e 2015 --forecast cur:USD --depth 2 --cumulative
Ending balances (cumulative) in 2014q4:

                    ||   2014/10/31     2014/11/30     2014/12/31 
 Assets:US          ||  2546.60 USD    5581.60 USD    8616.60 USD 
 Expenses:Financial ||     4.00 USD       4.00 USD       4.00 USD 
 Expenses:Food      ||    83.00 USD     583.00 USD    1083.00 USD 
 Expenses:Health    ||    96.90 USD     296.90 USD     496.90 USD 
 Expenses:Home      ||            0       2545 USD       5090 USD 
 Expenses:Taxes     ||  1992.20 USD    6292.20 USD   10592.20 USD 
 Expenses:Transport ||   120.00 USD     240.00 USD     360.00 USD 
 Income:US          || -4639.70 USD  -15339.70 USD  -26039.70 USD 
 Liabilities:US     ||  -203.00 USD    -203.00 USD    -203.00 USD 
                    ||            0              0              0 

According to forecast, assets are expected to grow to 8600+ USD by the end of 2014. However, our forecast does not include a couple of big one-off year end expenses. First, we plan to buy prize turkey for the Christmas table every year from 2014, spending up to 500 USD on it. And on 17th Nov 2014 we would celebrate birthday of significant other, spending up to 6000 USD in a fancy restaurant:

~ every 20th Dec from 2014
  Expenses:Food   500 USD ; Prize turkey, the biggest of the big

~ 2014/11/17
  Expenses:Food   6000 USD ; Birthday, lots of guests 

Note that turkey transaction is not entered as "yearly from 2014/12/20", since yearly/quarterly/monthy/weekly periodic expressions always generate entries at the first day of the calendar year/quarter/month/week. Thus "monthly from 2014/12" will occur on 2014/12/01, 2015/01/01, ..., whereas "every 20th of month from 2014/12" will happen on 2014/12/20, 2015/12/20, etc.

With latest additions forecast now looks like this:

hledger balance -f bcexample.hledger -f budget.journal -M -b 2014-10 -e 2015 --forecast cur:USD --depth 2 --cumulative
Ending balances (cumulative) in 2014q4:

                    ||   2014/10/31     2014/11/30     2014/12/31 
 Assets:US          ||  2546.60 USD    -418.40 USD    2116.60 USD 
 Expenses:Financial ||     4.00 USD       4.00 USD       4.00 USD 
 Expenses:Food      ||    83.00 USD    6583.00 USD    7583.00 USD 
 Expenses:Health    ||    96.90 USD     296.90 USD     496.90 USD 
 Expenses:Home      ||            0       2545 USD       5090 USD 
 Expenses:Taxes     ||  1992.20 USD    6292.20 USD   10592.20 USD 
 Expenses:Transport ||   120.00 USD     240.00 USD     360.00 USD 
 Income:US          || -4639.70 USD  -15339.70 USD  -26039.70 USD 
 Liabilities:US     ||  -203.00 USD    -203.00 USD    -203.00 USD 
                    ||            0              0              0 

It is easy to see that in Nov 2014 we will run out of Assets. Using register we can figure out when or why it would happen:

$ hledger register -f bcexample.hledger -f budget.journal -b 2014-10 -e 2014-12 --forecast cur:USD Assets
2014/10/04 "BANK FEES" | "Monthly bank fee"         Assets:US:BofA:Checking                      -4.00 USD     -4.00 USD
2014/10/09 "Hoogle" | "Payroll"                     Assets:US:BofA:Checking                    2550.60 USD   2546.60 USD
2014/10/10 "Transfering accumulated savings to o..  Assets:US:BofA:Checking                   -5000.00 USD  -2453.40 USD
                                                    Assets:US:ETrade:Cash                      5000.00 USD   2546.60 USD
2014/11/01 Forecast transaction                     Assets:US                                     3035 USD   5581.60 USD
2014/11/17 Forecast transaction                     Assets:US                                    -6000 USD   -418.40 USD

It is 6000 USD planned for birthday! Something will have to be done about the birthday plans.

Common journal entries

Example hledger journal entries for various kinds of transaction.


2017/1/26 market
  expenses:food    $10

Tracking a mortgage

2019/01/01 Buy House
    Assets:House                                      500,000.00

2019/02/01 Mortgage Payment
    Liabilities:Mortgage                                1,000.00
    Expenses:Interest:Real Estate                         833.33
    Assets:Cash                                         -1833.33

2019/03/01 Mortgage Payment
    Liabilities:Mortgage                                1,002.00
    Expenses:Interest:Real Estate                         831.33
    Assets:Cash                                         -1833.33

2019/03/01 Zillow Price Estimate
    Assets:House                                                 = 505,000.00
    Equity:Unrealized Gains


Invoicing entries are different for accrual basis or cash basis accounting. Large companies use accrual basis, individuals and small companies typically use cash basis. https://en.wikipedia.org/wiki/Basis_of_accounting

Accrual basis

Send an invoice. This is the taxable event:

2018-04-16 * (2018-001) SuperCompany invoice
    Revenue:Software Development                        $ -2420.00
    Assets:Accounts Receivable:SuperCompany              $ 2420.00

Receive payment:

2018-04-26 * (2018-001) SuperCompany payment
    Assets:Accounts Receivable:SuperCompany             $ -2420.00 = $0
    Assets:Checking                                      $ 2420.00

Cash basis

Invoices aren't normally tracked in cash basis, so we're using unbalanced postings to track them here. Send an invoice:

2018-04-16 * (2018-001) SuperCompany invoice
    (Assets:Accounts Receivable:SuperCompany)             $2420

Receive payment. This is the taxable event. Also: estimate the corresponding tax, and save that amount in a subaccount so there'll be money to pay taxes:

2018-04-26 * (2018-001) SuperCompany payment
    (Assets:Accounts Receivable:SuperCompany)            $-2420 = $0
    Revenue:Software Development                         $-2420
    (Liabilities:Tax:2018)                                $-420
    Assets:Checking:Estimated Tax Savings:2018             $420
    Assets:Checking                                       $2000

Foreign trip expenses

From https://www.reddit.com/r/plaintextaccounting/comments/9r9cfj/beancount_price_and_cost :

  1. Before going to vacation to Europe, I borrowed 350 EUR, cash.
  2. I also took out of ATM 200 EUR, cash - now I know the price.
  3. I spent 500 EUR in trip, and I have 50 left.
  4. Now, after the trip, I exchanged some of my home currency to 300 EUR to give it back - and it's the different price from step two. So how do I write all this down?

My attempt follows. Notes:

  • When transactions occur on such trips, I sometimes know the USD amount spent, and sometimes the EUR amount. I sometimes know the total converted amount, and sometimes the conversion rate. I record whichever of these is more convenient.
  • After the trip, when reviewing expenses, I'll add a P market price directive covering the period of the trip, and use -V to see all expenses in home currency (USD).
; a hledger example based on colindean's
; hledger doesn't currently support the {} syntax, just @ or @@

2018-10-25 * vacation loan
    Assets:Cash                             350 EUR

2018-10-26 * ATM withdrawal
    Assets:Cash                             200 EUR @@ 220 USD  ; conversion price written out for clarity; redundant due to -225 USD below
    Expenses:Fees:CurrencyConversion          5 USD
    Assets:Bank                            -225 USD

2018-10-27 * food
    Assets:Cash                            -190 EUR

2018-10-27 * hotel
    Assets:Cash                            -310 EUR = 50 EUR    ; assert that Cash's EUR balance is now 50

2018-10-28 * withdraw more euros to repay loan
    Assets:Cash                             300 EUR @@ 360 USD  ; conversion rate has gone up to 1.20
    Expenses:Fees:CurrencyConversion          5 USD
    Assets:Bank                            -365 USD

2018-10-28 * repay vacation loan
    Liabilities:Loans:Vacation              350 EUR = 0 EUR     ; assert that euro loan is repaid

; Conversion rate to use in reports for the trip period.
; You could declare each time it changed, eg:
; P 2018-10-25 EUR 1.10 USD
; P 2018-10-28 EUR 1.20 USD
; but hledger currently picks just one,
; and for expense reporting a rough average price is usually fine:
P 2018-10-25  EUR  1.15 USD

Here are a few different reports, for comparison:

Simple balance change report for all accounts. --flat and -Y help ensure a readable tabular layout here.

$ hledger bal --flat -Y
Balance changes in 2018:

                                  ||                 2018 
 Assets:Bank                      ||          -590.00 USD 
 Expenses:Fees:CurrencyConversion ||            10.00 USD 
 Expenses:Vacation:Food           ||              190 EUR 
 Expenses:Vacation:Hotel          ||              310 EUR 
                                  || 500 EUR, -580.00 USD 

Adding the -B/--cost flag converts transaction amounts to the other commodity in the transaction, using the conversion rate specified in the transaction if any. This typically helps collapse the grand total to one commodity, so we can see it is zero here (expected, since we're showing all accounts).

$ hledger bal --flat -Y -B
Balance changes in 2018:

                                  ||                 2018 
 Assets:Bank                      ||          -590.00 USD 
 Assets:Cash                      || -500 EUR, 580.00 USD 
 Expenses:Fees:CurrencyConversion ||            10.00 USD 
 Expenses:Vacation:Food           ||              190 EUR 
 Expenses:Vacation:Hotel          ||              310 EUR 
                                  ||                    0 

Adding the -V/--value flag instead converts report amounts using the market price effective on the reporting date (hledger prices and date can help identify that). The grand total of -5 USD here corresponds to our capital loss due to change in exchange rate (the price of a euro went from $1.10 to $1.20 while we still owed some):

$ hledger prices 
P 2018-10-25 EUR 1.15 USD
$ date
Fri Oct 26 15:03:00 PDT 2018
$ hledger bal --flat -Y -V
Balance changes in 2018:

                                  ||        2018 
 Assets:Bank                      || -590.00 USD 
 Expenses:Fees:CurrencyConversion ||   10.00 USD 
 Expenses:Vacation:Food           ||  218.50 USD 
 Expenses:Vacation:Hotel          ||  356.50 USD 
                                  ||   -5.00 USD 

The "exp" account query is added to show just the expenses. Now we can see their total.

$ hledger bal --flat -Y -V exp
Balance changes in 2018:

                                  ||       2018 
 Expenses:Fees:CurrencyConversion ||  10.00 USD 
 Expenses:Vacation:Food           || 218.50 USD 
 Expenses:Vacation:Hotel          || 356.50 USD 
                                  || 585.00 USD 

Or you might use the is/incomestatement command which is specialised for income/expense reporting. It's tabular and flat by default.

$ hledger is -V
Income Statement 2018/10/25-2018/10/28

                                  || 2018/10/25-2018/10/28 
 Revenues                         ||                       
 Expenses                         ||                       
 Expenses:Fees:CurrencyConversion ||             10.00 USD 
 Expenses:Vacation:Food           ||            218.50 USD 
 Expenses:Vacation:Hotel          ||            356.50 USD 
                                  ||            585.00 USD 
 Net:                             ||           -585.00 USD 

Borrowing and Lending

Lending, calculating interest manually

0.41% interest per month (roughly equivalent to 5% APR), calculated manually:

2020-01-01 opening balances
  assets:bank:checking   1000
  equity:opening/closing balances

2020-01-01 lend to Trusty Tara
  assets:receivable:tt    100
2020-02-01 charge 5% interest
  assets:receivable:tt      0.41   ; 100 x 0.41

2020-02-15 Tara payment
  assets:receivable:tt    -50

2020-03-01 charge 5% interest
  assets:receivable:tt      0.21   ; 50.41 x 0.41, rounded

2020-03-15 Tara payment
  assets:receivable:tt    -50

Monthly balance sheet:

$ hledger bs -M
Balance Sheet 2020-01-31,,2020-03-31

                      || 2020-01-31  2020-02-29  2020-03-31 
 Assets               ||                                    
 assets:bank:checking ||     900.00      950.00     1000.00 
 assets:receivable:tt ||     100.00       50.41        0.62 
                      ||    1000.00     1000.41     1000.62 
 Liabilities          ||                                    
 Net:                 ||    1000.00     1000.41     1000.62 

Lending, calculating interest with hledger-interest

Loan and payment transactions are in the main journal:

2020-01-01 opening balances
  assets:bank:checking   1000.00
  equity:opening/closing balances

2020-01-01 lend to Trusty Tara
  assets:receivable:tt    100 = 100
2020-02-15 Tara payment
  assets:receivable:tt    -50

2020-03-15 Tara payment
  assets:receivable:tt    -50

We use hledger-interest to add interest transactions, here 5% per year:

$ hledger-interest assets:receivable:tt --act --annual=0.05 -s revenues:interest:tt -t assets:receivable:tt 
2020-01-01 lend to Trusty Tara
    assets:bank:checking         -100.00
    assets:receivable:tt          100.00 = 100.00

2020-02-15 5% interest for 100.00 over 46 days
    assets:receivable:tt            0.63
    revenues:interest:tt           -0.63

2020-02-15 Tara payment
    assets:receivable:tt          -50.00
    assets:bank:checking           50.00

2020-03-15 5% interest for 50.63 over 29 days
    assets:receivable:tt            0.20
    revenues:interest:tt           -0.20

2020-03-15 Tara payment
    assets:receivable:tt          -50.00
    assets:bank:checking           50.00

It doesn't print the opening balance transaction for some reason. So we'll print that too, then get a monthly balance sheet:

$ (hledger print desc:opening; hledger-interest assets:receivable:tt --act --annual=0.05 -s revenues:interest:tt -t assets:receivable:tt) | hledger -f- bs -M
Balance Sheet 2020-01-31,,2020-03-31

                      || 2020-01-31  2020-02-29  2020-03-31 
 Assets               ||                                    
 assets:bank:checking ||     900.00      950.00     1000.00 
 assets:receivable:tt ||     100.00       50.63        0.83 
                      ||    1000.00     1000.63     1000.83 
 Liabilities          ||                                    
 Net:                 ||    1000.00     1000.63     1000.83 


Freelancers and businesses send invoices to clients to request payment. You can use hledger to track how much is due, and how much has been paid.

See also:

Journal entries

Accrual accounting

Businesses use accrual accounting, where you record revenue (and the tax event) at the time of invoicing, and you keep track of what is receivable (awaiting payment).

; send an invoice:
2020-02-01 * (201602ab) AB Inc. | invoice
    revenues:consulting:ab     $-1000
    assets:receivable:ab        $1000

; receive payment:
2020-02-15 * AB Inc. | payment for 201602ab
    assets:receivable:ab       $-1000  ; = $0  ; assert this if fully paid (optional)
    assets:checking             $1000

Cash accounting

Cash accounting is simpler; you record revenue at the time of receiving the money. Invoices and receivables aren't part of cash accounting, but we'll use unbalanced postings to track them anyway.

; send an invoice:
2020-02-01 * (201602ab) AB Inc. | invoice
    (assets:receivable:ab)      $1000

; receive payment:
2020-02-15 * AB Inc. | payment for 201602ab
    (assets:receivable:ab)     $-1000  ; = $0  ; assert this if fully paid (optional)
    revenues:consulting:ab     $-1000
    assets:checking             $1000


What invoices have been sent and are not yet paid ? Using either example above (assuming the payment transaction is commented out):

$ hledger bal receivable
               $1000  assets:receivable:ab

Creating Invoices

How to translate the data from your ledger into a professional-looking invoice you can send to clients ?

You can create the invoice manually or semi-manually, eg using a tool like Freshbooks, and copy-paste the numbers in.

Or you can automate this somehow. There's no ready-made tool for this, you will have to build it yourself.

With pandoc

But here's a method I use to semi-automate the invoice generation. It still requires some manual work, but much less than otherwise. The workflow is:

  1. figure out the amount to be invoiced, eg from a timelog report
  2. use the makefile below to help create the invoice
  3. adjust the invoice transaction which it added to the journal.

This is based on Generate PDF invoices from Markdown using Pandoc by Martin Betz, see that page for more background. Some version of the files below can also be found in examples/invoicing/. [Copy-pasted from a working setup, not regularly tested, expect to adapt this].

# Makefile
# Generates HTML/PDF invoices from a markdown template

	@echo "make copy     # copy last invoice to YYYYMMab.md, open in emacs"
	@echo "(edit)"
	@echo "make invoice  # make YYYYMMab.pdf and YYYYMMab.html, git commit, add invoice transaction to journal"

LAST=`ls -t 2*md | head -1`
# must end with "ab":
NEW=$(shell date +%Y%mab)

	@echo "copying invoice $(LAST) to $(strip $(NEW)).md"
	@cp $(LAST) $(strip $(NEW)).md
	emacsclient -s2 -n $(strip $(NEW)).md

	@echo "making invoice $(NEW)"
	@make $(NEW)
	@git add $(NEW).{md,html,pdf}
	@git commit -m "invoice $(NEW)" -- $(NEW).{md,html,pdf}
	@printf "`date +%Y-%m-%d` * (`date +%Y%m`ab) AB Inc. | invoice\n    (assets:receivable:ab)                 \$$1000  ; TODO:adjust\n\n" >>$$LEDGER_FILE

	@make [email protected] [email protected]

%.pdf: %.md $(CSS) #logo.jpg
	pandoc $< -t html5 --css $(CSS) -o [email protected] --metadata title=" "
# --metadata title to silence warning, space to avoid unwanted display

	ls | entr make -s $*.pdf

%.html: %.md $(CSS) #logo.jpg
	pandoc $< -t html5 -s --css $(CSS) -o [email protected] --metadata title=" "

	ls | entr make -s $*.html
/* invoice.css */

@charset "utf-8";

body {
/* font-size: 10.5pt; */
/* font-family:  */
/*     "Avenir Next", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", */
/*     "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; */
hyphens: auto;
height: 250mm; /* 280 - 10 (top) - 20 (bottom) */
line-height: 140%;
margin: 0;
padding: 0;

code {
font-family: "Source Sans Code", Courier New, Courier, monospace;
margin-left: 1pt;
/* font-size:12pt; */

a {
color: black;
margin-left: 1pt;

h1 {
font-size: 20pt;
margin-top: 10mm;
/* margin-top: 6pt; */
/* margin-bottom: 0; */

h2 {
font-size: 16pt;
/* margin-top: 20pt; */
margin-top: 10mm;
/* font-weight: normal; */
/* margin-top: 0; */
/* margin-bottom: 20pt; */

p {
width: 100%;

p:first-of-type {
text-align: center;
font-size: 9pt;
word-spacing: 1pt;

p:nth-of-type(2) {
margin-top: 10mm;

p:nth-of-type(3) {
text-align: center;


/* p:nth-last-of-type(3) { */
/* margin-top: 10mm; */
/* } */

/* p:last-of-type { */
/* text-align: center; */
/* font-size: 9pt; */
/* position: absolute; */
/* bottom: 2mm; */
/* margin-bottom: 0; */
/* padding-bottom: 0; */
/* color: #444; */
/* } */

table {
width: 100%;

table:nth-of-type(1) {
border: 1px solid black;
padding: 5pt;

table:nth-of-type(1) td {
border-top: 1px solid #eee;

table:nth-of-type(1) tr:nth-last-of-type(1) {
font-weight: bold;
table:nth-of-type(1) tr:nth-last-of-type(1) td {
/* border-top: 1px solid black; */
padding-top: 1em;

/* table:nth-of-type(1) td:nth-of-type(2) { */
/* text-align: center; */
/* } */

hr {
border: 1px solid #eee;

hr:last-of-type {
position: absolute;
bottom: 14mm;
width: 100%;

figure {
margin: 0;

A sample invoice source file, 202001ab.md:

papersize: letter
margin-left: 20mm
margin-right: 25mm
margin-top: 20mm
margin-bottom: 20mm

![](logo.jpg){ width=100mm }\
Joe Consultant | +1 (111) 111 1111 | [email protected] | 500 Done Dr. #1, Work Ville, CA 10000, USA

Carl Client\
AB Inc.\
PO Box 11111\
CA 20000\

January 1, 2020

# Invoice 202001ab

| Description                                 |   Rate | Qty |  Total |
| Custom software development & maintenance   | $  100 |  10 | $ 1000 |
| Systems reliability engineering             | $ 2000 |   - | $ 2000 |
| On-call monitoring & tech support           | $ 2000 |   - | $ 2000 |
| Contractor/vendor management                | $  500 |   - | $  500 |
| Expenses (service providers, tools, dues..) |        |     | $    0 |
| &nbsp;                                      |        |     |        |
| Total due                                   |        |     | $ 5500 |
|                                             |        |     |        |

## Terms

Now due. Your prompt payments are appreciated, thank you!

Your text editor probably can make editing markdown convenient. For example in Emacs with markdown-mode:

  • press TAB while in the table to realign it
  • to sum the Totals, select them from top left to bottom right, C-x * :
  • alternatively, you could probably make a nice yasnippet template for generating the markdown

Multicurrency tutorial

Anya begins using hledger without any currency symbols. She adds some journal entries like this (not bothering with descriptions, either):

  assets:bank          1000
  expenses:food         500

She knows hledger is filling in the missing amounts, which can be seen with print's -x/--explicit flag:

$ hledger print -x
    income:gifts            -1000
    assets:bank              1000

    assets:bank              -500
    expenses:food             500

The balance command with no arguments shows all balance changes. The total is zero, as Anya expects - each transaction sums to zero, and all transactions are included in this report, so the report also sums to zero:

$ hledger bal
                 500  assets:bank
                 500  expenses:food
               -1000  income:gifts

Unlike partial balance reports (omitting some accounts), which typically do not have a zero total:

$ hledger bal food
                 500  expenses:food

Anya maintains a popular free software project. She remembers that she added a Liberapay button to the project website yesterday, allowing donations. Her native currency is rubles, but Liberapay pays out US dollars or euros.

She realises she had better start tracking currencies in her journal or things will get confusing. So she adds currency symbols throughout her journal:

  assets:bank         ₽1000
  expenses:food        ₽500

Thinking ahead, she sees that entering euro symbols will be a bit unergonomic on her keyboard. She thinks perhaps she'll use standard alphabetic currency codes instead, and on the right-hand side:

  assets:bank          1000 RUB
  expenses:food         500 RUB

But she finds this a bit verbose. She decides to use single letters - R for rubles:

  assets:bank          1000 R
  expenses:food         500 R

Now her reports show the currency symbol:

$ hledger bal 
               500 R  assets:bank
               500 R  expenses:food
             -1000 R  income:gifts

And she is ready for multicurrency accounting. Just in time, because next day a donation of 10 euros arrives! She records it, using E for euros:

  assets:bank          1000 R
  expenses:food         500 R

  assets:liberapay       10 E

Now she has a multicurrency journal, and the balance report shows both currencies:

$ hledger bal 
                10 E        
               500 R  assets
               500 R    bank
                10 E    liberapay
               500 R  expenses:food
               -10 E        
             -1000 R  income
               -10 E    foss
             -1000 R    gifts

However, it's a bit confusing. The assets and income parent accounts now have multicurrency balances, and each currency is displayed on its own line. She tries flat mode, and finds it clearer:

$ hledger bal --flat
               500 R  assets:bank
                10 E  assets:liberapay
               500 R  expenses:food
               -10 E  income:foss
             -1000 R  income:gifts

But she has heard that hledger's tabular output is best for multicurrency reports, always showing amounts on one line. She starts using that, adding one of the report interval flags (-Y/--yearly) to activate it:

$ hledger bal -Y
Balance changes in 2018:

                  ||    2018 
 assets:bank      ||   500 R 
 assets:liberapay ||    10 E 
 expenses:food    ||   500 R 
 income:foss      ||   -10 E 
 income:gifts     || -1000 R 
                  ||       0 

Anya requests a withdrawal of the Liberapay funds to her bank. Her bank holds rubles, so the euros will get converted. She's not sure of the exact exchange rate or fees, but next day, when the transaction clears, she can see that 10 euros left her liberapay account and 750 rubles arrived in her bank account. She decides to just record that:

  assets:bank          1000 R
  expenses:food         500 R

  assets:liberapay       10 E

  assets:liberapay      -10 E
  assets:bank           750 R

This is her first multicurrency transaction. She hasn't written the exchange rate explicitly, but the manual says hledger can figure it out. It seems to work:

$ hledger bal  -Y
Balance changes in 2018:

               ||         2018 
 assets:bank   ||       1250 R 
 expenses:food ||        500 R 
 income:foss   ||        -10 E 
 income:gifts  ||      -1000 R 
               || -10 E, 750 R 

However, two things surprise her. First, where has the liberapay account gone ? She remembers that balance reports hide zero-balance accounts by default, and adds -E/--empty to show it. (She also notes that zero amounts are displayed without a currency symbol, and would be a little clearer with currency symbols on the left):

$ hledger bal  -YE
Balance changes in 2018:

                  ||         2018 
 assets:bank      ||       1250 R 
 assets:liberapay ||            0 
 expenses:food    ||        500 R 
 income:foss      ||        -10 E 
 income:gifts     ||      -1000 R 
                  || -10 E, 750 R 

Second, the balance report is now showing a non-zero total. The individual euro and ruble totals look correct, but why isn't it zero ? Is the journal unbalanced ?

Anya asks for help on the #hledger IRC channel and is advised to add the -B/--cost flag. Sure enough, the total is now zero:

$ hledger bal -YEB
Balance changes in 2018:

                  ||         2018 
 assets:bank      ||       1250 R 
 assets:liberapay || 10 E, -750 R 
 expenses:food    ||        500 R 
 income:foss      ||        -10 E 
 income:gifts     ||      -1000 R 
                  ||            0 

But now the liberapay account, which should be empty, is showing a positive euro and negative ruble balance. As if one had not been converted into the other. Why is this ?

With a little help, Anya goes troubleshooting. Inspecting the multicurrency transaction with print -x (and a date filter to exclude the rest) shows how hledger has parsed it:

$ hledger print -x date:20181104
    assets:liberapay    -10 E @@ 750 R
    assets:bank                  750 R

The manual makes this a bit clearer. Anya wrote the entry in transaction prices style 3 ("let hledger infer the price that balances the transaction"). hledger has converted this to style 2 ("@@ TOTALPRICE after the amount"), recording that the 10 euro were priced at 750 rubles in this transaction.

With -B added, the 10 euro is converted to its cost in rubles:

$ hledger print -x date:20181104 -B
    assets:liberapay          -750 R
    assets:bank                750 R

The register command shows how the balance reports above calculate the liberapay balance. Without -B: 10 euro are added, 10 euro are removed, the liberapay account's end balance is zero:

$ hledger reg liberapay
2018/11/03                     assets:liberapay               10 E          10 E
2018/11/04                     assets:liberapay              -10 E             0

With -B: 10 euro are added, 750 rubles are removed, the liberapay account's end balance is "10 euro, -750 rubles". (With each currency on its own line, again. Also, it seems that register aligns the account name with the top amount, unlike the balance command):

$ hledger reg liberapay -B
2018/11/03                      assets:liberapay              10 E          10 E
2018/11/04                      assets:liberapay            -750 R          10 E
                                                                          -750 R

In summary, it seems that the balance report must sum either the primary posting amounts (bal), or the cost amounts (bal -B), consistently for both the account balances above the line, and the total below the line. Otherwise the total would be incorrect. Which means that one or the other of these will be displayed as an unconverted multicurrency amount.

Anya decides to find out more about the other currency-related flag: -V.


  • declaring a market price corresponding to the price in the fourth transaction ( P 2018/11/01 E 75 R ) and adding -V will show everything completely in rubles (with or without -B, at least in this case), preserving the zero total

  • declaring an accurate market price instead ( P 2018/11/01 E 74.91 R ), there will be a small non zero total, which corresponds to the gain/loss due to exchanging at a slightly different price. After adding an explicit gain/loss transaction, the zero total is restored.

  • The new -X and --value options.

Project accounting

Some ways to track small business/freelancer activity - orders, budgets, invoices, payments..

Accrual method

Revenue is declared when work is performed:

; budget:* - virtual accounts tracking what customers have committed
; to pay for various things. Should not go below 0.
2017/10/30 Order from CUSTOMER (order id) 
    (budget:CUSTOMER:PROJECT_ID:pos1)                       1000
    (budget:CUSTOMER:PROJECT_ID:pos2)                       3000
; some work was done on pos1 and pos2, invoice for it.
; Using accrual accounting method
; (revenue is declared when work is done, ~= when invoiced)
2017/10/31 Invoice (invoice id) - (PROJECT_ID)
    (budget:CUSTOMER:PROJECT_ID:pos1)                       -500  ; update project budget
    (budget:CUSTOMER:PROJECT_ID:pos2)                      -1000
    assets:receivable:CUSTOMER:PROJECT_ID:pos1               500
    assets:receivable:CUSTOMER:PROJECT_ID:pos2              1000
    (liabilities:tax:federal)                               -150  ; note tax due, eg 15% of revenue

; a customer payment is received
2017/11/15 Payment for INVOICE_ID
    assets:receivable:CUSTOMER:PROJECT_ID:pos1              -500
    assets:receivable:CUSTOMER:PROJECT_ID:pos2             -1000

; make a tax payment
2018/4/15 Pay taxes due from 2017
    liabilities:tax:federal                                 5000

Cash method

Revenue is declared when payment is received:

2017/10/30 Order from CUSTOMER (order id) 
    (budget:CUSTOMER:PROJECT_ID:pos1)                       1000
    (budget:CUSTOMER:PROJECT_ID:pos2)                       3000

; record an invoice sent. Not a real transaction in cash accounting,
; but we can balance it with the project budget as shown:
2017/10/31 Invoice (invoice id) - (PROJECT_ID)
    budget:CUSTOMER:PROJECT_ID:pos1                         -500
    assets:receivable:CUSTOMER:PROJECT_ID:pos1               500
    budget:CUSTOMER:PROJECT_ID:pos2                        -1000
    assets:receivable:CUSTOMER:PROJECT_ID:pos2              1000

; receive payment. Cash basis, so revenue declared here.
2017/11/15 Payment for INVOICE_ID
    (assets:receivable:CUSTOMER:PROJECT_ID:pos1)            -500
    (assets:receivable:CUSTOMER:PROJECT_ID:pos2)           -1000
    revenues:CUSTOMER                                      -1500
    (liabilities:tax:federal)                               -150  ; note tax due, eg 15% of revenue

; make a tax payment
2018/4/15 Pay taxes due from 2017
    liabilities:tax:federal                                 5000

Time planning

Simon's hledger time dashboard 2018/05

Here's how I have been logging time for a few years, and showing budget reports for a few weeks.

I have four files:

  1. time-2018.timedot is the current year's time log. It contains daily entries in timedot-ish format, like:

    adm.email             20m
    inc.client1.enh.1342  .... .
    inc.client1.enh.1188  .
    inc.client1.enh.1335  ..
  2. time.journal is in journal format so it can include multiple timedot file(s) and provide an account alias allowing period instead of colon in account names:

    ; allow . as subaccount separator in timedot files
    alias /\./=:
    ;include time-2016.timedot
    ;include time-2017.timedot
    include time-2018.timedot
  3. time-daily.budget defines some daily goals, optionally date-bounded, using periodic transaction(s):

    ~ daily  ; [from Y/M/D] [to Y/M/D]
        (adm)    1  ; your goals here
        (inc)    1
        (fos)    1
  4. time-weekly.budget defines some weekly goals. I like to set these independently of the above, which in current hledger means they must be in a separate file:

    ~ weekly  ; [from Y/M/D] [to Y/M/D]
        (adm)    1
        (biz)    1
        (inc)    1
        (fos)    1

The monthly budget reuses the weekly goals.

I have an iTerm2 Hotkey Window (a terminal that drops down on ALT-space) with six panes:

  1. a bash script showing latest laptop wake/sleep and timelog save times, as a memory aid for time logging:


    (Here's that script, useful only to mac users; please show me something better!):

     # record timelog update times
     export TIMELOG=$HOME/time-2018.timedot
     alias timelogsaved="ls -lT $TIMELOG | cut -d' ' -f8-11"
     alias timelogaccessed="ls -lTu $TIMELOG | cut -d' ' -f8-11"
     alias timelogcreated="ls -lTU $TIMELOG | cut -d' ' -f8-11"
     # show mac sleep/wake & display on/off  events
     alias wakelog="pmset -g log | egrep -E '((Sleep|Wake) +\t|Display is)' | sed -E \
           -e 's/(Notification|Sleep|Wake) *	//' \
           -e 's/is turned //' \
           -e 's/Entering Sleep state/Sleep/' \
     # show recent wakeup/timelog save times to help with time logging, clipped to screen width
     # The width clipping is to help watch display this in dashboard.
     function tlog()
       ( wakelog | tail -$LINES
         printf " \n"
         printf "$(timelogcreated) timelog created\n"
         printf "$(timelogsaved) timelog saved\n"
         printf "$(timelogaccessed) timelog accessed\n"
       ) | cut -c-$(expr $COLUMNS - 1)
     # run a brief tlog report periodically, passing any args to watch
     function tlogwatch()
       watch -t -n60 [email protected] "bash -ic 'tlog '$LINES"
     # TODO why does "tlogwatch 10" give "sh: 10: command not found" ?
  2. a text-mode emacs for updating the time log:

    emacs -nw time-2018.timedot
  3. -5: updating (using entr) time budget reports for the current day/week/month, using hledger 1.9.1+:

    ls time.journal time-2018.timedot time-daily.budget  | entr sh -c 'clear; hledger -f time.journal -f time-daily.budget  bal --budget -1 -D date:today-tomorrow'
   ls time.journal time-2018.timedot time-weekly.budget | entr sh -c 'clear; hledger -f time.journal -f time-weekly.budget bal --budget -1 -W date:thisweek-nextweek'
   ls time.journal time-2018.timedot time-weekly.budget | entr sh -c 'clear; hledger -f time.journal -f time-weekly.budget bal --budget -1 -M date:thismonth-nextmonth'
  1. an updating hledger-ui for exploring time usage (shift-up/down to resize period, shift-left/right to step through time, t to return to today):

    hledger-ui --watch --change date:today -f time.journal

Track investments

You can use hledger to track stock investments. In fact, the double-entry accounting is flexible enough to support most constellations you will come across. However, you may find that some transactions could be better supported. Caveats are:

  • hledger does not validate the cost basis during a sale.
  • historical mark-to-market performance is not supported (but the market value at one instant, like today, can be calculated)


Buying a stock

Let's go over a simple example using transaction prices:

2017/1/1 opening balance
  (assets:depot)  $3000

2017/1/2 buy shares at $200
  ; let's assume no fees
  assets:shares   10 TSLA @ $200  ; transaction/purchase price

Some reports. We start with $3000. After the 1/2 purchase, we have $1000 remaining and 10 TSLA shares:

$ hledger -f t.j bal --flat -HD
Ending balances (historical) in 2017/01/01-2017/01/02:

               ||  2017/01/01     2017/01/02
 assets:depot  ||       $3000          $1000
 assets:shares ||           0        10 TSLA
               ||       $3000 $1000, 10 TSLA

Show the shares' value at cost, with -B/--cost:

$ hledger -f t.j bal --flat -HD -B
Ending balances (historical) in 2017/01/01-2017/01/02:

               ||  2017/01/01  2017/01/02
 assets:depot  ||       $3000       $1000
 assets:shares ||           0       $2000
               ||       $3000       $3000

Value reporting

Add the following to the journal file.

; market price, has jumped since yesterday's purchase!
P 2017/1/3 TSLA $250

Show the shares's value using the latest applicable market price, with -V/--value. A $500 capital gain is apparent in the totals:

$ hledger -f t.j bal --flat -HD -V
Ending balances (historical) in 2017/01/01-2017/01/02:

               ||  2017/01/01  2017/01/02
 assets:depot  ||       $3000       $1000
 assets:shares ||           0       $2500
               ||       $3000       $3500

There are still limitations in the value reporting that hledger can currently do. More information can be found in Github issue #131 and Github issue #329.

You may want to investigate the output after adding more prices to the journal file.

P 2017/1/1 TSLA $210
P 2017/1/4 TSLA $250
P 2017/1/8 TSLA $270

Selling a stock and tracking capital gains

At some point you will probably sell shares. It may seem intuitive to model such a sale as follows.

2017/1/4 sell shares at $250      ; NOTE: You probably want to model capital gains too; see below
  assets:shares   -10 TSLA @ $250  ; sell price

This leads to the following evolution

hledger -f t.j balance --flat -HD
Ending balances (historical) in 2017/01/01-2017/01/04:

               ||  2017/01/01     2017/01/02     2017/01/03  2017/01/04
 assets:depot  ||       $3000          $1000          $1000       $3500
 assets:shares ||           0        10 TSLA        10 TSLA           0
               ||       $3000 $1000, 10 TSLA $1000, 10 TSLA       $3500

You end up with the correct amount in your depot. At some point, however, you will have to report the capital gain that you realized with your sale. This gain is currently invisible. In fact, we have violated the double-entry principle and created money out of nowhere.

Let's report our sale in a different way.

2017/1/4 sell shares at $250
  assets:shares         -10 TSLA @ $200  ; cost basis (must be tracked by user!)
  assets:depot          $2500            ; cash proceeds
  revenue:capital_gains                  ; deduce profit

Now, the new $500 are correctly balanced with the capital gains account.

hledger -f t.j balance --flat -HD
Ending balances (historical) in 2017/01/01-2017/01/04:

                       ||  2017/01/01     2017/01/02     2017/01/03  2017/01/04
 assets:depot          ||       $3000          $1000          $1000       $3500
 assets:shares         ||           0        10 TSLA        10 TSLA           0
 revenue:capital_gains ||           0              0              0       $-500
                       ||       $3000 $1000, 10 TSLA $1000, 10 TSLA       $3000

Further reading

Track investments 2

0.1 (first draft), 2020-04

Here's a tutorial on tracking "investments" - stocks, cryptocurrencies, and similar - in hledger. This is a more in-depth version of Tracking investments, using hledger 1.17; older hledger versions may not match this doc. I hope to teach you a little basic investment accounting, or a little about doing it with hledger (and other PTA tools), or a little of both. Notably, you'll see three different ways to record lots and capital gains:

  1. using {} notation (works in Ledger, Beancount)

  2. using just @ notation (works in hledger, Ledger, Beancount..)

  3. using just standard postings (works in any double-entry accounting system).

A stock purchase

We'll use a cryptocurrency and fictitious prices here, but these examples apply equally well to stocks. Let's say we start the year with $1000 for investment:

2020-01-01 opening balances
    assets:bank:checking             $1000
    equity:opening/closing balances

In february we see ADA is priced at $0.02, and decide to buy a little. We'll spend $40, which at 2 cents per ADA buys 2000 ADA. Here's the most straightforward journal entry:

2020-02-01 buy ada
    assets:cc:ada          2000 ADA
    assets:bank:checking   -$40

We paid $40 (from one asset) and received 2000 ADA in exchange (another asset). The exact account names don't matter; here we've used a cc subaccount to group all cryptocurrencies, and an additional ada subaccount to distinguish ADA holdings from other cryptocurrencies (this can be helpful in some reports.)

In real life there'll probably be another posting or two, for transaction expenses. We'll omit those for now.

There are a few more things to say about this entry...

How does that balance ?

As you may know, in hledger and other PTA tools the primary rule is:

  • "Each transaction must balance - its amounts must add up to zero".

Well, in hledger (and Ledger), there's another rule:

  • "When a transaction's unbalanced amounts involve exactly two commodities, assume one is converted to the other with a conversion rate that makes the transaction balance."

We can use hledger print -x (x for explicit) to show the above with all amounts and the inferred conversion price:

$ hledger print -x
    assets:cc:ada           2000 ADA @@ $40
    assets:bank:checking    $-40

This @ notation has two forms: @@ TOTALPRICE or @ UNITPRICE. In hledger we call it the "transaction price", because it records the price/conversion rate that was used in this specific transaction:

Note this more explicit journal entry has some redundancy, but that's no harm - it makes things clearer to the human reader, it provides an extra test of the tool's rounding and precision, and it helps guard against typos. So we'll use this one.

But here are two more ways of writing the above, just for completeness:

    assets:cc:ada          2000 ADA @@ $40
    assets:bank:checking                   ; $-40 inferred here
    assets:cc:ada          2000 ADA @ $0.02
    assets:bank:checking                    ; -(2000 x $0.02) = $-40 inferred here

A more correct entry

Do you find this still wrong ? Some amount of dollars seems to disappear, and some amount of ADA appears. Doesn't this violate the accounting equation, the foundation of double entry bookkeeping, which essentially says "money is not created out of thin air" ? #1177 agrees with you; in summary, it seems the correct entry for this transaction is more like:

    assets:cc:ada          2000 ADA
    equity:conversion     -2000 ADA
    equity:conversion       $40
    assets:bank:checking   -$40

This entry might look a little strange to you, but it does show more clearly that the transaction is balanced; it gathers commodity conversions into a single "conversion" account which can provide useful information; and it preserves the accounting equation. The "conversion" account name isn't special, but it's an equity account. Another way to write it, relying on automatic transaction balancing, is:

    assets:cc:ada          2000 ADA
    assets:bank:checking   -$40
    equity:conversion                 ; -2000 ADA, +$40 inferred here

There's a problem, however: hledger (and other PTA tools) do not recognise these more general forms as a commodity conversion, so will not be able to show cost reports (with -B, at least..). For this reason, and considering that the unbalanced accounting equation often does not affect your everyday reports, you might prefer to stick with the @ notation.

What about lots ?

If you know a little about investing, or have read Ledger or Beancount docs, you'll know that it's important to track the date and cost of each stock purchase, or lot. This information is needed:

  • While holding investments, to calculate unrealised capital gains/losses, ie the change in their value "on paper" as market prices fluctuate.

  • When selling an investment, so that:

    1. We can select the right lots to sell. Tax law may require, eg, selling the oldest lots first (the FIFO strategy - First In, First Out).

    2. We can calculate the realised capital gain/loss from this sale, by comparing the selling price with the original cost (AKA cost basis) of those lots.

Ledger and Beancount provide a special syntax and some builtin reports for tracking lots and calculating capital gains. Currently, hledger does not (aside from a little support for lot syntax). So for now, how can we track lots in hledger ?

We can use the obvious categorisation feature: accounts. We'll give every lot its own uniquely-named subaccount. Since we already have an account just for ADA, we'll just name the lot subaccounts by the lots' purchase dates. If doing multiple ADA purchases per day, we could add a sequence number. We could also include the cost in the name, if we want extra clarity.

So, let's amend the above journal entry, adding the 20200201 subaccount to represent this first lot:

    assets:cc:ada:20200201    2000 ADA @@ $40
    assets:bank:checking      $-40


So far, what have we got ? The journal is :

2020-01-01 opening balances
    assets:bank:checking                       $1000
    equity:opening/closing balances

    assets:cc:ada:20200201                      2000 ADA @@ $40
    assets:bank:checking                        $-40

The balance sheet (truncated for brevity) shows assets are $960 and 2000 ADA:

$ hledger bs --flat | head -10
Balance Sheet 2020-02-01

                        ||     2020-02-01 
 Assets                 ||                
 assets:bank:checking   ||           $960 
 assets:cc:ada:20200201 ||       2000 ADA 
                        || $960, 2000 ADA 

(| head -10 is used here just to hide the empty Liabilities section. You can omit it if it doesn't work on your system.)

And with -B/--cost (B for cost basis) we see costs so far are:

$ hledger bs --flat -B |head -10
Balance Sheet 2020-02-01, valued at cost

                        || 2020-02-01 
 Assets                 ||            
 assets:bank:checking   ||       $960 
 assets:cc:ada:20200201 ||        $40 
                        ||      $1000 

Market prices

We'll also start recording the prevailing market price that was in effect at each point in time. This will allow us to report on the market value of our investments.

We don't need to know it every day or every hour; for this example, we'll just record it at the start of each month. hledger will assume that market price through the month, until the next one is declared.

Here's the P (market Price) directive declaring that the market price of ADA, in dollars, on 2020-02-01, was $0.02:

P 2020-02-01 ADA $0.02

And let's say that ADA's price has doubled by march 1st; we'll record that too:

P 2020-03-01 ADA $0.04

We'll add these to the main journal for simplicity. Or if you prefer you can keep them in a separate file, eg 2020.prices, adding include 2020.prices to the main journal.

Note that the initial $0.02 market price, and the $0.02 transaction price we recorded above, are the same in this example. In real life they may not be exactly the same, but normally they will be quite similar.

Value reports; some gotchas

Now we have enough data to do a little value reporting. This can be a little confusing at first, so here are a few examples.

Our journal so far is:

2020-01-01 opening balances
    assets:bank:checking                       $1000
    equity:opening/closing balances

    assets:cc:ada:20200201                      2000 ADA @@ $40
    assets:bank:checking                        $-40

P 2020-02-01 ADA $0.02
P 2020-03-01 ADA $0.04

Let's check the current market value (AKA mark to market) of our holdings. -V is a simple form of the --value flag:

$ hledger bs --flat -V |head -10
Balance Sheet 2020-02-01, current value

                        || 2020-02-01 
 Assets                 ||            
 assets:bank:checking   ||    $960.00 
 assets:cc:ada:20200201 ||     $80.00 
                        ||   $1040.00 

But the ADA market value looks wrong - on 2020-02-01 it was $40, not $80. So it's wise to check the manual. In particular, note: "For single period reports, the valuation date is today (equivalent to --value=now)". So even though the report's end date is 2020-02-01 (the date of the last transaction), hledger picked "now" as the valuation date, and therefore used our latest 2020-03-01 P directive.

We can fix this by specifying an explicit report end date, which also sets the valuation date. We'll use -e to specify 2020-02-01:

$ hledger bs --flat -V -e 2020-02-01 |head -10
Balance Sheet 2020-01-31, current value

                      || 2020-01-31 
 Assets               ||            
 assets:bank:checking ||   $1000.00 
                      ||   $1000.00 

Now there's no ADA balance at all - what gives ? Remember that end dates are exclusive, so the ADA purchase on 2020-02-01 is excluded. With a later end date, eg 2020-02-02, we see it:

$ hledger bs --flat -V -e 2020-02-02 |head -10
Balance Sheet 2020-02-01, current value

                        || 2020-02-01 
 Assets                 ||            
 assets:bank:checking   ||    $960.00 
 assets:cc:ada:20200201 ||     $40.00 
                        ||   $1000.00 

And if we specify an end date after the second price directive, we'll see the value at that date:

$ hledger bs --flat -V -e 2020-03-02 |head -10
Balance Sheet 2020-03-01, current value

                        || 2020-03-01 
 Assets                 ||            
 assets:bank:checking   ||    $960.00 
 assets:cc:ada:20200201 ||     $80.00 
                        ||   $1040.00 

Often, a multiperiod, eg monthly, report makes things clearer. For this case the manual says: "valuation date ... for multiperiod reports, it is the last day of each subperiod". For this example we still need to specify the end date though, otherwise the report will stop at the end of the month containing the last transaction (ie, 2020-02-29). This time we'll say -e apr (or -e 202004), which is less typing and includes all of march:

$ hledger bs --flat -M -V -e apr | head -10
Balance Sheet 2020-01-31,,2020-03-31, valued at period ends

                        || 2020-01-31  2020-02-29  2020-03-31 
 Assets                 ||                                    
 assets:bank:checking   ||   $1000.00     $960.00     $960.00 
 assets:cc:ada:20200201 ||          0      $40.00      $80.00 
                        ||   $1000.00    $1000.00    $1040.00 

Finally, we can clearly see the value of our holdings over time. No ADA, just dollars, in january; ADA worth $40 when purchased, in february; and worth $80, thanks to the increase in market price, in march.

Just for comparison, here's the same report but showing cost instead of value. Of course cost is not affected by market prices:

$ hledger bs --flat -M -B -e apr | head -10
Balance Sheet 2020-01-31,,2020-03-31, valued at cost

                        || 2020-01-31  2020-02-29  2020-03-31 
 Assets                 ||                                    
 assets:bank:checking   ||   $1000.00     $960.00     $960.00 
 assets:cc:ada:20200201 ||          0      $40.00      $40.00 
                        ||   $1000.00    $1000.00    $1000.00 

Unrealised capital gain

The difference between the $40 purchase cost of the ADA, and its $80 value in march, is an unrealised capital gain. "Unrealised" (and therefore not yet taxable, typically) because we haven't yet sold the ADA and captured the gain in our base currency.

A sale

The next day, we decide to sell all the ADA, just to test the process and capture a little profit. Assuming that this will look much like the purchase transaction in reverse, using the @ notation again, we come up with this:

2020-03-02 sell all ada
    assets:cc:ada:20200201  -2000 ADA @ $0.04
    assets:bank:checking      $80

For a little extra error checking, this time we used the @ UNITPRICE form, so we can visually check that the per-unit cost looks correct (at or close to the market price).

Here's the new balance sheet, with -E (empty) to make it show the now empty ada account:

$ hledger bs --flat -e apr -E | head -10
Balance Sheet 2020-03-31

                        || 2020-03-31 
 Assets                 ||            
 assets:bank:checking   ||   $1040.00 
 assets:cc:ada:20200201 ||          0 
                        ||   $1040.00 

Realised capital gain

Our dollar balance has increased, from $1000 to $1040, but somewhat magically - there seems to be no transaction causing it. This seems like a bad sign. And indeed a full balance sheet including equity shows a non-zero total, confirming that the Accounting Equation has been disturbed:

$ hledger bse --flat
Balance Sheet With Equity 2020-03-02

                                 || 2020-03-02 
 Assets                          ||            
 assets:bank:checking            ||   $1040.00 
                                 ||   $1040.00 
 Liabilities                     ||            
 Equity                          ||            
 equity:opening/closing balances ||   $1000.00 
                                 ||   $1000.00 
 Net:                            ||     $40.00 

$40 has appeared from somewhere. This increase is the realised capital gain, which is considered a revenue. If we had sold at a lower price than we paid, this number would be negative, representing a capital loss, which is an expense.

We want this gain/loss to be recorded in the journal, to satisfy the accounting equation and keep accurate records, and also because it is typically a taxable event; we'll need to know all of these revenues/expenses when filing taxes.

Recording capital gain

The sale transaction above is balanced, with no room for an extra revenue posting. If we try, hledger complains:

2020-03-02 sell all ada
    assets:cc:ada:20200201                     -2000 ADA @ $0.04 = 0 ADA
    assets:bank:checking                         $80
    revenues:capital gain                       $-40
$ hledger print
could not balance this transaction (real postings are off by $-40.00)

Ledger (and Beancount) will accept an entry like this, if you add a special {} notation identifying the lot's original cost. Below, note the extra {$0.02}, which says "this is a lot, and was purchased at $0.02 each". Ledger will calculate the expected capital gain of $40 and will consider this transaction to be balanced:

    assets:cc:ada:20200201                     -2000 ADA {$0.02} @ $0.04 = 0 ADA
    assets:bank:checking                         $80
    revenues:capital gain                       $-40

But hledger doesn't know about lots or capital gains, as mentioned. (hledger 1.17.99+ will parse the {} notation, but ignores it.) So how can we model a stock sale in hledger ? In general, we should:

  1. convert back to cash using the lot's cost price
  2. manually calculate the capital gain (difference of cost and selling price) and record it as a revenue/expense

If we used the @ notation for the purchase, we should use it here too. The sale looks like this:

2020-03-02 sell ada
    assets:cc:ada:20200201                     -2000 ADA @ $0.02  ; the original cost
    revenues:capital gain                       $-40              ; the capital gain, 2000 x ($0.04-$0.02)
    assets:bank:checking                         $80              

Or if we used the more correct entry for the purchase, ie just standard double entry bookkeeping postings, the sale looks like this:

2020-03-02 sell ada
    assets:cc:ada:20200201                     -2000 ADA
    equity:conversion                           2000 ADA
    equity:conversion                           $-40       ; the original cost
    revenues:capital gain                       $-40       ; the capital gain
    assets:bank:checking                         $80              

If we check the bse report now, we'll still see a $40 total, but this is expected because a revenue has been recorded and not yet merged into equity by "closing the books". If we were to do that temporarily:

2020-03-02 close the books, just for testing
    revenues:capital gain                        $40 = $0
    equity:retained earnings                    -$40

We would see the proper zero total:

$ hledger bse --flat 
Balance Sheet With Equity 2020-03-02

                                 || 2020-03-02 
 Assets                          ||            
 assets:bank:checking            ||   $1040.00 
                                 ||   $1040.00 
 Liabilities                     ||            
 Equity                          ||            
 equity:opening/closing balances ||   $1000.00 
 equity:retained earnings        ||     $40.00 
                                 ||   $1040.00 
 Net:                            ||          0 

A variation: some discussions on the web suggest transferring directly from equity, bypassing revenues. In that case we would need to remember to include it in our tax reports, eg we would need to look at hledger bal revenue expenses 'capital gain' instead of just hledger is.


Using hledger roi to compute return on investment

Cash-only investments

Let's consider the easy case first, where your assets and your investment is the same single commodity (in this case, USD), and whenever value of your investment changes, you record the change manually, balancing it against equity:unrealized gains.

Lets say that we found an investment in Snake Oil that is promising to give us 10% annually:

2019-01-01 Investing in Snake Oil
  assets:cash  -$100
  investment:snake oil

2019-12-24 Recording the growth of Snake Oil
  investment:snake oil   = $110
  equity:unrealized gains

For now, basic computation of the rate of return, as well as IRR and TWR, gives us the expected 10%:

$ hledger roi -Y --inv investment --pnl "unrealized"
|   ||      Begin |        End || Value (begin) | Cashflow | Value (end) | PnL ||    IRR |    TWR |
| 1 || 2019-01-01 | 2019-12-31 ||             0 |     $100 |        $110 | $10 || 10.00% | 10.00% |

However, lets say that shorty after investing in the Snake Oil we started to have second thoughts, so we prompty withdrew $90, leaving only $10 in. Before Christmas, though, we started to get the "fear of missing out", so we put the $90 back in. So for most of the year, our investment was just $10 dollars, and it gave us just $1 in growth:

2019-01-01 Investing in Snake Oil
  assets:cash  -$100
  investment:snake oil

2019-01-02 Buyers remorse
  assets:cash  $90
  investment:snake oil
2019-12-30 Fear of missing out
  assets:cash  -$90
  investment:snake oil

2019-12-31 Recording the growth of Snake Oil
  investment:snake oil   = $101
  equity:unrealized gains

Now IRR and TWR are drastically different:

$ hledger roi -Y --inv investment --pnl "unrealized"
|   ||      Begin |        End || Value (begin) | Cashflow | Value (end) | PnL ||   IRR |   TWR |
| 1 || 2019-01-01 | 2019-12-31 ||             0 |     $100 |        $101 |  $1 || 9.32% | 1.00% |

Here, IRR tells us that we made close to 10% on the $10 dollars that we had in the account most of the time. And TWR is ... just 1%? Why?

Based on the transactions in our journal, TWR "thinks" that we are buying back $90 worth of Snake Oil at the same price that it had at the beginning of the year, and then after that our $100 investment gets $1 increase in value, or 1% of $100. Let's take a closer look at what is happening here by asking for quarterly reports instead of annual:

$ hledger roi -Q --inv investment --pnl "unrealized"
|   ||      Begin |        End || Value (begin) | Cashflow | Value (end) | PnL ||    IRR |   TWR |
| 1 || 2019-01-01 | 2019-03-31 ||             0 |      $10 |         $10 |   0 ||  0.00% | 0.00% |
| 2 || 2019-04-01 | 2019-06-30 ||           $10 |        0 |         $10 |   0 ||  0.00% | 0.00% |
| 3 || 2019-07-01 | 2019-09-30 ||           $10 |        0 |         $10 |   0 ||  0.00% | 0.00% |
| 4 || 2019-10-01 | 2019-12-31 ||           $10 |      $90 |        $101 |  $1 || 37.80% | 4.03% |

Now both IRR and TWR are thrown off by the fact that all of the growth for our investment happens in Q4 2019. Reported rates are annualized, that is IRR computation is still yielding 9.32% and TWR is still 1%, but these rates are computed over three month period instead of twelve, so in order to get an annual rate they should be multiplied by four!

Let's try to keep a better record of how Snake Oil grew in value:

2019-01-01 Investing in Snake Oil
  assets:cash  -$100
  investment:snake oil

2019-01-02 Buyers remorse
  assets:cash  $90
  investment:snake oil

2019-02-28 Recording the growth of Snake Oil
  investment:snake oil  
  equity:unrealized gains  -$0.25

2019-06-30 Recording the growth of Snake Oil
  investment:snake oil  
  equity:unrealized gains  -$0.25

2019-09-30 Recording the growth of Snake Oil
  investment:snake oil  
  equity:unrealized gains  -$0.25

2019-12-30 Fear of missing out
  assets:cash  -$90
  investment:snake oil

2019-12-31 Recording the growth of Snake Oil
  investment:snake oil
  equity:unrealized gains  -$0.25

Would our quarterly report look better now? Almost:

$ hledger roi -Q --inv investment --pnl "unrealized"
|   ||      Begin |        End || Value (begin) | Cashflow | Value (end) |   PnL ||    IRR |    TWR |
| 1 || 2019-01-01 | 2019-03-31 ||             0 |   $10.00 |      $10.25 | $0.25 ||  9.53% | 10.53% |
| 2 || 2019-04-01 | 2019-06-30 ||        $10.25 |        0 |      $10.50 | $0.25 || 10.15% | 10.15% |
| 3 || 2019-07-01 | 2019-09-30 ||        $10.50 |        0 |      $10.75 | $0.25 ||  9.79% |  9.78% |
| 4 || 2019-10-01 | 2019-12-31 ||        $10.75 |   $90.00 |     $101.00 | $0.25 ||  8.05% |  1.00% |

Something is still wrong with TWR computation for Q4, and if you have been paying attention you know what it is already: big $90 buy-back is recorded prior to the only transaction that captures the change of value of Snake Oil that happened in this time period. Lets combine transactions from 30th and 31st of Dec into one:

2019-12-30 Fear of missing out and growth of Snake Oil
  assets:cash  -$90
  investment:snake oil
  equity:unrealized gains  -$0.25

Now growth of investment properly affects its price at the time of buy-back:

$ hledger roi -Q --inv investment --pnl "unrealized"
|   ||      Begin |        End || Value (begin) | Cashflow | Value (end) |   PnL ||    IRR |    TWR |
| 1 || 2019-01-01 | 2019-03-31 ||             0 |   $10.00 |      $10.25 | $0.25 ||  9.53% | 10.53% |
| 2 || 2019-04-01 | 2019-06-30 ||        $10.25 |        0 |      $10.50 | $0.25 || 10.15% | 10.15% |
| 3 || 2019-07-01 | 2019-09-30 ||        $10.50 |        0 |      $10.75 | $0.25 ||  9.79% |  9.78% |
| 4 || 2019-10-01 | 2019-12-31 ||        $10.75 |   $90.00 |     $101.00 | $0.25 ||  8.05% |  9.57% |

And for annual report, TWR now reports the exact profitability of our investment:

$ hledger roi -Y --inv investment --pnl "unrealized"
|   ||      Begin |        End || Value (begin) | Cashflow | Value (end) |   PnL ||   IRR |    TWR |
| 1 || 2019-01-01 | 2019-12-31 ||             0 |  $100.00 |     $101.00 | $1.00 || 9.32% | 10.00% |

Using commodities and prices

Let's redo the same Snake Oil example, but creating a special commodity to track amount of Snake Oil we have.

We will use SNKOIL as a commodity name, and will assume that 1 SNKOIL = $1 at the beginning of 2019.

As before, we start with a simple example where we invest in SNKOIL, and by the end of 2019 our investment growth by 10%.

2019-01-01 Investing in Snake Oil
  investment:snake oil   100 SNKOIL @@ $100

; Recording the growth of Snake Oil
P 2019-12-24  SNKOIL $1.1

We need to tell roi that we are interested in the growth of value of our investment with --value=then switch, which forces it to use prices that were in effect at each moment in time that roi inspects for its computations:

$ hledger roi -Y --inv investment --pnl "unrealized" --value=then
|   ||      Begin |        End || Value (begin) | Cashflow | Value (end) |   PnL ||    IRR |    TWR |
| 1 || 2019-01-01 | 2019-12-31 ||             0 |   $100.0 |      $110.0 | $10.0 || 10.00% | 10.00% |

Following the story from the previous example, lets say that shorty after investing in the Snake Oil we started to have second thoughts, so we prompty withdrew $90, leaving only $10 in. Before Christmas, though, we started to get the "fear of missing out", so we put the $90 back in. So for most of the year, our investment was just $10 dollars (or, rather 10 SNKOIL):

2019-01-01 Investing in Snake Oil