Cash Envelope System in Beancount: Building Virtual Envelopes for Budget Control

I’ve been fascinated by the classic cash envelope budgeting method, but let’s be honest—carrying physical cash envelopes in 2026 feels impractical. Most merchants prefer digital payments, it’s inconvenient, and there are security concerns with carrying cash around.

So I’ve been building a virtual envelope system in Beancount that gives me all the psychological benefits of the envelope method without the cash downsides.

The Concept

The traditional envelope method works by physically dividing cash into labeled envelopes (groceries, dining out, entertainment, etc.). When an envelope is empty, you stop spending in that category. Simple, visual, and effective.

In Beancount, I’m recreating this with virtual “envelope” accounts that represent budgeted amounts rather than actual bank balances.

My Implementation

1. Envelope Account Structure

; Virtual envelope accounts (not real bank accounts)
2026-01-01 open Assets:Envelopes:Groceries
2026-01-01 open Assets:Envelopes:DiningOut
2026-01-01 open Assets:Envelopes:Entertainment
2026-01-01 open Assets:Envelopes:Clothing
2026-01-01 open Assets:Envelopes:PersonalCare
2026-01-01 open Assets:Envelopes:Gifts

; Funding source for envelopes
2026-01-01 open Equity:Budget:Allocation

2. Monthly Envelope Funding (“Stuffing”)

On payday, I “stuff” my virtual envelopes:

2026-02-01 * "Budget" "February envelope stuffing"
  Equity:Budget:Allocation                 -1,200.00 USD
  Assets:Envelopes:Groceries                  400.00 USD
  Assets:Envelopes:DiningOut                  150.00 USD
  Assets:Envelopes:Entertainment              100.00 USD
  Assets:Envelopes:Clothing                   150.00 USD
  Assets:Envelopes:PersonalCare                50.00 USD
  Assets:Envelopes:Gifts                      100.00 USD
  Assets:Envelopes:Buffer                     250.00 USD  ; emergency overflow

3. Spending Draws from Envelopes

Each purchase draws from both my real account AND the virtual envelope:

2026-02-05 * "Whole Foods" "Weekly groceries"
  Expenses:Food:Groceries                    127.43 USD
  Assets:Checking                           -127.43 USD
  ; Envelope tracking
  Assets:Envelopes:Groceries                -127.43 USD
  Equity:Budget:Spent                        127.43 USD

4. Checking Envelope Balances

The magic query that shows me what’s left in each envelope:

SELECT account, sum(position) as remaining
WHERE account ~ "Assets:Envelopes"
ORDER BY remaining

My Questions

  1. Is this approach too complex? I’m essentially double-booking every discretionary transaction. Is there a simpler way?

  2. Rollover handling? What do you do when an envelope has money left at month-end? Roll it over, or sweep it to savings?

  3. Overspending alerts? Has anyone built a plugin that warns when an envelope goes negative?

  4. Alternative approaches? I’ve seen people use tags instead of separate accounts. Which approach scales better?

I love the psychological constraint of “this envelope is empty, stop spending,” but I want to make sure my Beancount implementation is sustainable long-term.

I’ve been using a virtual envelope system for about 3 years now. Your implementation is solid, but let me share a simplification that might help.

The Double-Entry Simplification

You’re right that double-booking every transaction is tedious. Here’s my streamlined approach that achieves the same result with less friction:

Instead of tracking envelope draws per transaction, I only record:

  1. Monthly envelope allocations (funding)
  2. Monthly expense totals per category (spending)

The envelope balance is then: Allocation - Expenses

; No per-transaction envelope tracking needed!
; Just query at any time:

SELECT 
  account,
  400.00 - sum(position) as envelope_remaining  ; 400 = monthly grocery budget
WHERE account = "Expenses:Food:Groceries"
  AND MONTH(date) = MONTH(today())

The trade-off: you don’t see envelope balances in your balance sheet directly. But for me, running a quick query is easier than maintaining parallel transactions.

Rollover Strategy

I use a “use it or lose it” approach for most envelopes, but with exceptions:

  • Groceries, Dining: No rollover—these reset monthly
  • Clothing, Gifts: Roll over—these are inherently lumpy
  • Entertainment: Roll over up to 2x monthly budget, then sweep excess
; End of month rollover decision
2026-02-28 * "Budget" "Sweep excess entertainment to savings"
  Assets:Envelopes:Entertainment           -75.00 USD  ; excess above 2x
  Assets:Savings:General                    75.00 USD

The Psychology Hack

