Recording Taxes in Beancount (The Pragmatic Way)
Taxes can feel like a special, complicated beast in the world of personal finance. But what if they weren't? What if you could treat them just like any other flow of money in your ledger? Good news: you can. By treating taxes as simple movements of value, your Beancount ledger will stay clean, easy to query, and—most importantly—understandable.
Below is a practical, no-nonsense pattern you can drop into a personal or small-business Beancount file. It’s a simple system for handling paychecks, tax payments, and even those pesky refunds that cross over into the new year. We'll cover the essential accounts you need, walk through real-world examples, and show you the exact queries to run to get the answers you need.
The Core Principles
Before we dive into the code, let's agree on a few simple rules. These principles keep things logical and prevent future headaches.
-
Separate "what it is" from "when the cash moves." 🗓️ This is the most important concept. A tax expense belongs to the year you earned the income (e.g., 2024), even if you settle the bill with the IRS in April 2025. If you don't separate the timing of the expense from the timing of the cash payment, your year-over-year reports will get messy and misleading.
-
Keep your account hierarchy boring and simple. 📁 Name your accounts clearly based on the type of tax (e.g.,
IncomeTax
,SocialSecurity
). This makes your queries incredibly simple. Don't clutter account names with vendor names or form numbers like "W-2" or "1099"; use metadata and tags for those details instead. -
Embrace accrual for year-end adjustments. ⚖️ Even for a personal ledger, using a simple accrual entry at year-end is the cleanest way to make your reports accurate. It means recognizing an expense or refund in the correct year, even if the money doesn't move until the next. It’s one small extra step that saves you from mental gymnastics later.
-
Write for your future self. 🧠 Your goal is clarity. Only add extra details, like the tax year, to an account name if it genuinely makes your queries easier. Avoid creating a new set of accounts every single year (
Expenses:Taxes:2024:Federal
,Expenses:Taxes:2025:Federal
, etc.) unless you have a compelling reason. A flat structure is often easier to manage.
A Minimal Account Skeleton
Here’s a basic set of accounts to get you started. This structure is US-centric, but you can easily adapt the names for your own country's tax system. Just drop these open
directives into your Beancount file.
; --- US Federal Income & Payroll Taxes ---
; For money withheld from your paycheck
2024-01-01 open Expenses:Taxes:Federal:IncomeTax:Withheld USD
; For estimated payments or tax-day bills you pay directly
2024-01-01 open Expenses:Taxes:Federal:IncomeTax:Payments USD
; For tax refunds you receive
2024-01-01 open Expenses:Taxes:Federal:IncomeTax:Refunds USD
; Your FICA contributions
2024-01-01 open Expenses:Taxes:Federal:SocialSecurity USD
2024-01-01 open Expenses:Taxes:Federal:Medicare USD
; --- Other Common Taxes ---
; For sales/use taxes you pay on purchases
2024-01-01 open Expenses:Taxes:Sales USD
; --- Accounts for Year-End Adjustments (Optional but Recommended!) ---
; A temporary holding account for taxes you owe but haven't paid yet
2024-01-01 open Liabilities:AccruedTaxes:Federal:Income USD
; A temporary holding account for a refund you're owed but haven't received
2024-01-01 open Assets:Tax:Receivable USD
This setup separates withheld taxes from direct payments and refunds, making it easy to see exactly where your money went. The Liabilities
and Assets
accounts are our secret weapon for keeping year-end reporting accurate.
Example 1: The Paycheck
Let's book a typical paycheck where taxes are withheld automatically. The key is to record your gross pay first, then show how it was split between taxes and the cash that actually landed in your bank account.
2025-07-15 * "Employer Inc." "Salary for first half of July"
Income:Work:Salary -6,000.00 USD
Expenses:Taxes:Federal:IncomeTax:Withheld 1,200.00 USD
Expenses:Taxes:Federal:SocialSecurity 372.00 USD
Expenses:Taxes:Federal:Medicare 87.00 USD
Assets:Cash:Checking 4,341.00 USD
This single transaction tells the whole story:
- You earned $6,000 in gross income.
- $1,200 of it was sent to the IRS for federal income tax.
- 87 to Medicare.
- The remaining $4,341 is what you took home.
Pro-tip: You can attach metadata from your pay stub (like pay_period_end: "2025-07-15"
) to the transaction for an easy audit trail.
Example 2: Filing Your Return (The Year-Crossing Problem)
Here's the scenario that trips people up: It's April 2025, and you're filing your 2024 taxes. You learn that after all your withholding, you still owe an extra $3,000.
How do you record this? You want the expense to count toward 2024, but the cash payment happens in 2025. Here are two excellent ways to handle it.
Option A: The Manual Two-Step Accrual
This method is pure Beancount, no plugins required. It's a clear, two-step process.
Step 1: Recognize the expense at the end of the tax year. On the last day of 2024, you create a "true-up" entry. No cash is moving yet; you're just acknowledging the expense and parking it in a temporary liability account.
2024-12-31 * "Federal income tax true-up for 2024"
Expenses:Taxes:Federal:IncomeTax:Payments 3,000.00 USD
Liabilities:AccruedTaxes:Federal:Income -3,000.00 USD
Now, your 2024 income statement correctly shows this $3,000 expense.
Step 2: Record the cash payment when it happens. In April 2025, when you actually send the money to the IRS, you clear out the liability.
2025-04-15 * "IRS" "Payment for 2024 tax return"
Liabilities:AccruedTaxes:Federal:Income 3,000.00 USD
Assets:Cash:Checking -3,000.00 USD
Your 2024 reports are correct, and your 2025 cash flow is correct. Perfect! This same pattern works in reverse for a refund—just use Assets:Tax:Receivable
instead of the liability account.
Option B: Automate It with a Plugin
If you prefer to keep the payment in a single transaction, a fantastic community plugin called beancount_reds_plugins.effective_date
can help. It lets you assign a different "effective date" to a single line item.
First, enable the plugin in your main Beancount file:
plugin "beancount_reds_plugins.effective_date"
Now, you can write a single transaction. The plugin will automatically split it behind the scenes to make your reports accurate.
; One entry; the plugin handles the rest
2025-04-15 * "IRS" "Payment for 2024 tax return"
Assets:Cash:Checking -3,000.00 USD
Expenses:Taxes:Federal:IncomeTax:Payments 3,000.00 USD
effective_date: 2024-12-31
Here, the cash portion is recorded on April 15, 2025, but the expense portion is retroactively applied to December 31, 2024. It achieves the same result as Option A with a different workflow.
What About Sales Tax?
For most personal ledgers, sales tax is simple. If you're not claiming it back, just split it out as its own expense during a purchase.
2025-07-19 * "Local Grocery Store"
Expenses:Groceries 12.32 USD
Expenses:Taxes:Sales 1.28 USD
Assets:Cash:Checking -13.60 USD
This lets you easily track how much you're spending on sales tax over the year. If you run a business that deals with VAT, you'd use a more formal system with payable and receivable accounts, but the principle is the same.
Queries You'll Actually Run
The whole point of this structure is to make getting answers easy. Here are some BQL queries to see your tax picture.
1. What was my total federal income tax for 2024?
SELECT cost(sum(position))
WHERE account ~ "Expenses:Taxes:Federal:IncomeTax"
AND date >= 2024-01-01 AND date < 2025-01-01;
2. How did that total break down between withholding, payments, and refunds?
SELECT account, cost(sum(position))
WHERE account ~ "Expenses:Taxes:Federal:IncomeTax"
AND date >= 2024-01-01 AND date < 2025-01-01
GROUP BY account
ORDER BY account;
3. Do I have any outstanding tax debts or receivables? (Useful for checking your work!)
SELECT account, units(sum(position))
WHERE account ~ "Liabilities:AccruedTaxes" OR account ~ "Assets:Tax"
GROUP BY account
ORDER BY account;
If this query returns non-zero balances, it means you have accruals you haven't settled yet.
Quick FAQ
-
Do I really need per-year accounts like
Expenses:Taxes:2024
? Probably not. The accrual method (or the plugin) keeps a flat account structure clean and readable. Only create per-year accounts if you find it makes your specific queries easier to write. -
Can Beancount calculate my taxes for me? Not directly, but it can prepare the data. Some advanced users write scripts to pipe BQL query results into tax calculation software, which is great for estimating your liability during the year.
-
Is this tax advice? No. This is a bookkeeping pattern for organizing your data. The accounting is sound, but you should always consult a tax professional for advice specific to your situation.
Your Drop-In Checklist
Ready to get started?
- ✅ Add the account skeleton to your Beancount file (and adapt names for your country).