Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction to Beancount 3

Experimental Documentation This repository is an experiment to verify if Artificial Intelligence can create high-quality technical documentation. The content you see here was generated by an AI agent.

Welcome to the Beancount 3 documentation.

Beancount is a double-entry bookkeeping system that runs on your computer. Unlike traditional accounting software (like Quicken, YNAB, or GnuCash) that stores data in complex databases or proprietary file formats, Beancount defines a simple language to describe financial transactions in plain text files.

You use your favorite text editor to write down what happened with your money, and Beancount provides the tools to read those files, verify that the math works out, and generate financial reports.

The Philosophy: Plain Text Accounting

Beancount belongs to the family of Plain Text Accounting (PTA) tools. This approach offers several unique advantages:

  • You own your data: Your financial history is stored in text files that will be readable 50 years from now, regardless of whether Beancount still exists.
  • Version Control: Because it’s text, you can manage your finances with Git. You can track changes, revert mistakes, and sync across devices securely.
  • Speed and Efficiency: Entering data is as fast as you can type. There are no slow graphical interfaces or mouse clicks required to enter a coffee purchase.
  • Programmability: Since the data is text, it is trivial to write scripts to manipulate it, generate custom charts, or import data from your bank.

Why Beancount?

Among the PTA tools (which includes Ledger and Hledger), Beancount is known for:

  1. Strictness: Beancount enforces rules (like requiring you to declare accounts before using them) that prevent typos and errors from creeping into your history.
  2. Python: Beancount is written in Python (with a C++ core in v3), making it incredibly extensible for anyone who knows the language.
  3. Inventory Booking: It has a robust model for tracking investments, handling stock lots, cost basis, and capital gains automatically.

How this guide is organized

This documentation follows the Diátaxis framework to help you find exactly what you need:

  • Tutorials: Learning-oriented lessons. Start here if you are brand new to Beancount. We’ll walk you through your first file step-by-step.
  • How-to Guides: Task-oriented recipes. Go here if you want to know “How do I track a stock trade?” or “How do I handle a shared expense?”.
  • Reference: Information-oriented descriptions. Look here for the technical details of the syntax or CLI commands.
  • Explanation: Understanding-oriented discussions. Read these to understand the concepts behind double-entry accounting and Beancount’s design decisions.

Getting Started with Beancount 3

This tutorial guides you through creating your first Beancount ledger. You will learn how to set up the file structure, define your accounts, and record your first transactions.

By the end of this tutorial, you will have a working accounting system that can produce a balance sheet.

Step 1: Installation Check

Before we begin, ensure you have Beancount 3 installed in your environment.

python3 -m beancount --version

If this command fails, please refer to the installation instructions for your operating system.

Step 2: The Options

Create a new empty text file named main.beancount. This will be your master ledger.

The first thing every Beancount file needs is some basic configuration. Add these lines to the top of your file:

option "title" "My Personal Finances"
option "operating_currency" "USD"
  • title: Gives your ledger a name, which appears in reports.
  • operating_currency: Tells Beancount which currency is “yours.” You can have multiple operating currencies (e.g., "USD", "EUR"), but you must have at least one.

Step 3: Defining Accounts

Unlike some other plain text accounting tools, Beancount requires you to declare an account before you use it. This strictness helps catch typos (like typing Assets:Chekcing by mistake).

We use the open directive to create accounts. We usually date these back to a time before we started tracking (like 1970 or 2000).

Tip: Not sure how to name your accounts? Check out the Organizing Accounts guide.

Add these lines to your file:

; Define the equity account for initial balances
2000-01-01 open Equity:Opening-Balances

; Define our main bank account
2000-01-01 open Assets:Checking:Chase

; Define a category for food expenses
2000-01-01 open Expenses:Food:Groceries

Step 4: Setting the Opening Balance

When you start tracking your finances, you likely already have money in your bank account. We need to record this.

In double-entry accounting, money cannot appear out of thin air. It must come from somewhere. For opening balances, it comes from Equity.

Let’s say you have $1,000 in your account on Jan 1st, 2024. Add this transaction:

2024-01-01 * "Opening Balance for Checking"
  Assets:Checking:Chase       1000.00 USD
  Equity:Opening-Balances    -1000.00 USD

Notice that the two numbers sum to zero (+1000 and -1000). This is the core rule: Every transaction must balance to zero.

Step 5: Recording a Purchase

Now, let’s spend some money. On Jan 2nd, you bought groceries for $50.

2024-01-02 * "Whole Foods" "Weekly groceries"
  Expenses:Food:Groceries       50.00 USD
  Assets:Checking:Chase        -50.00 USD

Learn More: See Recording Transactions for more complex examples like salary and split expenses.

  • We increased our Expenses (Expenses are usually positive).
  • We decreased our Assets (money left the checking account).

Step 6: Verification

Beancount comes with a powerful tool called bean-check. It reads your file and ensures:

  1. All syntax is correct.
  2. All transactions balance to zero.
  3. You haven’t used any undeclared accounts.
  4. Your balance assertions (if any) are correct.

Run this in your terminal:

bean-check main.beancount

Reference: For more details on command line tools, see the CLI Reference.

If the command runs silently (no output), your file is perfect! If there are errors, it will tell you the line number and the problem.

Step 7: Generating a Report

Now that we have data, let’s see a report. The bean-report tool can generate many views. Let’s look at the balances:

bean-report main.beancount balances

You should see output similar to:

Assets:Checking:Chase      950.00 USD
Equity:Opening-Balances  -1000.00 USD
Expenses:Food:Groceries     50.00 USD

You now have a fully functional double-entry accounting system!

Recording Income and Split Transactions

In the Getting Started tutorial, we recorded a simple expense. Now, let’s tackle the other side of the equation: earning money.

We will also learn how to handle “Split Transactions”—transactions that touch more than two accounts. This is essential for recording things like your paycheck, which often has tax deductions, or a supermarket receipt that includes both food and household supplies.

Step 1: Defining New Accounts

Before we can record income, we need a place to put it. We also need accounts for the deductions we expect (like taxes).

Open your main.beancount file and add these open directives:

; Income accounts
2000-01-01 open Income:Salary

; Tax expenses
2000-01-01 open Expenses:Taxes:Federal
2000-01-01 open Expenses:Taxes:State
2000-01-01 open Expenses:Taxes:Medicare

Step 2: Understanding Income

In Beancount (and double-entry accounting in general), Income is recorded as a negative number.

This is often the most confusing part for beginners. Think of it this way:

  • Assets are positive (Money you have).
  • Income is negative (The source of that money).

When you get paid, money moves FROM your job (Income) TO your bank account (Asset). Since the sum of a transaction must be zero: (+Asset) + (-Income) = 0

Step 3: Recording a Simple Paycheck

Let’s start with a simplified example. You get paid ,000, deposited directly into your checking account.

2024-01-15 * "Employer Inc" "Salary"
  Assets:Checking:Chase       2000.00 USD
  Income:Salary              -2000.00 USD

Run bean-check main.beancount. It should pass. If you run bean-report main.beancount balances, you will see:

  • Assets:Checking:Chase increased by ,000.
  • Income:Salary is -2,000.

Step 4: The Real World (Split Transactions)

Real paychecks aren’t that simple. Usually, your “Gross Pay” is higher, but taxes and insurance are taken out before the money hits your bank.

Let’s say your Gross Pay is ,000.

  • Federal Tax takes 00.
  • State Tax takes 00.
  • The remainder (,500) is deposited into your bank.

This is a Split Transaction. It involves one source (Income) and multiple destinations (Bank, Tax Expenses).

Add this transaction to your file:

2024-01-31 * "Employer Inc" "Salary with deductions"
  Assets:Checking:Chase       2500.00 USD
  Expenses:Taxes:Federal       400.00 USD
  Expenses:Taxes:State         100.00 USD
  Income:Salary              -3000.00 USD

Why does this work?

Sum up the numbers: +2500 (Bank) + 400 (Fed) + 100 (State) - 3000 (Income) = 0

The transaction balances. This accurately reflects reality: you earned ,000, but you only received ,500 in cash because you “spent” 00 on taxes immediately.

Step 5: Splitting Expenses

The same logic applies to spending money. Suppose you go to “SuperMart” and buy:

  • Groceries (0)
  • A new toaster (Household supplies, 0)

First, ensure you have the accounts:

2000-01-01 open Expenses:Household:Supplies

Now record the split expense:

2024-02-02 * "SuperMart" "Groceries and Toaster"
  Expenses:Food:Groceries       60.00 USD
  Expenses:Household:Supplies   30.00 USD
  Assets:Checking:Chase        -90.00 USD

Here, money leaves your Checking account (-90) and splits into two different expense buckets (+60 and +30).

Summary

  • Income is negative. It balances out the positive increase in your Assets.
  • Transactions can have many lines. As long as they sum to zero, you can split money across as many accounts as you need.

Next, we will look at how to handle debt and credit cards.

Managing Credit Cards and Loans

In this tutorial, we introduce a new type of account: Liabilities.

A Liability represents money you owe to someone else. The most common example is a Credit Card. Unlike a Debit card (which pulls money directly from your Assets), a Credit Card increases your debt, which you pay off later.

Step 1: Defining a Credit Card Account

Credit cards live under the Liabilities hierarchy. Let’s open an account for a Visa card.

Add this to your main.beancount:

2000-01-01 open Liabilities:CreditCard:Visa

Step 2: Buying with Credit

When you swipe your credit card, two things happen:

  1. You incur an Expense (e.g., you bought dinner).
  2. You incur a Liability (you now owe the bank money).

In Beancount, Liabilities are generally recorded as negative numbers, just like Income.

  • Assets are positive (What you own).
  • Liabilities are negative (What you owe).

