Envelope Budgeting in Beancount - Using Virtual Accounts for Budget Categories

What Is Envelope Budgeting?

For those unfamiliar, envelope budgeting is a system where you allocate your income into specific envelopes (categories) at the start of each month. You can only spend what is in each envelope. When the envelope is empty, you stop spending in that category or you explicitly move money from another envelope. It is one of the oldest and most effective budgeting methods, and it translates surprisingly well to double-entry accounting.

The challenge in Beancount is that it does not support virtual postings like Ledger or hledger do. But after four years of experimenting, I have found a clean approach using dedicated budget accounts.

The Account Structure

The key insight is to create a parallel set of budget accounts under a top-level Budget account:

2026-01-01 open Budget:Groceries
2026-01-01 open Budget:Restaurants
2026-01-01 open Budget:Utilities
2026-01-01 open Budget:Transportation
2026-01-01 open Budget:Entertainment
2026-01-01 open Budget:Clothing
2026-01-01 open Budget:Emergency
2026-01-01 open Budget:Savings
2026-01-01 open Budget:Available

Monthly Allocation

When you receive income, first record the actual income transaction as normal:

2026-02-01 * "Employer" "Monthly salary"
  Assets:BankOfAmerica:Checking    5200.00 USD
  Income:Salary                   -5200.00 USD

Then create an allocation transaction that distributes money into your envelopes:

2026-02-01 * "Budget Allocation" "February 2026 envelope funding"
  Budget:Available          -4200.00 USD
  Budget:Groceries             600.00 USD
  Budget:Restaurants           200.00 USD
  Budget:Utilities             300.00 USD
  Budget:Transportation        250.00 USD
  Budget:Entertainment         150.00 USD
  Budget:Clothing              100.00 USD
  Budget:Emergency             200.00 USD
  Budget:Savings              2400.00 USD

The remaining 1000 from the 5200 salary goes to taxes and benefits. The Budget:Available account acts as your unallocated funds pool.

Tracking Spending Against Envelopes

When you spend money, record the actual expense AND deduct from the corresponding envelope:

2026-02-05 * "Trader Joes" "Weekly groceries"
  Expenses:Food:Groceries      87.34 USD
  Assets:BankOfAmerica:Checking

2026-02-05 * "Budget Tracking" "Groceries envelope deduction"
  Budget:Groceries            -87.34 USD
  Budget:Available             87.34 USD

Recording two transactions per purchase is tedious. That is where automation comes in. I use a Beancount plugin that automatically generates the budget tracking entry:

from beancount.core import data, amount
from beancount.core.number import ZERO

ENVELOPE_MAP = {
    "Expenses:Food:Groceries": "Budget:Groceries",
    "Expenses:Food:Restaurants": "Budget:Restaurants",
    "Expenses:Housing:Utilities": "Budget:Utilities",
    "Expenses:Transport": "Budget:Transportation",
    "Expenses:Entertainment": "Budget:Entertainment",
    "Expenses:Clothing": "Budget:Clothing",
}

def envelope_tracker(entries, options_map):
    new_entries = list(entries)
    for entry in entries:
        if not isinstance(entry, data.Transaction):
            continue
        for posting in entry.postings:
            budget_account = None
            for expense_prefix, envelope in ENVELOPE_MAP.items():
                if posting.account.startswith(expense_prefix):
                    budget_account = envelope
                    break
            if budget_account and posting.units.number > ZERO:
                meta = data.new_metadata("<envelope>", 0)
                budget_txn = data.Transaction(
                    meta, entry.date, "*",
                    "Budget Tracking",
                    f"Auto-deduct from {budget_account}",
                    frozenset(), frozenset(),
                    [
                        data.Posting(budget_account,
                            amount.Amount(-posting.units.number,
                            posting.units.currency),
                            None, None, None, None),
                        data.Posting("Budget:Available",
                            amount.Amount(posting.units.number,
                            posting.units.currency),
                            None, None, None, None),
                    ]
                )
                new_entries.append(budget_txn)
    return new_entries, []

Checking Your Envelope Balances

Query your remaining budget at any time using bean-query:

bean-query main.beancount "SELECT account, sum(position) WHERE account ~ 'Budget' GROUP BY account"

The fava-envelope Alternative

There is also the fava-envelope extension by polarmutex on GitHub, which provides envelope budgeting through a Fava web interface with CSV-based budget definitions. The advantage is a nice visual dashboard without writing plugin code. The disadvantage is that your budget data lives outside your ledger file.

Handling Overspending and Rollovers

The beauty of this system is that overspending is immediately visible. If Budget:Groceries goes negative, you know you have overdrawn that envelope. You can reallocate:

2026-02-20 * "Budget Reallocation" "Move funds from entertainment to groceries"
  Budget:Entertainment    -50.00 USD
  Budget:Groceries         50.00 USD

For rollovers, simply do not zero out the envelopes at month end. Whatever remains carries forward automatically. This is one area where plain-text accounting makes envelope budgeting easier than physical envelopes or most apps.

I have been running this system for 3 years and it has genuinely changed my relationship with spending. The forced intentionality of allocating every dollar is powerful.

Great writeup, Mike. I have been using a similar system but with one significant difference in philosophy.

I disagree with keeping budget data inside the ledger. Here is why: your budget is a plan, not a record of what happened. Mixing aspirational numbers with actual transaction data muddies the waters. When I run reports, I want to query actual spending without having to filter out budget allocation entries.

