Skip to content

Budgeting

Methods for tracking budgets and comparing against actual spending in beancount.

Approach 1: Query-Based Budgeting

The simplest approach - define budgets externally and query actuals.

Define Your Budget

Create a reference file or spreadsheet:

CategoryMonthly Budget
Food:Groceries$400
Food:Restaurants$150
Transport$200
Entertainment$100

Query Actuals

bash
# This month's spending by category
rledger query ledger.beancount "
  SELECT root(account, 2) AS category, sum(cost(position)) AS spent
  WHERE account ~ 'Expenses'
    AND year(date) = year(today())
    AND month(date) = month(today())
  GROUP BY category
  ORDER BY spent DESC
"

Compare in Shell Script

bash
#!/bin/bash
# budget-check.sh

LEDGER="ledger.beancount"
MONTH=$(date +%Y-%m)

echo "Budget vs Actual for $MONTH"
echo "============================"

# Food:Groceries - Budget $400
actual=$(rledger query "$LEDGER" "
  SELECT sum(cost(position))
  WHERE account ~ 'Expenses:Food:Groceries'
    AND year(date) = year(today())
    AND month(date) = month(today())
" -f csv | tail -1)

echo "Groceries:    \$${actual} / \$400"

# Add more categories...

Approach 2: Budget Accounts

Track budgets within beancount using special accounts.

Setup

beancount
; Budget accounts
2024-01-01 open Assets:Budget:Food
2024-01-01 open Assets:Budget:Transport
2024-01-01 open Assets:Budget:Entertainment
2024-01-01 open Income:Budget:Allocation

Allocate Budget Monthly

At the start of each month, allocate your budget:

beancount
2024-01-01 * "January budget allocation"
  Assets:Budget:Food            550.00 USD
  Assets:Budget:Transport       200.00 USD
  Assets:Budget:Entertainment   100.00 USD
  Income:Budget:Allocation     -850.00 USD

Track Spending Against Budget

When you spend, reduce both your real account and budget:

beancount
2024-01-15 * "Grocery Store"
  Expenses:Food:Groceries    85.00 USD
  Assets:Bank:Checking      -85.00 USD
  ; Track against budget
  Assets:Budget:Food        -85.00 USD
  Expenses:Budget:Food       85.00 USD

Check Remaining Budget

bash
rledger query ledger.beancount "
  SELECT account, sum(position)
  WHERE account ~ 'Assets:Budget'
  GROUP BY account
"

Output:

Assets:Budget:Entertainment    75.00 USD
Assets:Budget:Food            120.00 USD
Assets:Budget:Transport       150.00 USD

Approach 3: Envelope Budgeting

A variation where you "move" money into envelopes.

Setup

beancount
; Envelope accounts (virtual, for budgeting)
2024-01-01 open Assets:Envelopes:Groceries
2024-01-01 open Assets:Envelopes:Dining
2024-01-01 open Assets:Envelopes:Gas
2024-01-01 open Assets:Envelopes:Fun

; Holding account for unallocated funds
2024-01-01 open Assets:Envelopes:Unallocated

Fund Envelopes from Income

beancount
2024-01-15 * "Paycheck"
  Assets:Bank:Checking      3500.00 USD
  Income:Salary            -3500.00 USD

2024-01-15 * "Fund envelopes"
  Assets:Envelopes:Groceries      400.00 USD
  Assets:Envelopes:Dining         150.00 USD
  Assets:Envelopes:Gas            200.00 USD
  Assets:Envelopes:Fun            100.00 USD
  Assets:Envelopes:Unallocated   2650.00 USD
  Assets:Bank:Checking          -3500.00 USD  ; Virtual transfer

Wait - this double-counts the money! Let's fix that.

Better: Use Virtual Accounts

Instead, track envelopes separately from real money:

beancount
2024-01-15 * "Allocate to envelopes" ^budget-jan
  Assets:Envelopes:Groceries      400.00 USD
  Assets:Envelopes:Dining         150.00 USD
  Assets:Envelopes:Gas            200.00 USD
  Assets:Envelopes:Fun            100.00 USD
  Equity:Budgeted                -850.00 USD

When you spend:

beancount
2024-01-20 * "Grocery Store"
  Expenses:Food:Groceries    85.00 USD
  Assets:Bank:Checking      -85.00 USD
  ; Reduce envelope
  Equity:Budgeted            85.00 USD
  Assets:Envelopes:Groceries -85.00 USD

Approach 4: Custom Metadata

Tag transactions with budget categories:

beancount
2024-01-15 * "Grocery Store"
  budget-category: "food"
  Expenses:Food:Groceries   85.00 USD
  Assets:Bank:Checking

2024-01-16 * "Gas Station"
  budget-category: "transport"
  Expenses:Transport:Gas    45.00 USD
  Assets:Bank:Checking

Query by budget category:

bash
rledger query ledger.beancount "
  SELECT sum(cost(position))
  WHERE meta('budget-category') = 'food'
    AND month(date) = 1 AND year(date) = 2024
"

Monthly Budget Review

Summary Report Script

bash
#!/bin/bash
# monthly-review.sh

LEDGER="${LEDGER_FILE:-ledger.beancount}"
YEAR=$(date +%Y)
MONTH=$(date +%m)

echo "=== Monthly Budget Review: $YEAR-$MONTH ==="
echo ""

# Income
echo "INCOME"
rledger query "$LEDGER" "
  SELECT root(account, 2), sum(cost(position)) AS amount
  WHERE account ~ 'Income'
    AND year(date) = $YEAR AND month(date) = $MONTH
  GROUP BY 1
  ORDER BY amount
"

echo ""
echo "EXPENSES BY CATEGORY"
rledger query "$LEDGER" "
  SELECT root(account, 2), sum(cost(position)) AS amount
  WHERE account ~ 'Expenses'
    AND year(date) = $YEAR AND month(date) = $MONTH
  GROUP BY 1
  ORDER BY amount DESC
"

echo ""
echo "NET INCOME"
rledger query "$LEDGER" "
  SELECT sum(cost(position)) AS net
  WHERE (account ~ 'Income' OR account ~ 'Expenses')
    AND year(date) = $YEAR AND month(date) = $MONTH
"

Year-to-Date Comparison

sql
-- Query each month separately
SELECT root(account, 2) AS category, sum(cost(position)) AS total
WHERE account ~ "Expenses" AND year(date) = 2024 AND month(date) = 1
GROUP BY category ORDER BY category

SELECT root(account, 2) AS category, sum(cost(position)) AS total
WHERE account ~ "Expenses" AND year(date) = 2024 AND month(date) = 2
GROUP BY category ORDER BY category

Savings Goals

Track progress toward savings goals:

beancount
2024-01-01 open Assets:Savings:Emergency
2024-01-01 open Assets:Savings:Vacation
2024-01-01 open Assets:Savings:House

; Monthly allocation
2024-01-15 * "Savings allocation"
  Assets:Savings:Emergency   200.00 USD
  Assets:Savings:Vacation    150.00 USD
  Assets:Savings:House       300.00 USD
  Assets:Bank:Checking      -650.00 USD

Check progress:

bash
rledger query ledger.beancount "
  SELECT account, sum(position) AS balance
  WHERE account ~ 'Assets:Savings'
  GROUP BY account
"

Tips

1. Keep It Simple

Start with query-based budgeting. Only add complexity if needed.

2. Review Regularly

Set a monthly reminder to review spending vs budget.

3. Adjust Budgets

Your first budget will be wrong. Adjust based on actual spending patterns.

4. Use Rolling Averages

Instead of fixed budgets, track 3-month averages:

sql
-- Get last 3 months of expenses (adjust date range as needed)
SELECT root(account, 2),
       sum(cost(position)) / 3 AS monthly_avg
WHERE account ~ "Expenses"
  AND date >= 2024-01-01 AND date < 2024-04-01
GROUP BY 1
ORDER BY 2 DESC

5. Separate Needs vs Wants

Use account hierarchy:

Expenses:Needs:Housing
Expenses:Needs:Utilities
Expenses:Needs:Groceries
Expenses:Wants:Dining
Expenses:Wants:Entertainment
Expenses:Wants:Shopping

Query each:

sql
SELECT sum(cost(position))
WHERE account ~ "Expenses:Needs"

See Also