However, unlike Income, we don’t usually talk about “increasing negative debt.” We just say the balance gets “more negative.”

Let’s record a 0 dinner on the Visa card:

2024-02-14 * "Romantic Bistro" "Valentine's Dinner"
  Expenses:Food:Dining           50.00 USD
  Liabilities:CreditCard:Visa   -50.00 USD

Notice:

  • Expense is Positive (+50).
  • Liability is Negative (-50).
  • Sum is Zero.

If you run bean-report main.beancount balances, your Visa account will show -50.00 USD. This means you owe 0.

Step 3: Buying More Stuff

Let’s buy something else. A 0 book.

2024-02-15 * "Bookstore" "New Novel"
  Expenses:Entertainment:Books   20.00 USD
  Liabilities:CreditCard:Visa   -20.00 USD

(Make sure you open Expenses:Entertainment:Books if you haven’t already!)

Your Visa balance is now -70.00 USD.

Step 4: Paying the Bill

A month later, you receive your credit card statement. It says you owe 0. You pay it from your Checking account.

This transaction is a Transfer. You are moving money from an Asset to a Liability.

  • Checking Account decreases (-70).
  • Credit Card debt decreases (moves from -70 back towards 0, so +70).
2024-03-15 * "Chase Bank" "Paying off Visa bill"
  Liabilities:CreditCard:Visa    70.00 USD
  Assets:Checking:Chase         -70.00 USD

After this transaction:

  • Your Checking account has 0 less.
  • Your Visa account balance is 0.00 USD. You are debt-free!

Common Confusion

New users often ask: “Why do I record the expense when I buy the item? Why not when I pay the bill?”

In double-entry accounting (and accrual accounting), you record the expense when it happens.

  1. Feb 14: You ate the dinner. That is when you “spent” the value.
  2. Mar 15: You moved cash to satisfy the debt. That is just a transfer of funds.

This gives you a more accurate picture of your spending. If you only recorded expenses when you paid the bill, it would look like you spent /run/current-system/sw/bin/bash in February and 0 on “Credit Card Bill” in March. That doesn’t tell you what you bought!

Summary

  1. Spending: Increase Expense (+), Increase Debt (-).
  2. Paying Bill: Decrease Debt (+), Decrease Asset (-).
  3. Liabilities usually have negative balances.

Next, we will learn how to make sure these numbers match your actual bank statements using Balance Assertions.

Reconciling with Balance Assertions

You have been entering transactions manually. But humans make mistakes. Maybe you typed 0.00 instead of .00, or you forgot to record that coffee you bought last Tuesday.

How do you trust your numbers?

In Beancount, we use Balance Assertions. This is the “killer feature” that makes plain-text accounting robust.

Step 1: What is a Balance Assertion?

A balance assertion is a statement of fact. You are telling Beancount: “I checked my bank website, and on this specific date, the balance was exactly X.”

Beancount will then calculate the balance of all your transactions up to that date.

  • If they match: Great! Nothing happens.
  • If they differ: Beancount stops with an error, telling you exactly how much you are off by.

Step 2: Adding an Assertion

Let’s say you log into your bank website on 2024-02-01. The website says your Checking account has ,450.00.

In your file, you might have entered transactions that leave you with ,450.00, or maybe ,445.00 if you missed a coffee.

Add this line to your file:

2024-02-01 balance Assets:Checking:Chase  1450.00 USD

Note the syntax:

  1. Date: The morning of this date (before any transactions on this date happen).
  2. Directive: balance
  3. Account: The account to check.
  4. Amount: The expected amount.

Step 3: Running the Check

Run the checker:

bean-check main.beancount

Scenario A: Success If the command outputs nothing (or just “no errors”), your history matches the bank’s reality perfectly.

Scenario B: Failure If you forgot a transaction, you might see an error like this:

main.beancount:45: Balance failed for Assets:Checking:Chase: expected 1450.00 USD but accumulated 1455.00 USD

This tells you:

  1. You expected ,450.
  2. Beancount calculated ,455 based on your transactions.
  3. You have too much in your Beancount file. This means you likely forgot to record a expense (or recorded a income twice).

Step 4: Finding the Error

When an assertion fails, you know the error happened between the last successful assertion and this one.

This is why frequent assertions are good.

  • If you assert once a year, you have to search 365 days of transactions for the error.
  • If you assert once a month (when your statement arrives), you only have to search one month.

To fix the error above ( difference), you would look through your bank statement, find the missing coffee, and add the transaction:

2024-01-28 * "Starbucks" "Forgot this one"
  Expenses:Food:Coffee          5.00 USD
  Assets:Checking:Chase        -5.00 USD

Run bean-check again. The calculated balance will drop by , matching the assertion (450), and the error will disappear.

Best Practices

  1. Assert regularly: Every time you receive a monthly PDF statement from your bank, add a balance directive for the date of the statement.
  2. Pad directives: Sometimes (especially when starting), you can’t find the error. Beancount has a pad directive that auto-inserts a transaction to make the balance match. Use this sparingly! (See the Reference docs for pad).
  3. Order matters: Assertions check the balance at the beginning of the day. If you have transactions on 2024-02-01, the assertion 2024-02-01 balance ... checks the state before those transactions.

Summary

You have now completed the core tutorials!

  1. Basics: Created accounts and simple transactions.
  2. Income: Handled salary and split transactions.
  3. Liabilities: Managed credit card debt.
  4. Reconciliation: Used assertions to ensure accuracy.

You are ready to manage your own finances. Check the How-to Guides for specific scenarios like tracking investments, shared expenses, or multi-currency trips.

Advanced Investing: Stocks and Capital Gains

This tutorial moves beyond simple income and expenses to handle Assets held at Cost. This is the foundation of tracking investments, stocks, and currency trading in Beancount.

Step 1: Defining Accounts

Investment accounts look like regular asset accounts, but they hold Commodities (like “AAPL” or “GOOG”) instead of just Currency (USD).

Add these to your main.beancount:

2000-01-01 open Assets:Brokerage:Cash        USD
2000-01-01 open Assets:Brokerage:Stocks      AAPL, GOOG
2000-01-01 open Income:Dividends
2000-01-01 open Income:Capital-Gains

Step 2: Buying Stock (Cost Basis)

When you buy stock, you aren’t just transferring value; you are acquiring specific “lots” of an asset at a specific price. Beancount tracks this Cost Basis.

On Jan 1st, you buy 10 shares of AAPL at $150 each.

2024-01-01 * "Buy AAPL"
  Assets:Brokerage:Stocks      10 AAPL {150.00 USD}
  Assets:Brokerage:Cash     -1500.00 USD
  • 10 AAPL: The quantity and commodity.
  • {150.00 USD}: The Cost per share. This attaches a “price tag” to these specific 10 shares.

Step 3: Buying More (Multiple Lots)

On Feb 1st, the price drops. You buy 5 more shares at $140.

2024-02-01 * "Buy more AAPL"
  Assets:Brokerage:Stocks       5 AAPL {140.00 USD}
  Assets:Brokerage:Cash      -700.00 USD

You now own 15 shares of AAPL, but Beancount sees them as two distinct Lots:

  1. 10 shares @ $150
  2. 5 shares @ $140

Step 4: Selling and Realizing Gains

On March 1st, the price hits $160. You decide to sell 5 shares. But which 5 shares? The expensive ones ($150) or the cheap ones ($140)?

This is the Booking Method. By default, Beancount lets you choose specific lots (Specific Identification).

Let’s sell 5 shares from the first batch ($150).

2024-03-01 * "Sell AAPL"
  Assets:Brokerage:Stocks      -5 AAPL {150.00 USD} @ 160.00 USD
  Assets:Brokerage:Cash       800.00 USD
  Income:Capital-Gains        -50.00 USD

Breaking it down:

  1. -5 AAPL {150.00 USD}: We are removing 5 shares that had a cost of $150.
  2. @ 160.00 USD: We sold them at a market price of $160.
  3. Assets:Brokerage:Cash 800.00 USD: We received $800 cash (5 * $160).
  4. Income:Capital-Gains -50.00 USD: The profit.

The Math:

  • Cost of sold shares: 5 * $150 = $750.
  • Cash received: 5 * $160 = $800.
  • Profit: $800 - $750 = $50.

Since Income is negative, we record -50.00 USD. The transaction balances: -750 (Stock Cost) + 800 (Cash) - 50 (Profit) = 0.

Step 5: Dividends

Dividends are simply income.

2024-03-15 * "Apple Dividend"
  Assets:Brokerage:Cash        15.00 USD
  Income:Dividends            -15.00 USD

Summary

  1. Use {price} to attach cost basis when buying.
  2. Use {cost} @ price when selling to calculate P/L.
  3. Profit is the difference between the Cost Basis and the Sell Price.

How to Record Transactions

Transactions describe the movement of commodities (usually money) between accounts. While simple income and expense transactions are common, real life is often more complex.

This guide covers the standard patterns you will encounter.

The Anatomy of a Transaction

A transaction is a single unit that ensures money is neither created nor destroyed (except through Income/Equity).

2024-01-10 * "Payee" "Narration / Description"
  Account1     -10.00 USD
  Account2      10.00 USD
  • Date: YYYY-MM-DD. Strict requirement.
  • Flag: * (cleared/confirmed) or ! (pending).
  • Payee: (Optional) Who did you trade with?
  • Narration: (Optional) What was this for?
  • Postings: The lines indented below. You can have as many as you want, as long as they sum to zero.

1. Complex Income (Salary with Deductions)

Real paychecks are rarely just “Income -> Bank”. They include taxes, 401k contributions, and insurance. You record all of these in a single transaction.