The real magic of envelope budgeting is the constraint feeling. Here’s what I do to recreate that digitally:

I set up a Fava dashboard that shows envelope balances with color coding:

  • Green: >50% remaining
  • Yellow: 25-50% remaining
  • Red: <25% remaining

Seeing that red bar on my dining envelope mid-month is just as effective as seeing an empty physical envelope!

I set up envelope systems for several of my small business clients—it’s a great way to help business owners manage variable expenses like marketing, travel, and professional development.

The Client Perspective

For business owners, I frame it as “departmental budgets” rather than “envelopes,” but it’s the same concept. Each expense category gets a monthly allocation, and we track remaining budget.

The approach I use with clients is simpler than yours to reduce bookkeeping burden:

; Define budgets in a config file, not transactions
2026-01-01 custom "budget" "Expenses:Marketing" 500.00 USD
2026-01-01 custom "budget" "Expenses:Travel" 300.00 USD
2026-01-01 custom "budget" "Expenses:Training" 200.00 USD

Then I use a Python script that reads these custom directives and compares against actual spending. No double-entry needed during the month.

Overspending Alert Plugin

You asked about plugins for overspending alerts. I wrote a simple check that runs with bean-check:

# envelope_check.py (simplified)
from beancount import loader
from beancount.core import getters

def check_envelopes(entries, options):
    errors = []
    budgets = get_budget_directives(entries)
    actuals = get_monthly_spending(entries)
    
    for category, budget in budgets.items():
        spent = actuals.get(category, 0)
        if spent > budget:
            errors.append(EnvelopeOverspentError(
                f"{category}: Spent {spent}, Budget {budget}"
            ))
    return errors

This gives you a warning every time you run bean-check if any envelope is overdrawn. Catches it before month-end!

My Advice on Tags vs. Accounts

I’ve tried both. My recommendation:

Use accounts for envelopes when:

  • You want balances visible in Fava’s balance sheet
  • You’re doing proper zero-sum budgeting (every dollar assigned)
  • You want historical envelope balances

Use tags when:

  • Envelopes are informational only (soft limits)
  • You don’t want the maintenance overhead
  • You’re okay with query-based reporting

For personal budgeting, tags are usually sufficient. For business budgets that need to tie to actual accounts, use the account approach.

Great thread! I want to add the YNAB-inspired perspective since that’s what got me into envelope budgeting before I switched to Beancount.

The YNAB Philosophy in Beancount

YNAB (You Need A Budget) popularized digital envelope budgeting with four rules:

  1. Give every dollar a job
  2. Embrace your true expenses
  3. Roll with the punches
  4. Age your money

Here’s how I implement these in Beancount:

Rule 1: Give Every Dollar a Job (Zero-Sum Budgeting)

; Available to budget = Income received - Already budgeted
; This should always equal zero if every dollar has a job

SELECT sum(position) as unbudgeted
WHERE account ~ "Assets:Envelopes" OR account ~ "Equity:Budget"

Rule 2: Embrace True Expenses (Sinking Funds)

This is huge—budget monthly for annual/irregular expenses:

; Annual expenses, funded monthly
Assets:Envelopes:CarInsurance      ; Budget /mo for ,200 annual premium
Assets:Envelopes:Vacation          ; Budget /mo for annual trip
Assets:Envelopes:ChristmasGifts    ; Budget /mo starting January
Assets:Envelopes:CarMaintenance    ; Budget /mo for tires, repairs, etc.

When the bill comes, you have the money waiting!

Rule 3: Roll With the Punches (Envelope Transfers)

When you overspend in one category, move money from another:

2026-02-15 * "Budget" "Cover dining overspend from entertainment"
  Assets:Envelopes:Entertainment          -40.00 USD
  Assets:Envelopes:DiningOut               40.00 USD

This is key—it’s not a failure, it’s a conscious reallocation. Your ledger shows every time you made this choice.

Rule 4: Age Your Money

This one’s trickier in Beancount. The idea is to spend money that’s at least 30 days old. I approximate it by tracking when income was received vs. when it was spent:

2026-02-01 * "Employer" "February paycheck"
  income-date: 2026-02-01
  Assets:Checking                        3,500.00 USD
  Income:Salary                         -3,500.00 USD

Then I can query average age of money spent in a given period. If I’m spending February income in February, my money is 0 days old. If I’m spending January income in February, it’s 30+ days old.

@newbie_accountant - The psychological power of envelope budgeting isn’t the tracking—it’s the intentionality. Every purchase draws down a conscious allocation. That mindset shift is what makes it work!