I need to tell you about the day I almost got a nonprofit client audited because of Excel.
The Spreadsheet Nightmare
Six months ago, I took on a new client—a local food bank with five active grants totaling about $300K annually. They were tracking everything in Excel. Not one spreadsheet, mind you, but FIVE separate files, one per grant. Each had its own tab structure: Budget, Expenses, Match Requirements, Reporting Deadlines.
The executive director would email me updated versions with names like “Grant_ABC_final.xlsx” and “Grant_ABC_final_REVISED.xlsx”. You know where this is going.
The breaking point: We missed a quarterly reporting deadline for a $75K federal grant because I was working off version 3, the ED was updating version 4, and the board treasurer had added expenses to version 2. None of us knew until the funder called asking where the report was.
I spent a weekend reconstructing transactions from bank statements, emails, and three conflicting spreadsheets. We got the report in (late, with apologies), but I knew we couldn’t keep doing this.
Enter Beancount
I’d been using Beancount for my own business finances, but this crisis made me wonder: could plain text accounting solve nonprofit grant tracking?
Turns out, yes.
Here’s what I implemented:
1. Metadata Tags for Grant Identification
Every transaction gets a grant tag:
2026-02-15 * "Office Depot" "Program supplies"
grant: "USDA-2026-001"
expense-class: "program"
Expenses:Program:Supplies 245.67 USD
Assets:Grants:USDA:Checking -245.67 USD
The grant: metadata lets me query expenses by funder instantly. No more hunting through five spreadsheets.
2. Account Hierarchies for Fund Separation
Each grant gets its own account structure:
Assets:Grants:USDA:Checking
Assets:Grants:ABC-Foundation:Checking
Expenses:Program:Salaries
Expenses:Program:Supplies
Expenses:Admin:Salaries
Expenses:Admin:Rent
This mirrors fund accounting principles—restricted funds stay separated.
3. Query-Based Reporting
Need all expenses for a specific grant in Q1?
SELECT date, narration, account, position
WHERE grant = "USDA-2026-001"
AND date >= 2026-01-01
AND date < 2026-04-01
Done. No pivot tables, no VLOOKUP formulas that break when someone inserts a row.
4. Expense Classification for Functional Reporting
Nonprofits need to report expenses by function (program vs. admin vs. fundraising). I add metadata:
expense-class: "program"
expense-class: "admin"
expense-class: "fundraising"
Now Form 990 functional expense reporting is a query, not a month-end scramble.
The Migration Process
Week 1: I built the chart of accounts and documented the naming conventions (grant codes, expense classifications).
Week 2: I imported January’s transactions from bank statements, manually adding metadata. It was tedious. I wrote notes in transaction comments explaining grant eligibility: ; Eligible under USDA Equipment category, see grant agreement section 4.2
Week 3: The ED tried to review the Beancount file in VS Code and gave me that look—you know the one. So I set up Fava on a local server. Suddenly they could see balances, run queries, and filter by grant in a web interface. Buy-in achieved.
Weeks 4-8: I caught up on Feb-March, started building reusable import scripts, and documented quarterly reporting queries.
What I Learned (The Hard Way)
Challenge 1: Learning curve. I’m comfortable with plain text. The ED and board treasurer are not. Fava helped, but I still handle transaction entry. For them, Beancount is the reporting layer.
Challenge 2: Client education. Explaining “this is your bookkeeping system, but it’s a text file in Git” requires patience. I focused on results: “You’ll never work off the wrong version again because there’s only ONE version.”
Challenge 3: Indirect cost allocation. Some grants allow indirect costs (admin overhead) up to a percentage. I’m still figuring out the best way to model this. Right now I manually calculate and record a monthly journal entry. Not ideal.
The Results (6 Months In)
Zero missed reporting deadlines. We’ve filed four quarterly reports, all on time.
Audit trail confidence. Every transaction has a date, narration, grant tag, and often a comment with justification. If we get audited tomorrow, I’m ready.
Query flexibility. Last month the board asked, “How much have we spent on program salaries across all grants?” Five minutes later, I had the answer. In the spreadsheet days, that would’ve been an hour of copy-paste.
Version control peace of mind. The ED and I both work off the same Git repository. Merge conflicts are rare (we’re not editing simultaneously), and when they happen, Git tells us.
My Question for the Community
How are you handling grant tracking in Beancount?
- Are you using metadata, account hierarchies, or something else?
- How do you handle indirect cost allocation?
- Any tips for making Beancount more approachable for nonprofit staff who aren’t technical?
- Anyone built Fava plugins for grant dashboards?
I’ve shared what worked for me, but I’m sure there are better approaches out there. Would love to hear how others are solving this.
TL;DR: Moved a nonprofit client from five conflicting Excel files to Beancount with grant metadata tags. No more version conflicts, missed deadlines, or weekend reconciliation marathons. Plain text accounting might just save your nonprofit’s sanity.