Why is Income negative? In Beancount, Income is money flowing from the outside world into your accounts. To make the math balance to zero, Income is recorded as a negative number. Read the full explanation here.

2024-01-15 * "Tech Corp" "Payroll #42"
  Assets:Checking:Chase        2500.00 USD  ; Net pay deposited
  Expenses:Taxes:Federal        500.00 USD
  Expenses:Taxes:State          200.00 USD
  Assets:401k:PreTax            300.00 USD  ; Contribution to retirement
  Expenses:Insurance:Health     100.00 USD
  Income:Salary               -3600.00 USD  ; Gross Pay (negative)

Note that the Gross Pay (-3600) balances out all the positive numbers (2500+500+200+300+100).

2. Shared Expenses (Reimbursements)

If you pay for lunch for a friend and they pay you back later, you shouldn’t record the full amount as your expense.

Scenario: You pay $40. $20 is your food, $20 is for Alice.

2024-02-01 * "Restaurant" "Lunch with Alice"
  Assets:Checking:Chase        -40.00 USD
  Expenses:Food:Dining          20.00 USD
  Assets:Receivable:Alice       20.00 USD

Later, when Alice pays you back:

2024-02-05 * "Alice" "Venmo repayment"
  Assets:Checking:Chase         20.00 USD
  Assets:Receivable:Alice      -20.00 USD

Your Assets:Receivable:Alice account is now zero.

3. Foreign Currency (Vacation)

When you use a card abroad, the bank converts the currency. You should record the actual cost to you (in your home currency) or the exact conversion if you want to track the foreign currency.

Simple approach (Convert at cost): You bought a €10 coffee, and your bank charged you $11.20.

2024-03-10 * "Cafe Paris" "Coffee"
  Expenses:Travel:Food          11.20 USD
  Assets:Checking:Chase        -11.20 USD

Advanced approach (Tracking rates): Explicitly stating the exchange rate using @.

2024-03-10 * "Cafe Paris" "Coffee"
  Expenses:Travel:Food          10.00 EUR @ 1.12 USD
  Assets:Checking:Chase        -11.20 USD

You can attach extra data to transactions to help with filtering later.

Deep Dive: For full syntax details, see the Syntax Reference.

  • Tags: Use #tagname for categories that cross-cut accounts (like #vacation2024 or #tax-deductible).
  • Links: Use ^linkname to group related transactions (like an invoice and its payment).
  • Metadata: Key-value pairs for specific details.
2024-04-01 * "Hotel" "Stay in Tokyo" #vacation2024 ^invoice-1234
  booking_id: "XYZ-999"
  Liabilities:CreditCard      -200.00 USD
  Expenses:Travel:Lodging      200.00 USD

How to Manage Documents

Beancount allows you to link external files (PDFs, images) to specific transactions. This is invaluable for auditing and keeping track of receipts.

The document directive

To link a file to an account (without a specific transaction context), use the document directive.

2024-03-15 document Liabilities:CreditCard "/path/to/statement_mar2024.pdf"

Linking to Transactions

Beancount automatically associates documents with transactions if the date and tags match, but the most robust way is to use the metadata field statement or simply let Fava handle the discovery based on file location.

However, the standard convention in Beancount 3 is to use the document directive to declare the file exists, and then standard tooling (like Fava) displays it.

Organizing Files

Beancount enforces a specific directory structure for documents if you want automatic discovery:

/path/to/documents/
  Liabilities/
    CreditCard/
      2024-03-15.Statement.pdf
  1. Set the documents option in your file:
    option "documents" "/path/to/documents"
    
  2. Beancount will scan this directory.
  3. Fava will automatically show 2024-03-15.Statement.pdf next to any transaction involving Liabilities:CreditCard on that date.

Best Practices

  • Scans: Scan all physical receipts immediately.
  • Naming: Use YYYY-MM-DD.Description.pdf so they sort naturally in your file explorer.
  • Privacy: Remember these files are just on your disk. Beancount doesn’t encrypt them.

How to Organize Your Accounts

One of the first questions new users ask is “What accounts should I have?”

Beancount uses a hierarchical structure (separated by colons :) that allows you to be as general or specific as you want.

The Five Root Types (Strict Rule)

You cannot rename these. Every account must start with one of these five roots:

  1. Assets: Things you own that have value.
    • Examples: Bank accounts, Cash, House, Car, Stocks, Receivables (money owed to you).
  2. Liabilities: Money you owe to others.
    • Examples: Credit Cards, Mortgage, Student Loans.
  3. Income: Money flowing in to your life.
    • Examples: Salary, Interest, Dividends, Gifts Received.
  4. Expenses: Money flowing out of your life (consumption).
    • Examples: Rent, Food, Taxes, Utilities, Gifts Given.
  5. Equity: The net worth of the ledger.
    • Examples: Opening balances, Conversions, Retained Earnings.

Example 1: Simple Personal Finance

For most people starting out, keep it simple. Don’t over-engineer.

Assets:US:BofA:Checking
Assets:US:BofA:Savings
Assets:Vanguard:401k

Liabilities:US:Chase:SapphireCard

Income:US:Employer:Salary
Income:US:Bank:Interest

Expenses:Home:Rent
Expenses:Home:Utilities
Expenses:Food:Groceries
Expenses:Food:Restaurant
Expenses:Transport:Metro
Expenses:Transport:Uber
Expenses:Shopping:Clothing
Expenses:Health:Medical

Example 2: The Freelancer

Freelancers need to separate business and personal expenses.

; Business Assets
Assets:Biz:PayPal
Assets:Biz:Checking

; Personal Assets
Assets:Pers:Checking

; Business Income
Income:Biz:ClientA
Income:Biz:ClientB

; Expenses
Expenses:Biz:Hosting
Expenses:Biz:Software
Expenses:Pers:Food

Tips for Success

1. Geography is useful

If you have accounts in multiple countries, put the country code early in the hierarchy:

  • Assets:US:Chase
  • Assets:UK:Barclays This groups all your US assets together in reports.

2. Institution Names

Using the bank name helps you match physical statements to Beancount accounts:

  • Liabilities:US:Amex:Gold
  • Liabilities:US:Amex:Platinum

3. Don’t go too deep

Avoid: Expenses:Food:Groceries:Fruits:Bananas. Better: Expenses:Food:Groceries. Why? If you are too specific, classifying transactions becomes a chore. You can always use #tags for temporary specificity (like #wedding or #renovation).

4. Group by Fixed vs Variable

Some users like to separate fixed bills from discretionary spending:

  • Expenses:Fixed:Rent
  • Expenses:Fixed:Internet
  • Expenses:Variable:Dining
  • Expenses:Variable:Hobbies

Managing Shared Expenses

When you share finances with a partner, roommate, or group, you often pay for things on their behalf. Beancount handles this by tracking Receivables (money owed to you) and Payables (money you owe).

Scenario 1: You pay, they owe you

You pay $100 for dinner. $50 is for you, $50 is for your friend “Alice”.

2024-06-01 * "Restaurant" "Dinner with Alice"
  Assets:CreditCard:Visa        -100.00 USD
  Expenses:Food:Dining            50.00 USD
  Assets:Receivable:Alice         50.00 USD
  • Your Expense: Only $50.
  • Asset: You now hold a $50 claim against Alice.

When Alice pays you back:

2024-06-05 * "Alice" "Venmo repayment"
  Assets:Bank:Checking            50.00 USD
  Assets:Receivable:Alice        -50.00 USD

The Receivable account is now zero.

Scenario 2: They pay, you owe them

Alice pays $100. Your share is $50.

2024-06-01 * "Alice" "Paid for dinner"
  Expenses:Food:Dining            50.00 USD
  Liabilities:Payable:Alice      -50.00 USD

When you pay her back:

2024-06-05 * "Venmo" "Paying Alice back"
  Liabilities:Payable:Alice       50.00 USD
  Assets:Bank:Checking           -50.00 USD

Scenario 3: Netting Out (The “Tabs” approach)

If you live with someone, you might not settle every transaction. You just want to track the running balance.

Use a single account: Assets:Partner.

  • Positive balance: They owe you.
  • Negative balance: You owe them.

You pay electric bill ($100, split 50/50):

2024-06-01 * "Electric Co"
  Assets:Bank                     -100.00 USD
  Expenses:Utilities                50.00 USD
  Assets:Partner                    50.00 USD

They pay rent ($2000, split 1000/1000):

2024-06-01 * "Partner paid rent"
  Expenses:Housing:Rent           1000.00 USD
  Assets:Partner                 -1000.00 USD

Current Balance of Assets:Partner: -950.00 USD. You owe them $950. You write them a check for $950 to settle up.

Health Care & Insurance

Medical billing is complex. A single doctor’s visit can generate multiple bills, insurance adjustments, and reimbursements over months. Beancount’s Link feature (^) is perfect for this.

Tracking Claims (The “Incident”)

Use a unique link identifier for each medical event.

Step 1: The Visit You go to the doctor. You pay a $30 copay.

2024-03-01 * "Dr. Smith" "Checkup" ^claim-2024-03-smith
  Expenses:Medical:Doctor          30.00 USD
  Assets:CreditCard               -30.00 USD

Step 2: The Bill Arrives A month later, you get a bill for the remaining $150 that insurance didn’t cover.

2024-04-01 * "Dr. Smith" "Remaining balance" ^claim-2024-03-smith
  Expenses:Medical:Doctor         150.00 USD
  Assets:CreditCard              -150.00 USD

Step 3: Insurance Reimbursement Oops, insurance decided to cover $50 of that after all.

2024-04-15 * "Aetna" "Reimbursement" ^claim-2024-03-smith
  Assets:Bank                      50.00 USD
  Expenses:Medical:Doctor         -50.00 USD

By clicking the ^claim-2024-03-smith link in Fava, you can see the entire history of this specific medical event and calculate the true final cost ($130).

Health Savings Accounts (HSA)

HSAs are triple-tax-advantaged accounts. Treat them like any other investment asset.

Contribution (Payroll)

2024-01-15 * "Paycheck"
  Assets:Bank                    2000.00 USD
  Assets:HSA:Cash                 100.00 USD
  Income:Salary                 -2100.00 USD

Spending from HSA When you use your HSA card, you are spending directly from that asset.

2024-03-01 * "Pharmacy" "Prescription"
  Expenses:Medical:Meds            15.00 USD
  Assets:HSA:Cash                 -15.00 USD

Investing HSA If you invest your HSA funds, track the commodities just like a brokerage account.

2024-06-01 * "Buy Fund"
  Assets:HSA:VTSAX             5 VTSAX {100.00 USD}
  Assets:HSA:Cash             -500.00 USD

How to Record Trades

Tracking investments requires recording the cost basis (what you paid) so you can calculate capital gains when you sell.

Buying Assets

When you buy an asset (like a stock), you record the cost per share using curly braces {}.

Why the curly braces? Beancount tracks distinct “lots” of inventory. See How Inventories Work for a deep dive.

2024-05-01 * "Buy AAPL"
  Assets:Brokerage:Cash      -1500.00 USD
  Assets:Brokerage:AAPL         10 AAPL {150.00 USD}

Here, we bought 10 shares of AAPL at $150.00 each.

Selling Assets

When you sell, you must specify which “lot” you are selling by matching the original cost basis. You also record the selling price using @.

The difference between the cost {...} and the price @ is your Capital Gain (or Loss).

2024-06-01 * "Sell AAPL"
  Assets:Brokerage:AAPL         -5 AAPL {150.00 USD} @ 160.00 USD
  Assets:Brokerage:Cash        800.00 USD
  Income:CapitalGains          -50.00 USD

We sold 5 shares. Cost: $750 (5 * 150). Proceeds: $800 (5 * 160). Profit: $50.

Taxable vs. Tax-Exempt Accounts

The syntax for the trade is the same, but the Income account you use for the gains should differ.

1. Taxable Account

In a standard brokerage account, every sale is a taxable event. You should book gains to an Income account that reflects taxable income.

2024-06-01 * "Sell Stock in Taxable"
  Assets:Taxable:AAPL           -5 AAPL {150.00 USD} @ 160.00 USD
  Assets:Taxable:Cash          800.00 USD
  Income:Taxable:CapitalGains  -50.00 USD

2. Tax-Exempt / Tax-Advantaged Account (IRA, 401k)

In accounts like an IRA, you don’t pay taxes on individual trades. However, Beancount still requires the transaction to balance. You still made a profit, even if the IRS doesn’t care yet.

To handle this, map the gains to an Income account specifically for that tax shelter. This keeps it separate from your taxable income reports.

2024-06-01 * "Sell Stock in IRA"
  Assets:IRA:AAPL               -5 AAPL {150.00 USD} @ 160.00 USD
  Assets:IRA:Cash              800.00 USD
  Income:IRA:Gains             -50.00 USD

By separating Income:Taxable:CapitalGains and Income:IRA:Gains, you can easily filter your reports to see only your taxable obligations.

How to Track Stock Vesting (RSUs)

Restricted Stock Units (RSUs) are a common form of compensation. Tracking them requires handling the income event (vesting) and the subsequent capital gains (selling).

1. The Grant

When you are granted RSUs, you don’t actually own anything yet. You technically don’t need to record this in your main ledger, but you can track it in a separate “Shadow” account if you wish.

Most users skip this step and start at Vesting.

2. Vesting (The Taxable Event)

When shares vest, two things happen:

  1. You receive shares (Asset).
  2. You pay taxes (Expense), usually by selling some of those shares immediately (“Sell to Cover”).
  3. You recognize the total value as Income.

Example:

  • You vest 10 shares of GOOG.
  • Market price is $100/share. Total Value: $1000.
  • Tax rate is 40%. You “sell” 4 shares to pay $400 in taxes.
  • You keep 6 shares.
2024-05-15 * "Employer" "RSU Vest - 10 shares"
  Income:Employer:RSU          -1000.00 USD  ; Total Grant Value
  Expenses:Taxes:Federal         400.00 USD  ; Taxes paid
  Assets:Brokerage:GOOG            6 GOOG {100.00 USD}
  • Income: We record the full gross amount ($1000).
  • Asset: We record the 6 shares we actually kept, with a cost basis of the market price at that moment ({100.00 USD}).

3. Selling

Later, when you sell the shares, you calculate Capital Gains based on that cost basis.

Scenario: Price rises to $120. You sell your 6 shares.

2024-08-01 * "Broker" "Sell RSUs"
  Assets:Brokerage:GOOG           -6 GOOG {100.00 USD} @ 120.00 USD
  Assets:Brokerage:Cash          720.00 USD
  Income:CapitalGains           -120.00 USD
  • Cost: $600 (6 * 100).
  • Proceeds: $720 (6 * 120).
  • Gain: $120.

Employee Stock Purchase Plan (ESPP)

ESPP is different. You buy shares at a discount.

Example:

  • Market Price: $100.
  • Discount: 15% (You pay $85).
  • You buy 10 shares.
2024-06-30 * "Employer" "ESPP Purchase"
  Assets:Brokerage:Cash         -850.00 USD ; You paid $850
  Income:Employer:ESPP-Discount -150.00 USD ; The discount is essentially income
  Assets:Brokerage:GOOG           10 GOOG {100.00 USD} ; Cost basis is Fair Market Value

Note: ESPP tax rules are complex (Qualifying vs Disqualifying dispositions). This method sets the cost basis to the FMV ($100), treating the discount as immediate income. Consult a tax professional for your specific reporting needs.

How to Track Settlement Dates

In financial trading, there is often a delay between the Trade Date (when you agreed to the deal) and the Settlement Date (when the cash actually moves).

  • Trade Date: You bought the stock today. You own it now.
  • Settlement Date: The cash leaves your account in 2 days (T+2).

Beancount generally uses a single date per transaction. This guide shows you how to track both using an intermediate “Pending” account.

The Strategy

Instead of moving cash directly from Assets:Checking to Assets:Stock, we move it through Assets:Pending:Transfers.

  1. On Trade Date: Record the stock purchase and a debt to the pending account.
  2. On Settlement Date: Record the cash movement clearing the debt.

Example: Buying Stock

You buy 10 shares of AAPL for $150 on June 1st. The cash will settle on June 3rd.

Step 1: The Trade (June 1)

You legally own the stock now. You “owe” the cash to the settlement clearing house.

2024-06-01 * "Buy AAPL"
  Assets:Brokerage:AAPL         10 AAPL {150.00 USD}
  Assets:Pending:Transfers    -1500.00 USD
  • Stock: Account increases (You own it).
  • Pending: Account decreases (You owe money).

Step 2: The Settlement (June 3)

The bank actually transfers the money.

2024-06-03 * "Settlement for AAPL"
  Assets:Pending:Transfers     1500.00 USD
  Assets:Checking             -1500.00 USD
  • Pending: Account returns to zero.
  • Checking: Cash leaves your bank.

Using the pad Directive

If you import your bank data automatically, your bank will only show the transaction on June 3rd. Your brokerage might show the trade on June 1st.

You can use pad to fill the gap automatically without manually writing the settlement transaction every time.

  1. Trade (From Brokerage Importer):

    2024-06-01 * "Buy AAPL"
      Assets:Brokerage:AAPL         10 AAPL {150.00 USD}
      Assets:Pending:Transfers
    

    (Note: We leave the second leg empty or let the importer calculate it)

  2. Cash (From Bank Importer):

    2024-06-03 * "ACH Transfer"
      Assets:Checking             -1500.00 USD
      Assets:Pending:Transfers
    
  3. Pad Directive: If you don’t have the exact matching transaction, you can tell Beancount to “fill the gap” in the Pending account.

    2024-06-02 pad Assets:Pending:Transfers Equity:Timing-Differences
    

    Use pad with caution! It effectively “invents” a transaction to make balances match.

Benefits

  • Accuracy: Your stock holdings are correct on the trade date (important for dividends and voting rights).
  • Reconciliation: Your bank balance matches the bank statement dates (settlement dates).

Portfolio Returns

A common question is: “What is my investment return?” (e.g., “I made 8% this year”).

Beancount does not calculate percentage returns (IRR or TWR) natively in the core engine. It focuses on accounting correctness (Cost Basis and Capital Gains). However, you can extract the data needed to calculate this.

The Challenge

To calculate returns, you need to know:

  1. The value of the portfolio at the start.
  2. The value at the end.
  3. The exact date and amount of every cash flow in and out.

Since Beancount tracks all of this, the data is there.

Solution 1: Fava

Fava has a built-in “Holdings” view that attempts to calculate returns for your assets. This is the easiest way for most users.

Solution 2: Scripts

The standard way to calculate returns in the Beancount ecosystem is to use a script to extract the cash flows and compute the Internal Rate of Return (IRR).

Beancount provides a helper library beancount.plugins.ira_contribs which can help track contributions, but for returns, community scripts are often used.

Calculating Simple Gain (Profit)

You can easily see your total profit (Market Value - Cost Basis) in bean-query.

SELECT
  account,
  sum(convert(position, 'USD')) as market_value,
  sum(cost(position)) as cost_basis,
  (sum(convert(position, 'USD')) - sum(cost(position))) as unrealized_gain
