rustledger
June 29, 2026 · View on GitHub
rustledger
A blazing-fast Rust implementation of Beancount
Parse and validate your ledger faster than Python beancount.
Why rustledger?
| 10-30x faster | Parse and validate large ledgers in milliseconds (see benchmarks) |
| No dependencies | No Python runtime, no libraries to install |
| Drop-in replacement | Compatible bean-* CLI commands for easy migration |
| Full compatibility | Parses any valid beancount file |
| Editor support | LSP server for VS Code, Neovim, Helix, and more |
| AI-ready | MCP server for AI assistants, plus ag-rledger — an agent-native CLI with JSON envelopes |
| Runs anywhere | WebAssembly support for browser and Node.js |
| Better errors | Detailed error messages with source locations |
| 31 built-in plugins | Plus Python plugin compatibility via WASI sandbox |
| Bank import | CSV/OFX import with auto-detection, dedup, and categorization |
Comparison with other tools
| Feature | rustledger | Python beancount | hledger | ledger-cli |
|---|---|---|---|---|
| Language | Rust | Python | Haskell | C++ |
| Speed | Very fast | Slow | Fast | Fast |
| Beancount syntax | Native | Native | Via conversion | No |
| Query language | BQL (100% compat) | BQL | Custom | Custom |
| LSP server | Built-in | No | Via plugin | No |
| WASM support | Yes | No | Partial | No |
| Plugin system | Native + Python | Python | Haskell | Custom |
| Active development | Yes | Maintenance | Yes | Limited |
When to use rustledger:
- You use Beancount syntax and want speed
- You want a single binary with no runtime dependencies
- You need LSP editor integration
- You want to use existing Python plugins
When to use Python beancount:
- You need Fava web interface (until rustledger integration)
- You have complex Python plugins with C extensions
When to use hledger:
- You prefer hledger's syntax and reports
- You need time-tracking features
Install
| Platform | Command |
|---|---|
| macOS | brew install rustledger |
| Linux | brew install rustledger |
| Arch Linux | yay -S rustledger-bin |
| Fedora/RHEL | sudo dnf copr enable robcohen/rustledger && sudo dnf install rustledger |
| Windows | scoop bucket add rustledger https://github.com/rustledger/scoop-rustledger && scoop install rustledger |
| Cargo | cargo binstall rustledger or cargo install rustledger |
| Nix | nix run github:rustledger/rustledger |
| Docker | docker run --rm -v "$PWD:/data" ghcr.io/rustledger/rustledger /data/ledger.beancount |
| Binaries | GitHub Releases |
| npm (WASM) | npm install @rustledger/wasm |
| npm (MCP) | npx @rustledger/mcp-server (Model Context Protocol server) |
Missing your platform? Open an issue to request it.
Coming from Python beancount? See the Migration Guide for command equivalents and plugin mapping.
AI & agents
Two complementary ways to drive rustledger from AI tools:
-
MCP server —
npx @rustledger/mcp-serverexposes rustledger to AI assistants (Claude, Cursor, …) over the Model Context Protocol. Best for interactive assistants that call tools mid-conversation. -
ag-rledger— an agent-native CLI that mirrors everyrledgercommand but emits a single structured JSON envelope per call instead of human-readable text, so coding agents and scripts parse results directly (no output scraping). Each envelope carriesok, a typedexit_code, the command's output underresult, and a HATEOASnext_actionhint; pass the inner command's--jsonto get fully structuredresult.data. Best for headless agents and shell pipelines. It ships besiderledgerin the release archives (or build it withcargo build -p rustledger --bin ag-rledger --no-default-features --features ag-rledger):$ ag-rledger check --json ledger.beancount {"ok":true,"exit_code":0,"result":{"data":{"error_count":0}},"next_action":"…"}Same subcommands as
rledger(check,query,format,report,extract, …) — only the I/O is JSON.
Quick Start
rledger check ledger.beancount
rledger query ledger.beancount "SELECT account, SUM(position) GROUP BY account"
CLI Commands
| Command | Description |
|---|---|
rledger check | Validate ledger files with detailed error messages |
rledger query | Run BQL queries (interactive shell or one-shot) |
rledger format | Auto-format beancount files |
rledger report | Generate balance, account, and statistics reports |
rledger add | Add transactions interactively or via quick mode |
rledger doctor | Debugging tools for ledger issues |
rledger extract | Import transactions from CSV/OFX bank statements with auto-detection, dedup, and categorization |
rledger price | Fetch commodity prices from online sources |
rledger lint | Non-fatal advisory passes (e.g., detect inter-account transfer pairs) |
rledger config | Manage rustledger configuration (alias: cfg) |
rledger completions | Generate shell completions |
rledger-lsp | Language Server Protocol for editor integration |
Python beancount users can install bean-check, bean-query, etc. wrapper scripts via rledger compat install.
Report subcommands
| Subcommand | Alias | Description |
|---|---|---|
balances | All account balances | |
balsheet | bal | Balance sheet report |
income | is | Income statement |
journal | register | Transaction register |
holdings | Investment holdings | |
networth | Net worth over time | |
accounts | List all accounts | |
commodities | List all commodities | |
prices | Price entries | |
stats | Ledger statistics |
Doctor subcommands
Debugging and diagnostic tools:
| Subcommand | Description |
|---|---|
lex | Dump lexer tokens (alias: dump-lexer) |
parse | Parse and show directives |
context | Show transaction context at a line number |
linked | Find transactions by link (^link) or tag (#tag) |
missing-open | Generate missing Open directives |
list-options | List all available beancount options |
print-options | Print options parsed from a file |
stats | Display ledger statistics |
display-context | Show inferred decimal precision context |
roundtrip | Round-trip parse/format test |
directories | Validate directory hierarchy against accounts |
region | Print transactions in a line range with balances |
generate-synthetic | Generate synthetic test files |
# Debug a parsing issue at line 42
rledger doctor context ledger.beancount 42
# Find all transactions with a link
rledger doctor linked ledger.beancount ^trip-2024
# Generate Open directives for accounts missing them
rledger doctor missing-open ledger.beancount >> ledger.beancount
Run rledger <command> --help for all options.
CLI examples
# Validate with plugins
rledger check --native-plugin auto_accounts ledger.beancount
# Interactive query shell
rledger query ledger.beancount
# One-shot query
rledger query ledger.beancount "SELECT date, narration WHERE account ~ 'Expenses:Food'"
# Reports
rledger report ledger.beancount balances
rledger report ledger.beancount stats
# Format in place
rledger format --in-place ledger.beancount
Import & Categorization
rustledger includes a complete bank import pipeline (rledger extract) with automatic CSV/OFX parsing, duplicate detection, transaction categorization, and balance reconciliation.
CSV Import with Auto-Detection
# Auto-detect delimiter, date format, and column roles
rledger extract bank-statement.csv --auto -a Assets:Bank:Checking
# Check against existing ledger to avoid duplicates
rledger extract statement.csv --auto -a Assets:Bank --existing ledger.beancount
# Append balance assertion from bank statement
rledger extract statement.csv --auto -a Assets:Bank --balance 5000.00
# List available importers from config
rledger extract --list-importers --config importers.toml
Importers Configuration
Create importers.toml for reusable import profiles:
[[importers]]
name = "chase"
account = "Assets:Bank:Chase"
date_column = "Transaction Date"
amount_column = "Amount"
date_format = "%m/%d/%Y"
[importers.mappings]
"AMAZON" = "Expenses:Shopping"
"WHOLE FOODS" = "Expenses:Groceries"
rledger extract --importer chase chase-statement.csv
Config is searched in: current directory, ~/.config/rledger/importers.toml, or via --config.
Transaction Categorization
A 3-tier pipeline automatically categorizes transactions:
- Rules engine — substring, regex, and exact match patterns from
importers.toml - Merchant dictionary — ~75 built-in patterns across 10 categories (groceries, dining, transport, subscriptions, etc.)
- ML categorization — TF-IDF + a self-contained Multinomial Naive Bayes classifier (pure
std, no ML framework dependency)
Transfer Detection
Automatically identifies inter-account transfers by matching opposite-sign amounts within date windows, with keyword boosting for common transfer indicators.
Duplicate Detection
Fuzzy matching on date + amount + payee/narration prevents importing the same transaction twice. Structural fingerprinting (BLAKE3) provides stable hashes for comparison.
Balance Reconciliation
The --balance flag appends a balance assertion directive matching your bank statement's ending balance, helping verify import accuracy.
Custom WASM Importers
Beyond the built-in CSV and OFX importers, third parties can ship importers as sandboxed .wasm modules — no need to fork the workspace:
# Single file
rledger extract --wasm-importer ./my-bank.wasm statement.dat -a Assets:Bank
# Or scan a directory of .wasm modules (also configurable via importers.toml)
rledger extract --wasm-importer-dir /etc/rledger/importers.d statement.dat -a Assets:Bank
WASM importers implement the same Importer trait as the built-ins via the wasm_importer_main! macro and run inside the same wasmtime sandbox as directive plugins (no FS / network / WASI; configurable memory + fuel caps). See examples/wasm-importer-csv-example for a reference implementation.
Crates
| Crate | Description |
|---|---|
rustledger | CLI tool (rledger check, rledger query, etc.) |
rustledger-core | Core types: Amount, Position, Inventory |
rustledger-parser | Lexer and parser with error recovery |
rustledger-loader | File loading and includes |
rustledger-booking | Interpolation and 7 booking methods |
rustledger-validate | Validation error codes |
rustledger-query | BQL query engine |
rustledger-completion | Editor-agnostic completion logic (shared by LSP + WASM) |
rustledger-plugin | 31 built-in plugins + Python plugin support |
rustledger-plugin-types | Shared plugin type definitions |
rustledger-importer | Import framework: built-in CSV/OFX, plus a WasmImporter host loader for third-party .wasm importers |
rustledger-ops | Pure operations — ML categorization, LLM prompts, dedup, transfer detection, balance reconciliation, merchant dictionary |
rustledger-lsp | Language Server Protocol for editor integration |
rustledger-wasm | WebAssembly bindings for JavaScript/TypeScript |
rustledger-ffi-component | Primary embedding surface — FFI via WASI Preview 2 / Component Model (typed WIT contract) |
rustledger-ffi-wasi | Legacy WASI (wasip1) JSON-RPC FFI for embedding — slated for removal |
Booking methods (7)
| Method | Description |
|---|---|
STRICT | Lots must match exactly (default) |
STRICT_WITH_SIZE | Exact-size matches accept oldest lot |
FIFO | First in, first out |
LIFO | Last in, first out |
HIFO | Highest cost first |
AVERAGE | Average cost basis |
NONE | No cost tracking |
Built-in plugins (31)
| Plugin | Description |
|---|---|
auto_accounts | Auto-generate Open directives |
auto_tag | Automatically tag transactions |
box_accrual | Accrual accounting for boxed periods |
gain_loss | Split capital gains into gain/loss accounts |
long_short | Split capital gains by holding period |
check_average_cost | Validate average cost bookings |
check_closing | Zero balance assertion on account close |
check_commodity | Validate commodity declarations |
check_drained | Ensure accounts are drained before close |
close_tree | Close descendant accounts |
coherent_cost | Enforce cost OR price (not both) |
commodity_attr | Validate commodity attributes |
currency_accounts | Auto-generate currency trading postings |
effective_date | Override posting date via metadata |
forecast | Generate recurring transactions |
generate_base_ccy_prices | Create base currency price entries |
implicit_prices | Generate price entries from transaction costs |
leafonly | Error on postings to non-leaf accounts |
noduplicates | Hash-based duplicate transaction detection |
nounused | Warn on unused accounts |
onecommodity | Single commodity per account |
pedantic | Enable all strict validations |
rename_accounts | Rename accounts via metadata |
rx_txn_plugin | Link related transactions |
sellgains | Cross-check capital gains against sales |
split_expenses | Split expenses across accounts |
unique_prices | One price per day per commodity pair |
unrealized | Calculate unrealized gains |
valuation | Mark-to-market valuation |
zerosum | Group transactions that sum to zero |
Additionally, document_discovery auto-discovers documents from option "documents" directories.
Python plugins: Run existing Python beancount plugins via CPython-WASI sandbox.
Plugin support
rustledger supports three types of plugins:
Native plugins (built-in, fastest):
# Run a native plugin from CLI
rledger check --native-plugin implicit_prices ledger.beancount
# Or declare in your beancount file (auto-detected as native):
# plugin "beancount.plugins.auto_accounts"
Python file plugins (via WASM sandbox):
# Declare in your beancount file:
# plugin "/path/to/my_plugin.py"
WASM plugins (sandboxed WebAssembly):
# Load a WASM plugin
rledger check --plugin /path/to/plugin.wasm ledger.beancount
How Python plugins work:
- File-based plugins (
.pyfiles) run in a sandboxed CPython compiled to WebAssembly - No system Python installation required
- Plugins cannot access the filesystem or network (sandbox isolation)
- Compatible with most pure-Python beancount plugins
Limitations:
- Module-based plugins (
beancount.plugins.xyz) only work if rustledger has a native implementation - Plugins with C extensions won't work (numpy, pandas, etc.)
- No network access (price fetching plugins need alternatives)
Editor Integration
rustledger includes a full-featured Language Server (rledger-lsp) for IDE support:
- Real-time diagnostics
- Autocompletion (accounts, currencies, payees)
- Go to definition / find references
- Hover information with account balances
- Rename refactoring
- Document formatting
See LSP setup guide for VS Code, Neovim, Helix, Zed, and Emacs.
Performance
Benchmarks run nightly on 10K transaction ledgers. View workflow →
Benchmark details
What's measured:
- Validation: Parse ledger + validate (balance assertions, account opens, etc.)
- Balance Report: Parse + compute all account balances
Memory efficiency: rustledger typically uses 3-5x less memory than Python beancount thanks to Rust's zero-cost abstractions and efficient data structures.
Run locally:
# Quick comparison (requires nix)
nix develop .#bench
./scripts/bench.sh
# Criterion micro-benchmarks
cargo bench -p rustledger-core
cargo bench -p rustledger-parser
See benchmarking.md for detailed benchmark documentation.
Contributing
See CONTRIBUTING.md for development setup and guidelines.
Documentation:
- Architecture - Crate structure and data flow
- BQL Reference - Query language guide
- Importing - CSV/OFX bank import tutorial
- Validation errors - Error code reference
- API docs - Rust API documentation
By submitting a pull request, you agree to the Contributor License Agreement.
License
Commercial licensing available - contact us for proprietary license options.
Funding
rustledger is free and open source. If you find it useful, consider supporting development: