Scriptable Workflows with Beancount and Fava
Beancount (a plain-text double-entry accounting tool) and Fava (its web interface) are highly extensible and scriptable. Their design allows you to automate financial tasks, generate custom reports, and set up alerts by writing Python scripts. In the words of one user, “I really like having my data in such a convenient format, and I like that I can automate things to my heart’s content. There is no API like a file on your disk; it’s easy to integrate with.” This guide will walk through creating scriptable workflows—from beginner-friendly automation to advanced Fava plugins.
Getting Started: Running Beancount as a Python Script
Before diving into specific tasks, ensure you have Beancount installed (e.g. via pip install beancount
). Since Beancount is written in Python, you can use it as a library in your own scripts. The general approach is:
-
Load your Beancount ledger: Use Beancount’s loader to parse the
.beancount
file into Python objects. For example:from beancount import loader
entries, errors, options_map = loader.load_file("myledger.beancount")
if errors:
print("Errors:", errors)This gives you a list of
entries
(transactions, balances, etc.) and anoptions_map
with metadata. All your accounts, transactions, and balances are now accessible in code. -
Leverage Beancount Query Language (BQL): Instead of manually iterating, you can run SQL-like queries on the data. For instance, to get total expenses by month, you could use the query API:
from beancount.query import query
q = query.Query(entries, options_map)
result = q.query("SELECT month, sum(position) WHERE account ~ 'Expenses' GROUP BY month")
print(result)This uses Beancount’s Query system to aggregate data. (Under the hood, this is similar to what the
bean-query
command does, but here you’re using it in a script.) In fact, the Beancount author notes that you can load the file and callrun_query()
directly via the Python API, avoiding the need to call external commands in a loop. -
Set up a project structure: Organize your scripts alongside your ledger. A common layout is to have directories for importers (to fetch/parse external data), reports or queries (for analysis scripts), and documents (to store downloaded statements). For example, one user keeps:
importers/
– custom Python import scripts (with tests),queries/
– scripts to generate reports (runnable viapython3 queries/...
),documents/
– downloaded bank CSVs/PDFs organized by account.
With this setup, you can run scripts manually (e.g. python3 queries/cash_flow.py
) or schedule them (via cron or a task runner) to automate your workflow.
Automating Reconciliation Tasks
Reconciliation means making sure your ledger matches external records (bank statements, credit card reports, etc.). Beancount’s plain-text ledger and Python API make it possible to automate much of this process.
Importing and Matching Transactions (Beginner)
For beginners, the recommended approach is to use Beancount’s importer plugins. You write a small Python class following Beancount’s importer protocol to parse a given format (CSV, OFX, PDF, etc.) and produce transactions. Then use the bean-extract
command or a script to apply these importers:
- Write an importer (a Python class with methods like
identify()
,extract()
) for your bank’s CSV format. Beancount’s documentation provides a guide and examples. - Use
bean-extract
in a script or Makefile (like thejustfile
example) to parse new statements. For instance, one workflow runsbean-extract
on all files in~/Downloads
and outputs transactions to a temporary file. - Manually review and copy transactions from the temp file into your main ledger, then run
bean-check
to ensure balances reconcile.
While this process still involves a review step, much of the grunt work of parsing and formatting entries is automated. Importer scripts can also auto-assign categories and even set balance assertions (statements of expected balances) to catch discrepancies. For example, after importing, you might have a line like 2025-04-30 balance Assets:Bank:Checking 1234.56 USD
which asserts the closing balance. When you run bean-check
, Beancount will verify that all of these balance assertions are correct, and flag any errors if transactions are missing or duplicated. This is a best practice: auto-generate balance assertions for each statement period to let the computer spot unreconciled differences for you.
Custom Reconciliation Scripts (Intermediate)
For more control, you can write a custom Python script to compare a bank’s transaction list (CSV or via API) with your ledger entries:
- Read the external data: Parse the bank’s CSV file using Python’s
csv
module (or Pandas). Normalize the data into a list of transactions, e.g. each with a date, amount, and description. - Load ledger transactions: Use
loader.load_file
as shown earlier to get all ledger entries. Filter this list to the account of interest (e.g. your checking account) and perhaps the date range of the statement. - Compare and find mismatches:
- For each external transaction, check if an identical entry exists in the ledger (match by date and amount, maybe description). If not found, mark it as “new” and possibly output it as a Beancount-formatted transaction for you to review.
- Conversely, identify any ledger entries in that account that don’t appear in the external source – these could be data entry errors or transactions that haven’t cleared the bank.
- Output results: Print a report or create a new
.beancount
snippet with the missing transactions.
As an example, a community script called reconcile.py
does exactly this: given a Beancount file and an input CSV, it prints a list of new transactions that should be imported, as well as any existing ledger postings that aren’t in the input (potentially a sign of misclassification). With such a script, monthly reconciliation can be as simple as running it and then appending the suggested transactions to your ledger. One Beancount user notes that they “do a reconciliation process on all the accounts each month” and use a growing collection of Python code to eliminate much of the manual work in importing and reconciling data.
Tip: During reconciliation, leverage Beancount’s tools for accuracy:
- Use balance assertions as mentioned, to have automated checks on account balances.
- Use the
pad
directive if desired, which can auto-insert balancing entries for minor rounding differences (use with caution). - Write unit tests for your importer or reconciliation logic (Beancount provides test helpers). For example, one workflow involved taking a sample CSV, writing failing tests with expected transactions, then implementing the importer until all tests passed. This ensures your import script works correctly for various cases.
Generating Custom Reports and Summaries
While Fava provides many standard reports (Income Statement, Balance Sheet, etc.), you can create custom reports using scripts. These can range from simple console outputs to rich formatted files or charts.