WHERE account ~ "Assets:Investments"
GROUP BY account

Note: This query requires that you have updated prices for your commodities.

Short Sales

A Short Sale is when you sell an asset you don’t own (by borrowing it), hoping to buy it back later at a lower price.

In Beancount terms, this means holding a negative number of units held at cost.

The Constraint

By default, Beancount enforces a constraint: you cannot have a negative inventory of lots.

  • If you hold 10 GOOG {100 USD}, you can sell at most 10.
  • If you try to sell 15, you get an error.

Enabling Short Sales

To allow short selling, you generally need to relax this constraint or use a dedicated account where the negative balance is expected.

Method 1: Dedicated Short Account

Separate your “Long” and “Short” positions.

2024-06-01 * "Short Sell"
  Assets:Brokerage:Cash          1000.00 USD
  Liabilities:Brokerage:Shorts    -10 GOOG {100.00 USD}

Here, we use a Liability account to track the short position. A negative liability is… well, usually a liability is already negative (credit). Wait.

In Beancount:

  • Assets are Positive.
  • Liabilities are Negative.

If you are Short, you owe the stock. It is a Liability. So Liabilities:Brokerage:Shorts should have a negative number of units.

Closing the Short (Covering): You buy the stock back.

2024-07-01 * "Cover Short"
  Liabilities:Brokerage:Shorts     10 GOOG {100.00 USD} @ 90.00 USD
  Assets:Brokerage:Cash          -900.00 USD
  Income:CapitalGains            -100.00 USD
  • You “buy” 10 units to neutralize the -10 units.
  • Cost Basis match: {100.00 USD}.
  • Price paid: $900.
  • Profit: $100.

Caveats

  • Dividends: If you are short, you pay dividends instead of receiving them. This is an Expense.
  • Interest: You usually pay margin interest on the borrowed value.

How to Fetch Market Prices

To track the value of your investments (Stocks, ETFs, Crypto) in Beancount, you need to record their market price over time.

The price Directive

Manually, you can add prices like this:

2024-01-01 price AAPL 150.00 USD
2024-01-02 price AAPL 152.00 USD

But doing this manually is tedious.

Using bean-price

Beancount comes with a tool called bean-price that fetches prices from the internet.

1. Install Requirements

You might need to install extra sources:

pip install beancount-beanprice

2. Configure Your Commodities

You need to tell bean-price where to look for the price. You do this by adding metadata to your commodity directive.

Note: The commodity directive is optional in basic Beancount but required if you want to attach metadata like this. See Syntax Reference.

1980-01-01 commodity AAPL
  price: "Yahoo/AAPL"
  name: "Apple Inc."

1980-01-01 commodity BTC
  price: "Coinbase/BTC-USD"

3. Run the Tool

Run bean-price against your ledger file. It will:

  1. Find all commodities with a price source.
  2. Find the last date they were updated.
  3. Fetch the current price.
bean-price main.beancount

4. Save the Prices

Usually, you append the output to a separate prices file (e.g., prices.beancount) that you include in your main file.

In main.beancount:

include "prices.beancount"

Run command:

bean-price -e USD main.beancount >> prices.beancount

How to Run Reports

Beancount is a command-line tool. While you can use GUIs like Fava (a separate web interface for Beancount), understanding the CLI tools gives you the most power.

The Main Command: bean-report

The basic syntax is: bean-report [filename] [report-name]

1. balances (The Snapshot)

Shows the final balance of every account at the end of history.

bean-report main.beancount balances

Common Flags:

  • --at=2023-01-01: See what the balances were on a specific date.

2. income (The P&L)

Shows your profit and loss (Income and Expenses) for the period. By default, it calculates the total for the entire history of the file.

bean-report main.beancount income

Common Flags:

  • --begin=2023-01-01: Start calculating from this date.
  • --end=2024-01-01: Stop calculating at this date.

3. check

Validates the file (same as bean-check).

bean-report main.beancount check

The SQL Interface: bean-query

For custom reports, Beancount provides a SQL-like query language. This is incredibly powerful.

Start the interactive shell:

bean-query main.beancount

Then type your query:

Example 1: Find all coffee purchases

SELECT date, narration, amount
WHERE account ~ "Expenses:Food" AND narration ~ "Coffee"

Example 2: Monthly spending on Food

SELECT year(date), month(date), sum(position)
WHERE account ~ "Expenses:Food"
GROUP BY year(date), month(date)

Example 3: Export to CSV You can run a query directly from the command line and save it to a CSV file for Excel/Google Sheets.

bean-query main.beancount 'HBOX "SELECT * WHERE account ~ 'Assets' "' -f csv > assets.csv

Visualizing with Fava

While not part of the core Beancount tool, almost every user installs Fava. It is a web interface that runs locally.

For a detailed guide on using its charts and filters, see Using Fava (Web UI).

Query Cookbook

bean-query is a powerful command-line tool that lets you extract data from your Beancount file using a SQL-like language called BQL (Beancount Query Language).

Unlike standard SQL which queries a database on disk, bean-query loads your text file into memory and runs queries against the internal data structures.

Note: For simple text reports (like balances or income statements), see Running Reports instead.

How to use bean-query

1. Interactive Mode (Shell)

If you run the command without a query string, you enter an interactive shell. This is best for exploring data.

$ bean-query main.beancount
Input file: "main.beancount"
Ready with 1000 directives (1500 postings).
beancount> SELECT sum(position) WHERE account ~ "Assets"
...

Type help inside the shell to see available commands.

2. Command Line Mode (Scripting)

You can pass the query directly as a string. This is useful for scripts or quick checks.

bean-query main.beancount "SELECT sum(position) WHERE account ~ 'Assets'"

Core Concepts

Available Columns

Every transaction posting has specific columns you can query:

  • date: The date of the transaction.
  • account: The account name (e.g., Assets:Bank).
  • narration: The description text.
  • payee: The payee text.
  • position: The amount and currency (e.g., 10.00 USD).
  • balance: The cumulative balance after this posting.
  • tags: A set of tags attached to the transaction.
  • links: A set of links attached to the transaction.

Functions

BQL supports many functions to manipulate data:

  • year(date), month(date): Extract date parts.
  • root(account, 2): Get the parent account (e.g., Assets:Bank from Assets:Bank:Chase).
  • sum(position): Add up amounts (handles multiple currencies automatically).
  • abs(amount): Absolute value.

Spending Analysis

How much did I spend on Coffee this year?

SELECT sum(position)
WHERE account ~ "Expenses:Food:Coffee"
  AND year(date) = 2024

What are my top 10 expense categories?

SELECT account, sum(position) as total
WHERE account ~ "Expenses"
  AND year(date) = 2024
GROUP BY account
ORDER BY total DESC
LIMIT 10

Income Analysis

Monthly Income by Source

SELECT year(date), month(date), root(account, 3), sum(position)
WHERE account ~ "Income"
GROUP BY year(date), month(date), root(account, 3)
ORDER BY year(date), month(date)

Net Worth

Current Net Worth (Sum of Assets + Liabilities) Note: Since Liabilities are negative, summing them gives the net value.

SELECT sum(position)
WHERE account ~ "Assets|Liabilities"

Auditing

Find huge transactions (over $1000)

SELECT date, narration, account, position
WHERE abs(number(position)) > 1000
ORDER BY date DESC

Find transactions missing a tag

SELECT *
WHERE tag IS NULL

Year-End Procedures

Beancount uses Continuous Accounting, which means you do not need to close out your books at the end of the year like a traditional business. Your Income and Expense accounts just keep accumulating history.

However, many users like to perform a “Year-End Check” to lock in their history.

1. Verify Balances

On Dec 31st (or Jan 1st), log into all your banks and check the exact balance. Add a balance assertion for every asset and liability account.

2024-01-01 balance Assets:Bank:Checking   1234.56 USD
2024-01-01 balance Liabilities:Visa      -450.00 USD

This ensures that no future edits can accidentally break your history for 2023.

2. Generate Reports

Since you don’t zero out accounts, you must filter by date to see your annual performance.

# Income Statement for 2023
bean-report main.beancount income --year=2023

3. (Optional) “Clear” Equity

If you really want to zero out your Income/Expense accounts (simulating a “closing”), you can transfer their balances to Equity:Retained-Earnings.

Note: This is manual and usually discouraged in Beancount because it destroys the ability to run multi-year reports easily.

4. Archive Documents

If you store receipts, now is a good time to zip up the 2023/ folder.

Using Fava (Web UI)

Fava is the standard web interface for Beancount. It transforms your text file into an interactive financial dashboard.

Installation & Start

pip install fava
fava main.beancount

Open http://localhost:5000 in your browser.

Tip: Press ? anywhere in Fava to see keyboard shortcuts.


The Dashboard (Indicators)

The dashboard shows your Net Worth and upcoming events.

Up-to-Date Indicators

Fava can show colored dots next to accounts to indicate if they have been reconciled recently.

To enable this, add metadata to your open directive:

2000-01-01 open Assets:Bank:Chase
  fava-uptodate-indication: TRUE
  • 🟢 Green: The last entry is a passed balance check.
  • 🔴 Red: The last entry is a failed balance check.
  • 🟡 Yellow: The last entry is not a balance check.
  • Grey: No activity for a long time (default 60 days).

Filtering

Fava’s filter bar is extremely powerful.

Time Filter

Supports natural language and intervals:

  • 2024: The whole year.
  • 2024-Q1: First quarter.
  • 2024-03: March.
  • year - 1: Last year.
  • month: This month.

Account Filter

Accepts regular expressions to match account names:

  • Assets:US matches all US assets.
  • ^Assets matches only Assets (no Income/Expenses).