My approach is to keep budgets in a separate YAML file:

# budgets/2026.yml
monthly:
  Expenses:Food:Groceries: 650
  Expenses:Food:Restaurants: 180
  Expenses:Transport:Gas: 140
  Expenses:Entertainment: 120
  Expenses:Subscriptions: 55

quarterly:
  Expenses:Insurance:Auto: 450
  Expenses:Insurance:Health: 1200

annual:
  Expenses:Travel:Vacation: 4000
  Expenses:Gifts: 800

Then my budget comparison script loads both the YAML config and the Beancount ledger, computing variance without polluting the ledger itself.

That said, I completely agree on the power of envelope budgeting for controlling spending. The psychological constraint of seeing an envelope balance decrease in real time is much more effective than looking at a monthly summary after the damage is done.

One optimization I would add: track your budget accuracy over time. I have a spreadsheet that records my budgeted vs. actual for every category over 24 months. The categories where I consistently over-budget get reduced; the ones where I under-budget get increased. After two years, my budget forecasts are now within 5% of actual spending in most categories. The budget becomes a genuinely predictive tool rather than wishful thinking.

Mike, this is really helpful for personal finances. I want to add a perspective for anyone running a small business with Beancount.

Envelope budgeting maps directly to the Profit First methodology for business owners. The idea (from Mike Michalowicz book) is that you allocate every dollar of revenue into specific accounts: Profit, Owner Pay, Tax, and Operating Expenses. It is literally envelope budgeting for businesses.

In Beancount, I implement it like this for my bookkeeping clients:

2026-01-01 open Assets:Business:Revenue
2026-01-01 open Assets:Business:Profit         ; 5% of revenue
2026-01-01 open Assets:Business:OwnerPay       ; 50% of revenue
2026-01-01 open Assets:Business:Tax            ; 15% of revenue
2026-01-01 open Assets:Business:OpEx           ; 30% of revenue

When revenue comes in, it gets distributed:

2026-02-03 * "Client ABC" "February retainer"
  Assets:Business:Checking     5000.00 USD
  Income:Consulting           -5000.00 USD

2026-02-03 * "Profit First Allocation" ""
  Assets:Business:Checking    -5000.00 USD
  Assets:Business:Profit        250.00 USD
  Assets:Business:OwnerPay     2500.00 USD
  Assets:Business:Tax           750.00 USD
  Assets:Business:OpEx         1500.00 USD

The key difference from personal envelope budgeting is that business expenses must ONLY come from the OpEx envelope. If OpEx runs low, you do not dip into Profit or Tax. You either find ways to reduce expenses or grow revenue.

One gotcha: make sure you keep the tax allocation envelope separate from personal tax obligations. I have seen business owners mix these up and end up short at tax time.

This whole thread is gold! I have been using YNAB for envelope budgeting and was wondering if Beancount could replicate that workflow. Turns out it can, with even more flexibility.

Question for Mike and Fred: how do you handle variable income months? My salary is fixed, but I also do freelance DevOps consulting on the side. Some months I get an extra 2000, other months nothing. In YNAB, I would just add the extra income to my Available bucket and then decide which envelopes to top up.

In the Beancount setup, would I just create an additional allocation transaction whenever freelance income arrives?

2026-02-15 * "Freelance Client" "Infrastructure migration project"
  Assets:Chase:Checking     2000.00 USD
  Income:Freelance          -2000.00 USD

2026-02-15 * "Budget Allocation" "Freelance income - extra savings"
  Budget:Available          -2000.00 USD
  Budget:Savings             1200.00 USD
  Budget:Emergency            500.00 USD
  Budget:Entertainment        300.00 USD

Also, Bob, that Profit First approach is really interesting. I have been thinking about setting up a separate Beancount ledger for my freelance business. Would you recommend keeping personal and business in one file or separate files?

Really useful approach, Mike. From a tax perspective, I want to emphasize that if anyone is using envelope budgeting for a business, the tax envelope is non-negotiable. I see so many small business owners who treat their tax allocation as optional — spending it when cash gets tight and then scrambling at tax time.

Estimated tax payments should be automated. If you are self-employed or have significant freelance income, the IRS expects quarterly estimated payments (Form 1040-ES). Your Budget:Tax envelope should be funded at a minimum of 25-30% of net self-employment income, and the payment should be scheduled automatically.

Here is a useful pattern I recommend to clients:

2026-01-01 open Budget:Tax:Federal
2026-01-01 open Budget:Tax:State
2026-01-01 open Budget:Tax:SelfEmployment

2026-02-01 * "Tax Allocation" "Q1 estimated tax funding"
  Budget:Available             -1500.00 USD
  Budget:Tax:Federal              900.00 USD
  Budget:Tax:State                300.00 USD
  Budget:Tax:SelfEmployment       300.00 USD

Separating federal, state, and self-employment tax envelopes makes it much easier to reconcile when you actually make the estimated payments. And if you overfund the tax envelopes, that is a much better problem to have than underfunding them.

@newbie_accountant — regarding your question about separate ledgers for business vs personal: as an EA, I strongly recommend separate ledger files. It creates a clean separation for audit purposes. You can use Beancount includes to reference between them if needed, but the IRS expects clear business vs personal delineation.