Emacs Carnival: Completion in Beancount Plain Text Accounting

This is my submission for the February 2026 Emacs Carnival; Sacha has proposed the theme of completion in emacs.

I manage our household finances using a Plain Text Accounting app called Beancount. I really like having all of our records in plain text rather than in a proprietary accounting system. And, by “all of our records,” I mean a pretty long history of earning and spending. The first entry in the ledger is dated 1 July 1988. I didn’t start out in plain text. I don’t remember the name of the first program I used. I moved from that forgotten program to quicken, then to Gnu Cash, then (moving into plain text) to Ledger, and finally, to Beancount. I use emacs with beancount-mode enabled to manage our ledger.

Many of the users of Beancount (or other plain text accounting programs) download and import financial records from their financial institutions, celebrating the ability to avoid the rather tedious process of entering transactions one by one. I suppose I’m a dinosaur (ok, boomer!); I like to be more hands-on with our records so that I actually see and think about each transaction. But I’m not opposed to saving time; I set up a few yasnippet snippets several years ago to enter some of the more standard transactions. When Sacha announced that the topic of this month’s carnival would be completion in emacs, I decided to re-think my use of yasnippet. I assumed that I would add more snippets to my collection, and I did that. But I also realized that I could eliminate some of the snippets I was using and replace them with one multi-purpose snippet. What I have here is decidedly not very sophisticated. I have some ideas for how I could improve it, and I know just enough about yasnippet to know that there are many strategies that haven’t occurred to me yet. But it’s near the end of the month, and I think the very basic system I have is worth writing about. Perhaps it will help others who’ve not done much with it yet. Even better, perhaps someone who knows much more about this than I do will offer some enhancements. I’m all ears!

Speaking of people who know more about completion in emacs than I do, I have to give a shout-out to Sacha for her detailed and informative account of what might emerge in this carnival. There’s so much information there. It’s much more than I can absorb on first reading; I’ve flagged it so that I can consult it as I think about incorporating more of the different completion tools available in emacs.

First, a bit of beancount information. Each transaction in the financial ledger is in a specified format that beancount’s reporting feature expects. The format is straightfoward. Here’s an example:

2025-02-28 * “Grocery Store” “Something to fix for supper”
  Expenses:Groceries           15.00 USD
  Liabilities:CreditCards:CredCardA

The first line has the date, the payee, and an optional description or narration of the transaction. The second line has the destination account of the transaction and the third line has the source account. The transaction amount can be in either the second or third line.