Advanced Filter

Filter by tags, links, metadata, or amounts.

  • #vacation: Only show transactions with this tag.
  • payee:"Starbucks": Filter by payee.
  • units > 100: Only transactions larger than 100.
  • any(account:"Expenses:Food"): Entries where at least one posting hits food.
  • all(-account:"Expenses"): Exclude all expenses.

The Editor

Fava includes a full Beancount text editor in the browser.

  • Auto-complete: Type an account name or payee to see suggestions.
  • Validation: Errors are highlighted in red immediately.
  • Formatting: Use Align Amounts to clean up your file.

Importing Data

Fava provides a GUI for beangulp importers.

  1. Configure your importers in python (see Writing Importers).
  2. Add the configuration to your file:
    custom "fava-option" "import-config" "my_import_config.py"
    custom "fava-option" "import-dirs" "/path/to/downloads"
    
  3. Go to the Import tab in Fava.
  4. Drag and drop CSV files onto the importer name.
  5. Fava will extract transactions and let you review/accept them before writing to your file.

Configuration Options

Fava has its own set of options defined using the custom directive.

Fiscal Year

Change the start of your year (e.g., UK tax year starts April 6).

custom "fava-option" "fiscal-year-end" "04-05"

Invert Signs

If you prefer to see Income/Liabilities as positive numbers (Net Profit view):

custom "fava-option" "invert-income-liabilities-equity" "true"

Language

Force a specific language (default is auto-detected).

custom "fava-option" "language" "fr"

Writing Importers with Beangulp

Beancount 3 uses a separate library called beangulp for importing data. Beangulp provides a framework to extract transactions from external files (like CSVs or PDFs) and convert them into Beancount directives.

Prerequisites

You need to install beangulp:

pip install beangulp

The Importer Protocol

To create a custom importer, you must subclass beangulp.Importer and implement these methods:

  1. identify(self, filepath): Returns True if this importer can handle the given file.
  2. extract(self, filepath, existing): Parses the file and returns a list of Beancount entries.
  3. account(self, filepath): Returns the account associated with this file (used for filing).
  4. date(self, filepath): Returns the date associated with this file (used for filing).

Example: A Simple CSV Importer

Let’s say you have a bank CSV that looks like this:

Date,Description,Amount
2024-01-15,Coffee Shop,-5.00
2024-01-16,Salary,3000.00

Here is a complete importer for this format.

import csv
from datetime import datetime
from beangulp import Importer
from beancount.core import data, amount
from decimal import Decimal

class MyBankImporter(Importer):
    def __init__(self, account_name):
        self.account_name = account_name

    def identify(self, filepath):
        # Only accept CSV files
        if not filepath.endswith(".csv"):
            return False
            
        # Peek at the header to be sure
        with open(filepath) as f:
            header = f.readline().strip()
        return header == "Date,Description,Amount"

    def account(self, filepath):
        return self.account_name

    def date(self, filepath):
        # Optional: Extract the date from the filename or the last transaction
        # For simplicity, we'll return the date of the last transaction in the file
        last_date = None
        with open(filepath) as f:
            reader = csv.DictReader(f)
            for row in reader:
                last_date = datetime.strptime(row['Date'], '%Y-%m-%d').date()
        return last_date

    def extract(self, filepath, existing=None):
        entries = []
        
        with open(filepath) as f:
            reader = csv.DictReader(f)
            for row in reader:
                # 1. Parse metadata
                date = datetime.strptime(row['Date'], '%Y-%m-%d').date()
                desc = row['Description']
                amt = Decimal(row['Amount'])
                
                # 2. Create the transaction
                meta = data.new_metadata(filepath, reader.line_num)
                txn = data.Transaction(
                    meta, date, "*", None, desc, data.EMPTY_SET, data.EMPTY_SET, []
                )

                # 3. Add postings
                # The main posting (the bank account)
                txn.postings.append(
                    data.Posting(self.account_name, amount.Amount(amt, "USD"), 
                               None, None, None, None)
                )
                
                # The other side (usually an expense placeholder)
                # We leave the amount empty so Beancount can balance it automatically
                txn.postings.append(
                    data.Posting("Expenses:Unknown", None, 
                               None, None, None, None)
                )

                entries.append(txn)
                
        return entries

Extracting Data from PDFs

Sometimes banks only provide PDF statements. Extracting data from them is harder but possible using tools like pdftotext.

1. Install Dependencies

You need the poppler-utils package installed on your system.

  • Debian/Ubuntu: sudo apt install poppler-utils
  • macOS: brew install poppler
  • Nix: nix-env -iA nixpkgs.poppler_utils

2. Calling pdftotext

Use Python’s subprocess to run the command and capture the output. The -layout flag is crucial as it preserves the visual structure of the table.

import subprocess

def extract_pdf_text(filepath):
    # Run pdftotext -layout input.pdf -
    # The '-' at the end tells it to print to stdout
    result = subprocess.run(
        ['pdftotext', '-layout', filepath, '-'], 
        stdout=subprocess.PIPE, 
        text=True
    )
    return result.stdout

3. Parsing the Output

Once you have the text string, you iterate over the lines and use Regular Expressions (Regex) to find transaction rows.

import re

# Example Regex for a line like: "Jan 01   Grocery Store   -50.00"
# (\w+ \d+)   : Date (Group 1)
# \s+         : Spaces
# (.*?)       : Description (Group 2)
# \s+         : Spaces
# (-?[\d\.]+) : Amount (Group 3)
TXN_REGEX = re.compile(r'(\w+ \d+)\s+(.*?)\s+(-?[\d\.]+)')

def parse_text(text):
    for line in text.splitlines():
        match = TXN_REGEX.search(line)
        if match:
            date_str, desc, amount_str = match.groups()
            # ... create transaction object ...

Running Your Import

Create a configuration script (e.g., import.py) to run your importers.

from beangulp import Ingest
import my_importers

ingest = Ingest()
ingest.add(my_importers.MyBankImporter("Assets:Bank:Checking"))

if __name__ == "__main__":
    ingest()

You can now run this script to generate beancount entries:

python3 import.py /path/to/downloaded.csv

Best Practices

  • Use beangulp.testing: Beangulp includes utilities to test your importers against sample files.
  • Smart Categorization: You can add logic in extract to guess the expense account based on the description (e.g., if “Coffee” in description, use Expenses:Food:Coffee).

Beancount 3 Syntax Reference

Beancount uses a precise but simple text format.

General Rules

  • Indentation: Postings must be indented (usually 2 spaces). Directives must start at the beginning of the line.
  • Comments: Any text following a ; is ignored.
  • Strings: Payees and Narrations must be in double quotes ".

Directives

1. option

Configures global settings.

option "title" "My Finances"

2. plugin

Loads Python extensions.

plugin "beancount.plugins.auto_accounts"

3. open

Initializes an account. Usually dated before the first transaction.

2000-01-01 open Assets:Bank:Chase

You can also restrict the currencies allowed in an account:

2000-01-01 open Assets:Bank:Chase USD, EUR

Syntax: open Account [Constraint1, Constraint2] ["BookingMethod"]

4. close

Marks an account as closed. The balance must be zero on this date.

2024-12-31 close Assets:Bank:OldAccount

5. commodity

Declares a currency or commodity. Optional, but useful for metadata.

1998-01-01 commodity USD
  name: "US Dollar"

6. price

Records the market price of a commodity on a specific date.

2024-06-01 price AAPL 155.00 USD

7. balance

Asserts that the balance of an account matches a number at the end of that day.

2024-01-01 balance Assets:Bank:Chase 1500.00 USD

8. pad

Automatically inserts a transaction to make the balance of an account match a future balance directive. The difference is booked to the source account provided.

; Calculate the difference needed to reach the next balance check
; and book it to Equity:Opening-Balances
2024-01-01 pad Assets:Bank:Chase Equity:Opening-Balances

2024-01-02 balance Assets:Bank:Chase 1500.00 USD

Use with caution! It effectively hides missing transactions.

9. custom

Used to define generic directives that can be handled by scripts or plugins.

2024-01-01 custom "budget" "Expenses:Food" "monthly" 500.00 USD

The Transaction Block

The most common block in the file.

Examples: See Recording Transactions for practical examples.

YYYY-MM-DD flag "Payee" "Narration" #tag ^link
  meta-key: meta-value
  Account1    Amount1 Currency
  Account2    Amount2 Currency
  • Flag: * (Completed) or ! (Incomplete).
  • Payee/Narration: At least one string is required. Beancount assumes the first string is the payee if there are two strings, or just the narration if there is only one.
  • Tags: #tagname. Used for categorization.
  • Links: ^linkname. Used to group transactions.
  • Metadata: Key-value pairs. Values can be strings, numbers, dates, or booleans.

Balancing Rules

Beancount enforces that every transaction sums to zero.

  • Amount Only: Weight is simply the amount (e.g., 10 USD).
  • Cost: Weight is Amount * Cost (e.g., 10 STOCK {100 USD} = 1000 USD).
  • Price: Weight is Amount * Price (e.g., 10 EUR @ 1.2 USD = 12 USD).
  • Cost & Price: Price is ignored for balancing. Weight is Amount * Cost.

Cost Basis {} and Price @

Used for trading and conversions.

  • {10.00 USD}: Cost. I bought this item for 10 USD.
  • @ 12.00 USD: Price. The market value of this item is 12 USD per unit.
  • @@ 120.00 USD: Total Price. The total value of the entire lot is 120 USD.

Example: Buying 10 shares at $10 each.

2024-01-01 * "Buy Stock"
  Assets:Brokerage      10 STOCK {10.00 USD}
  Assets:Cash         -100.00 USD

Options Reference

Global options configure how Beancount parses and validates your file.

Usage:

option "key" "value"

Essential Options

title

The title of your ledger, used in reports.

option "title" "My Personal Finances"

operating_currency

Defines the currencies that are “yours”. This affects how reports aggregate numbers. You can specify this multiple times.

option "operating_currency" "USD"
option "operating_currency" "EUR"

Parsing & Accuracy

tolerance

Sets the inferred tolerance for balancing transactions. Defaults to 0.005 for most currencies.

option "tolerance" "0.01"

account_rounding

Automatically inserts a rounding posting to an Equity account if a transaction is off by a tiny amount (due to currency conversion).

option "account_rounding" "Equity:Rounding"

Classification

documents

A list of directories where Beancount should look for external documents (PDFs) to link to transactions.

option "documents" "/home/user/finances/docs"

account_previous_balances

Beancount can calculate the balance of an account before the first transaction based on open date.

option "account_previous_balances" "Opening-Balances"

Formatting

render_commas

Whether to render numbers with thousands separators in reports (TRUE/FALSE).

option "render_commas" "TRUE"

plugin_processing_mode

“default” or “raw”. Determines when plugins run. usually “default”.

option "plugin_processing_mode" "default"

Root Account Naming (Localization)

You can rename the five standard root accounts to suit your language or preference.

name_assets, name_liabilities, name_equity, name_income, name_expenses

Override the default names.

option "name_assets"      "Actifs"
option "name_liabilities" "Passifs"
option "name_equity"      "Capitaux"
option "name_income"      "Revenus"
option "name_expenses"    "Depenses"

Beancount Query Language (BQL)

BQL is a SQL-like query language designed specifically for filtering and aggregating double-entry accounting data. It runs on the in-memory data structures of Beancount.

Query Structure

The basic syntax mirrors SQL:

SELECT <target1>, <target2>, ...
FROM <entry-filter>
WHERE <posting-filter>
GROUP BY <grouping1>, <grouping2>, ...
ORDER BY <order1>, <order2>, ...
LIMIT <number>

1. Data Sources (FROM)

The FROM clause selects which Entries (transactions) are considered. If an entry is excluded here, none of its postings will be visible to the rest of the query.

  • #tagname: Select entries with a specific tag.
  • ^linkname: Select entries with a specific link.
  • has_tag('tagname'): Standard function form.
  • has_link('linkname'): Standard function form.
  • RE("pattern"): Match narration or payee against a regex.

Example:

SELECT * FROM has_tag('vacation2024')

2. Filtering Postings (WHERE)

The WHERE clause filters individual Postings within the selected entries.

  • account: The account name (string).
  • currency: The commodity currency (string).
  • date: The date of the transaction.
  • year, month, day: Date components.
  • meta(key): Access metadata values.

Operators:

  • =, !=: Equality.
  • <, <=, >, >=: Comparison.
  • ~: Regular expression match (case-insensitive).
  • AND, OR, NOT: Logical operators.

Example:

SELECT * WHERE account ~ "Expenses:Food" AND currency = "USD"

3. Columns and Functions (SELECT)

Data Columns

These are the raw attributes available for every posting:

ColumnTypeDescription
dateDateDate of the transaction.
flagStringTransaction flag (* or !).
accountStringThe account name.
payeeStringThe payee name.
narrationStringThe transaction description.
positionPositionThe held amount (units + cost).
balancePositionRunning balance (only in journal mode).
idIDUnique internal ID.

Simple Functions

These functions operate on a single row/posting.

FunctionDescriptionExample
COST(pos)Get the cost inventory of a position.COST(position)
UNITS(pos)Get the number of units.UNITS(position)
CURRENCY(pos)Get the currency of the position.CURRENCY(position)
ABS(num)Absolute value.ABS(number)
NEG(num)Negate value.NEG(number)
PARENT(account)Get parent account name.PARENT(account)
ROOT(account, n)Get the n-th root component.ROOT(account, 1)
JOIN(str, ...)Join strings.JOIN(payee, narration)
META(key)Get metadata value.META('booking_id')
TODAY()Returns current date.date > TODAY()

Date Functions

FunctionDescription
YEAR(date)Extract year (integer).
MONTH(date)Extract month (integer).
DAY(date)Extract day (integer).
QUARTER(date)Format as “2024Q1”.

Aggregate Functions

These functions require a GROUP BY clause.

FunctionDescription
SUM(pos)Sums positions. Handles mixed currencies automatically.
COUNT(val)Counts number of rows.
MIN(val)Minimum value.
MAX(val)Maximum value.
FIRST(val)First value in the group sequence.
LAST(val)Last value in the group sequence.

4. Interactive Command Shell

You can run BQL queries interactively using the bean-query tool.

bean-query main.beancount

Inside the shell:

  • help: List available commands.
  • schema: Show available columns.
  • targets: Show available functions.

Common Recipes

Monthly Expenses by Category:

SELECT
  year(date) as year,
  month(date) as month,
  root(account, 2) as category,
  SUM(position)
WHERE
  account ~ "Expenses"
GROUP BY year, month, category
ORDER BY year, month, category

Last Reconciled Date per Account:

SELECT
  account,
  MAX(date) as last_reconciled
WHERE
  flag = "*"
GROUP BY account

Standard Plugins

Beancount includes several plugins that automate common tasks. To use them, add plugin "..." to your file.

beancount.plugins.auto_accounts

Description: Automatically creates open directives for accounts that are referenced but not defined. Usage: Great for lazy prototyping, but discouraged for strict accounting.

plugin "beancount.plugins.auto_accounts"

beancount.plugins.tag_pending

Description: Automatically adds a #pending tag to any transaction marked with the ! flag. Usage: clear visualization of what is not yet cleared.

plugin "beancount.plugins.tag_pending"

beancount.plugins.forecast

Description: Generates recurring transactions into the future for budgeting/planning. Usage: You define a “forecast” transaction, and the plugin projects it forward.

beancount.plugins.check_commodity

Description: Ensures that all commodities used in an account match the open directive restrictions. Usage: Strict validation.

2000-01-01 open Assets:Bank:US USD
; This will fail if you try to put EUR in it

Beancount 3 Command Line Interface

Beancount 3 provides several tools to interact with your ledger files.

bean-check

This is the most important command. It checks your file for syntax errors and balance mistakes.

bean-check my_ledger.beancount

If there are no errors, the command will produce no output.

bean-report

Generates reports from your data. Common reports include:

  • balances: Shows the balance of all accounts.
  • income: Shows your income and expenses.
bean-report my_ledger.beancount balances

bean-query

Allows you to run SQL-like queries on your transactions. This is very powerful for custom analysis.

bean-query my_ledger.beancount "SELECT date, narration, amount WHERE account ~ 'Expenses'"

bean-format

Automatically cleans up your ledger file by aligning amounts and sorting directives where possible.

bean-format my_ledger.beancount

Note: Beancount 3 focuses on performance and correctness. Always ensure you are running the version compatible with your installation by checking beancount --version.

Double-Entry Principles

Beancount enforces Double-Entry Bookkeeping. This system, codified by Luca Pacioli in 1494, is the standard for businesses worldwide because it is the only way to ensure mathematical consistency in financial records.

The fundamental rule: Sum = 0

In single-entry accounting (like a simple checkbook register), you just write down “Spent $50”. But where did that $50 come from? Did you pay cash? Credit card? Gift card?

In double-entry, you must describe the movement of value.

  • Value came FROM somewhere.
  • Value went TO somewhere.

Beancount models this mathematically: The sum of all postings in a transaction must be zero.

If you move $10 from your Left Pocket (-10) to your Right Pocket (+10), the total change in your wealth is zero (-10 + 10 = 0).

The Five Buckets

Everything in accounting fits into one of five buckets. In Beancount, we use positive and negative numbers to represent these, which simplifies the math.

CategoryNormal SignInterpretation
AssetsPositive (+)Money you have (Cash, Bank, House)
LiabilitiesNegative (-)Money you owe (Credit Cards, Loans)
IncomeNegative (-)Money you earned (Salary, Interest)
ExpensesPositive (+)Money you spent (Food, Rent)
EquityNegative (-)Your Net Worth (Assets - Liabilities)

Why is Income negative?

This is often the most confusing part for beginners. Think of it this way:

Income is money leaving the “External World” and entering “Your World”.

When your employer pays you $1000:

  1. They lose $1000. Beancount tracks this as Income:Salary with a value of -1000.
  2. You gain $1000. Your Assets:Checking goes up by +1000.

-1000 (Income) + 1000 (Asset) = 0. The transaction balances.

Why are Expenses positive?

When you buy coffee for $5:

  1. You lose $5 cash. Assets:Cash is -5.
  2. You gain $5 worth of “Coffee Value”. Expenses:Food is +5.

-5 (Asset) + 5 (Expense) = 0.

The Equity Equation

Because all transactions sum to zero, the sum of ALL accounts in your file must also equal zero at all times.

Assets + Liabilities + Income + Expenses + Equity = 0

If we rearrange this, we get the definition of Net Worth:

(Assets + Liabilities) + (Income + Expenses) + Equity = 0

  • (Assets + Liabilities) is your current wealth.
  • (Income + Expenses) is your profit for the year.
  • Equity is your historical wealth.

Continuous Accounting

Traditional accounting software (and corporate accounting) relies on Fiscal Years. At the end of the year, accountants “close the books.”

This involves:

  1. Summing up all Income and Expenses.
  2. Moving that net profit/loss to Equity:Retained-Earnings.
  3. Resetting Income and Expenses to zero for Jan 1st.

Why Beancount is Different

Beancount uses Continuous Accounting. It never resets accounts to zero.