I have examples of my different snippets below, but here’s a preliminary note: all of my snippets begin with a custom function called “org-insert-date.” This function presents the org-mode date-picker set to default to the current date. I can either accept the default or use the calendar to easily select the earlier date. Here’s this function:

  (defun org-insert-date ()
"Insert a date at point using `org-read-date' with its optional argument
of TO-TIME so that the user can customize the date format more easily."
(interactive)
  (require 'org)
  (let ((time (org-read-date nil 'to-time nil "Date:  ")))
    (insert (format-time-string "%Y-%m-%d" time))))

And now, the list of example templates. Obviously, you would need to substitute your own list of destination and source accounts.

A general multi-purpose template

Many of our transactions are one-offs in which at least several of the different elements change from one transaction to the next: a different date, payee, narration, destination account, transaction amount, and/or source account. For these transactions, beancount-mode can be set up to complete the source and destination accounts while entering the transaction directly into the ledger. That’s what I usually do, but to set the stage for later examples here’s a snippet that would generate the transaction in the proper format:

# name: beancount
# type: snippet
# key: /bc
# binding: direct-keybinding
# --
`(org-insert-date)`$1 * "`(read-string "Payee: ")`$2" "`(read-string "Description: ")`$3"
  `(yas-choose-value '("Expenses:Books" "Expenses:Charity:Cash" "Expenses:Christmas" "Expenses:Computer" "Expenses:Entertainment" "Expenses:Dining" "Expenses:Groceries" "Expenses:Gifts" "Expenses:HomeMaintenance" "Expenses:Medical" "Expenses:Misc" "Expenses:Transportation:CarShare" "Expenses:Transportation:MassTransit" "Expenses:Transportation:Taxi" "Expenses:Utilities:Electric" "Expenses:Utilities:Phone" "Expenses:Vacation:Lodging" "Expenses:Vacation:Travel"))`$4  `(read-string "Amount: ")`$5 USD
  `(yas-choose-value '("Liabilities:CredCard:CC1" "Liabilities:CredCard:CC2" "Liabilities:CredCard:CC3"	 ))`$6

At the beginning of the appropriate line in my ledger file, I type the key (/bc) followed by tab. I’m asked to confirm the date, supply the payee and description, the destination account (selected by regex), the transaction amount, and the source account (again, selected by regex). The snippet generates the appropriately formatted transaction.

A template in which most elements are the same from one transaction to the next

We have a few favorite local grocery stores from whom we purchase things regularly, using the same credit card. In these transactions, the only changing elements are the date and the amount spent. So the template is much simpler:

# name: favorite grocery
# type: snippet
# key: /fg
# binding: direct-keybinding
# --
`(org-insert-date)`$1 * "Favorite grocery" "favorite food"
  Expenses:Groceries  `(read-string "Amount: ")`$2 USD
  Liabilities:CredCard:CC1

In this case, I’m asked only to confirm the date and provide the amount of the transaction.

A special template for those old-fashioned paper bank checks

Click here if you don’t know what a bank check is.

A check is a special piece of paper that Person A gives to Person B. The paper stands in for a particular amount of money, e.g. 5.00 USD. It also has a numerical code that’s a unique number in Person A’s bank records. Person B takes the piece of paper to their bank. That bank takes the piece of paper and gives Person B 5.00 USD. Person B’s bank then gives the piece of paper to Person A’s bank in exchange for 5.00 USD. Finally, Person A’s bank takes 5.00 USD from Person A, and the process comes to an end. Whew.

We deal with one vendor who requires that we write a paper check. Each transaction is the same except for the date and the numerical code. Here’s a snippet that generates this transaction:

# name: PaperCheck
# type: snippet
# key: /pc
# binding: direct-keybinding
# --
`(org-insert-date)`$1 * "Write me a check" "for my services"
  code: `(read-string "Code: ")`$2
  Expenses:Services    75.00 USD
  Assets:Current:Checking

This template works as before, but this time asks only that I confirm the date and add the code from my check.

Transactions for reimbursable expenses

Some of our transactions record professional expenses to be reimbursed. I need to generate a monthly report of these transactions for reimbursement. Here I make use of additional features in beancount: tags and links. Here’s the example template:

# name: prof
# type: snippet
# key: /prof
# binding: direct-keybinding
# --
`(org-insert-date)`$1 * "`(read-string "Payee: ")`$2" "`(read-string "Description: ")`$3"  #vendor ^`(downcase (format-time-string "%b%Y"))`$4
  Assets:Current:Reimbursable:Professional    `(read-string "Amount: ")`$5 USD
  `(yas-choose-value '("Liabilities:CredCard:CC1" "Liabilities:CredCard:CC2" "Liabilities:CredCard:CC3"	 ))`$6

The additional elements here are the name of the vendor (prefaced with #) and the month and year in which the expenses are incurred (prefaced with “^” and generated with the current date formatted as “feb2026”). The template works as before, asking for payee, description, amount, and source account. At the end of each month I generate a report that includes all expenses tagged for “vendor” and dated for that month.

Regular monthly transactions, paid on the same date and for the same amount each month

Examples of these transactions include a mortgage or rent payment and regular charity donations. Here I don’t use yasnippet. Instead I put them a special section of my ledger, organized by month. Near the end of each month, I copy the entire list, create a heading for the following month, paste the transactions under that new heading, and do a quick search and replace to change the month number in each transaction. Primitive, admittedly, but it works for me.

Related Posts

comments