Advantages

  1. Multi-Year Reporting: You can run a report for “The last 18 months” or “2010 to 2020” instantly. In a closed system, this requires stitching together multiple closed files.
  2. Simplicity: You don’t have to create complex “closing entries” every year.
  3. Data Integrity: Your file is a single, unbroken stream of history.

How it works

When you want to see your income for just 2024, you don’t look at the raw balance (which is “All Income since the beginning of time”). Instead, you ask Beancount to calculate the change in balance between Jan 1, 2024, and Dec 31, 2024.

CLI tools do this automatically when you pass --year=2024.

Trade vs. Settlement Dates

In financial markets, a transaction happens in two distinct stages:

  1. Trade Date (T): The legal agreement to buy/sell is executed. The price is fixed.
  2. Settlement Date (T+2): The legal ownership is transferred and cash is exchanged.

The Accounting Dilemma

This delay creates a gap in your records.

  • On Date T: You “own” the stock risk (if price moves, you gain/lose), but you still have the cash in your account.
  • On Date T+2: The cash actually leaves.

Which date should you use for your books?

Model 1: Trade Date Accounting (Standard)

Beancount (and the IRS) generally favors Trade Date Accounting.

  • Concept: You record the asset change immediately on Day T.
  • Reality: Your Cash account in Beancount will show a lower balance than your actual bank balance for 2 days.
  • Why: Tax liability usually attaches on the Trade Date. The holding period (Long vs Short term capital gains) starts counting from the Trade Date.

Model 2: Settlement Date Accounting (Cash Basis)

Some users prefer to wait until the cash moves.

  • Concept: You ignore the trade until T+2.
  • Problem: Your Portfolio Value is wrong for 2 days. You might miss tax deadlines if the trade happens on Dec 31st but settles Jan 2nd.

Model 3: Full Accrual (The “Pending” Asset)

To be perfectly accurate during the gap, you must recognize that you owe money (a Payable) or are owed money (a Receivable).

On Trade Date: You don’t lose Cash yet; you gain a specific liability (“Settlement Payable”).

2024-06-01 * "Buy Stock"
  Assets:Brokerage:Stock       10 AAPL {100.00 USD}
  Liabilities:Brokerage:Settlement    -1000.00 USD

On Settlement Date: The liability is extinguished by Cash.

2024-06-03 * "Settlement"
  Liabilities:Brokerage:Settlement     1000.00 USD
  Assets:Brokerage:Cash               -1000.00 USD

While Model 3 is the most “correct” for an auditor, Model 1 (Trade Date) is the standard recommendation for personal finance in Beancount because it simplifies data entry significantly.

How Inventories Work

In many accounting systems, currencies are simple numbers. In Beancount, any account can hold “Inventory.”

What is Inventory?

Think of an inventory as a mixed bag of items. A single account doesn’t just hold “Dollars.” It can hold:

  • 100 USD
  • 50 EUR
  • 10 shares of GOOG (bought at $100)
  • 5 shares of GOOG (bought at $120)

Beancount tracks each of these “lots” separately.

Cost Basis: The Specific Lot

When you acquire a commodity (like stock), Beancount remembers the cost.

2024-01-01 * "Buy"
  Assets:Brokerage    10 GOOG {100.00 USD}

This creates a specific lot in your inventory: 10 GOOG @ 100 USD.

If you buy more later:

2024-02-01 * "Buy More"
  Assets:Brokerage    5 GOOG {120.00 USD}

Your Assets:Brokerage account now holds two distinct lots. It does not hold “15 GOOG at average cost $106”. It holds the specific lots.

Reduction (Selling)

When you sell, you must specify which lot you are reducing. Beancount needs to know this to calculate the Capital Gain correctly.

How-to: For step-by-step instructions, see Recording Trades.

2024-03-01 * "Sell"
  Assets:Brokerage    -5 GOOG {100.00 USD} @ 130.00 USD

Here, we explicitly told Beancount “Sell 5 shares from the lot that cost $100”.

  • Cost: 5 * 100 = 500
  • Price: 5 * 130 = 650
  • Profit: 150

If you tried to sell {-5 GOOG} without specifying the cost {...}, Beancount wouldn’t know which lot to pick and would raise an error (unless you use a booking method like FIFO, which Beancount 3 can automate in some contexts).

Strict vs Average Booking

Beancount uses a Strict Booking model by default, often called “Specific Identification” in tax terms.

Why Strict?

In many jurisdictions (like the US), the tax authority allows you to choose which shares you are selling.

  • Optimization: If you sell shares with a high cost basis, you realize a smaller capital gain (and pay less tax now).
  • Control: If you want to hold onto your oldest shares (for long-term capital gains rates), you need to explicitly sell the newer ones.

If Beancount automatically averaged your costs (e.g., merging the $100 and $120 lots into one $106.66 lot), you would lose this ability to optimize and report correctly.

What about Average Cost?

Some countries (like Canada or the UK) require you to use the Average Cost Basis (ACB) method. Beancount 3 handles this by allowing you to merge lots.

  • Manual: You can use a transaction to “convert” multiple specific lots into a single average lot, though this is tedious.
  • Automatic: Beancount 3 introduces flexible booking methods that can simulate average cost behavior during reduction, or you can use plugins to enforce it.

However, even with Average Cost, the underlying database in Beancount prefers strictness to ensure auditability. You can always derive an average from specific lots, but you cannot recover specific lots if you strictly averaged them on input.

Rounding and Precision

Computers often struggle with decimal numbers (e.g., 0.1 + 0.2 might equal 0.30000000000000004). Beancount avoids this by using Decimal types, not floating-point numbers.

Precision Inference

Beancount does not have a hardcoded list of currencies. Instead, it infers the precision from your file.

If you write:

2024-01-01 * "Test"
  Assets:Cash   10.500 USD

Beancount will assume USD has 3 decimal places for the entire file. Best Practice: Always be consistent. Use 10.00 USD, not 10 USD.

The Tolerance Problem

In real life, splitting bills or currency conversion often results in fractions of a cent.

Example: Splitting $10.00 three ways.

  • Person A: 3.33
  • Person B: 3.33
  • Person C: 3.33
  • Total: 9.99

The transaction is off by 0.01.

Automatic Tolerance

Beancount allows a small tolerance (default 0.005) for transactions to balance. If the difference is within this tolerance, Beancount accepts it.

Accumulating Rounding Errors

If you want to track these discrepancies explicitly (or if they are larger than the tolerance), use the account_rounding option.

option "account_rounding" "Equity:Rounding"

When this is enabled, Beancount will automatically add a posting to Equity:Rounding to make the transaction balance perfectly.

2024-01-01 * "Split Bill"
  Expenses:Food       10.00 USD
  Assets:A           -3.33 USD
  Assets:B           -3.33 USD
  Assets:C           -3.33 USD
  ; Implicitly adds:
  ; Equity:Rounding  -0.01 USD

Why Beancount 3?

Beancount 3 represents a significant evolution of the Beancount project. While the syntax remains largely backward-compatible with v2, the underlying engine and ecosystem have been completely overhauled.

This guide outlines the key improvements and differences for users migrating from v2 or those curious about the architecture.

1. Core Architecture & Performance (The C++ Rewrite)

The most significant change in v3 is the move from a pure Python implementation to a C++ core.

  • v2: Was primarily Python, with some C extensions for speed. As ledgers grew (with 10+ years of history), loading times could become noticeable (taking seconds to minutes).
  • v3: The core logic (parsing, booking, interpolation) is written in C++.

This results in:

  • Drastic Performance Gains: Loading and checking large files is orders of magnitude faster.
  • Scalability: Beancount 3 can handle massive transaction volumes (decades of data) without UI lag or slow reporting.

2. Strictness, Correctness, and Type Safety

Beancount 3 is stricter about accounting invariants. The rewrite allowed the authors to tighten the rules and ensure calculations are even more precise—critical for financial software.

  • Lot Matching: The logic for matching lots (e.g., selling stocks) is more robust.
  • Sign Handling: Ambiguities in sign handling (mixed positive/negative numbers in confusing contexts) are reduced or disallowed.
  • Precision: Floating-point issues are further minimized by the C++ decimal implementation.

3. Focus on the Core & Modular Ecosystem

Beancount 3 focuses on doing the core accounting tasks perfectly. It adopts a more modular philosophy, separating the core engine from auxiliary tools.

Importing Data (Beangulp)

In Beancount v2, the importing framework (beancount.ingest) was bundled directly into the main package. In v3, ingestion is split into a separate project called Beangulp.

  • This separates the concerns of ledger logic from data extraction logic.
  • Beangulp provides a cleaner API and better tools for deduplicating transactions.
  • Existing importers will likely need minor updates to work with Beangulp.

Modern Tooling

Beancount 3 is designed to work better with modern Python environments.

  • Fava: The popular web UI continues to support Beancount 3.
  • Bean-query: The SQL engine has been refined, though the syntax (BQL) remains mostly the same.
  • API Stability: For developers building tools on top of Beancount, v3 provides a more stable, typed API (via Python bindings to the C++ core).

4. Installation and Distribution

Because of the C++ core, installation is slightly different than the pure Python days of v2.

  • v2: Simple pip install beancount.
  • v3: Requires compiling the C++ core or using pre-built binary wheels.
    • The build system uses Bazel, ensuring reproducible builds.
    • Most users should stick to official binary releases to avoid complex compilation steps.

Summary for Migrating Users

  1. Your .beancount files likely don’t need changes. The syntax is stable.
  2. Your importers will need attention. You will likely need to migrate to Beangulp.
  3. Your scripts might break. Internal Python API changes will affect custom tools.
  4. It will be much faster.