Skip to content

Plugins Reference

rustledger supports a flexible plugin system for validation and transformation of your ledger data.

Plugin Architecture

rustledger supports three types of plugins:

TypePerformanceUse Case
Native RustFastestBuilt-in plugins, core functionality
WASMFastCustom plugins, external development
PythonSlow (10-100x)Legacy compatibility, migration path
┌─────────────────────────────────────────────────────────┐
│                    Beancount File                       │
│  plugin "beancount.plugins.auto_accounts"               │
│  plugin "my_custom_plugin.wasm"                         │
└─────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────┐
│                   Plugin Loader                         │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐       │
│  │   Native    │ │    WASM     │ │   Python    │       │
│  │  Registry   │ │   Runtime   │ │   Runtime   │       │
│  │  (30 plugins)│ │ (Wasmtime)  │ │(CPython-WASI)│      │
│  └─────────────┘ └─────────────┘ └─────────────┘       │
└─────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────┐
│              Plugin Interface (PluginInput)             │
│  - directives: Vec<DirectiveWrapper>                    │
│  - options: PluginOptions                               │
│  - config: Option<String>                               │
└─────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────┐
│              Plugin Output (PluginOutput)               │
│  - directives: Vec<DirectiveWrapper> (possibly modified)│
│  - errors: Vec<PluginError>                             │
└─────────────────────────────────────────────────────────┘

Plugin Loading

Plugins are specified in your beancount file using the plugin directive:

beancount
; Built-in plugin (by name)
plugin "beancount.plugins.auto_accounts"

; WASM plugin (by path)
plugin "/path/to/my_plugin.wasm"

; Plugin with configuration
plugin "beancount.plugins.split_expenses" "alice bob"

Plugin Execution Order

Plugins execute in the order they appear in your file. This matters because:

  • Transformation plugins modify directives that subsequent plugins see
  • Validation plugins should typically run after transformation plugins
beancount
; 1. First, auto-generate Open directives
plugin "beancount.plugins.auto_accounts"

; 2. Then, generate implicit prices from costs
plugin "beancount.plugins.implicit_prices"

; 3. Finally, validate the complete ledger
plugin "beancount.plugins.check_commodity"
plugin "beancount.plugins.noduplicates"

Using Plugins

Enable plugins in your beancount file:

beancount
plugin "beancount.plugins.auto_accounts"
plugin "beancount.plugins.implicit_prices"

Built-in Plugins

rustledger includes 30 native plugins. Here are the most commonly used:

Validation Plugins

PluginDescription
check_average_costValidate sales use correct average cost
check_closingZero balance assertion on posting with closing: TRUE
check_commodityEnsure commodities are declared
check_drainedZero balance assertion on balance sheet account close
coherent_costValidate cost specifications
commodity_attrValidate commodity metadata attributes
leafonlyRestrict postings to leaf accounts
noduplicatesDetect duplicate transactions
nounusedWarn about unused accounts
onecommodityOne commodity per account
pedanticStrict validation rules
unique_pricesOne price per commodity per day

Transformation Plugins

PluginDescription
auto_accountsAuto-generate Open directives
auto_tagAuto-tag transactions by account patterns
box_accrualSplit capital losses across multiple years
close_treeRecursively close account trees
currency_accountsAuto-generate currency trading postings
document_discoveryAuto-discover documents from directories
effective_dateMove postings to effective dates
forecastGenerate recurring transactions
generate_base_ccy_pricesGenerate base currency prices from FX rates
implicit_pricesGenerate prices from transactions
long_shortClassify capital gains as short/long term
gain_lossClassify capital gains as gains/losses
rename_accountsRename accounts using regex patterns
rx_txn_pluginSet defaults for Regular Expected Transactions
sellgainsAuto-generate capital gains postings
split_expensesSplit shared expenses
unrealizedCalculate unrealized gains/losses
valuationTrack opaque fund values
zerosumMatch zero-sum postings within date ranges

All Built-in Plugins

Complete list of 30 native plugins
PluginDescription
auto_accountsAuto-generate Open directives
auto_tagAutomatically tag transactions
box_accrualAccrual accounting for boxed periods
gain_lossSplit capital gains into gain/loss accounts
long_shortSplit capital gains by holding period
check_average_costValidate average cost bookings
check_closingZero balance on account close
check_commodityValidate commodity declarations
check_drainedEnsure accounts drained before close
close_treeClose descendant accounts
coherent_costEnforce cost OR price (not both)
commodity_attrValidate commodity attributes
currency_accountsAuto-generate currency trading postings
effective_dateOverride posting date via metadata
forecastGenerate recurring transactions
generate_base_ccy_pricesCreate base currency price entries
implicit_pricesGenerate prices from costs
leafonlyError on non-leaf account postings
noduplicatesDetect duplicate transactions
nounusedWarn on unused accounts
onecommoditySingle commodity per account
pedanticEnable all strict validations
rename_accountsRename accounts via metadata
rx_txn_pluginLink related transactions
sellgainsCross-check capital gains
split_expensesSplit expenses across accounts
unique_pricesOne price per day per pair
unrealizedCalculate unrealized gains
valuationMark-to-market valuation
zerosumGroup transactions that sum to zero

Plugin Details

auto_accounts

Automatically generates Open directives for any accounts used in transactions.

beancount
plugin "beancount.plugins.auto_accounts"

; No need for explicit Open directives
2024-01-15 * "Coffee"
  Expenses:Food:Coffee   5.00 USD
  Assets:Cash           -5.00 USD

Options: None

check_commodity

Ensures all commodities are explicitly declared.

beancount
plugin "beancount.plugins.check_commodity"

2020-01-01 commodity USD
2020-01-01 commodity AAPL

Options: None

coherent_cost

Validates that cost specifications are consistent with prices.

beancount
plugin "beancount.plugins.coherent_cost"

This plugin checks that when both cost and price are specified, they're coherent (e.g., for capital gains transactions).

Options: None

implicit_prices

Generates price directives from transaction costs.

beancount
plugin "beancount.plugins.implicit_prices"

2024-01-15 * "Buy stock"
  Assets:Brokerage   10 AAPL {150.00 USD}
  Assets:Cash       -1500.00 USD

; Generates:
; 2024-01-15 price AAPL 150.00 USD

Options: None

leafonly

Restricts postings to leaf accounts only. Reports an error if you post to an account that has sub-accounts.

beancount
plugin "beancount.plugins.leafonly"
beancount
; ERROR: Cannot post to parent account
2024-01-15 * "Coffee"
  Expenses   5.00 USD    ; Error if Expenses:Food exists
  Assets:Cash

; OK: Posting to leaf account
2024-01-15 * "Coffee"
  Expenses:Food:Coffee   5.00 USD
  Assets:Cash

Options: None

noduplicates

Detects duplicate transactions based on date, payee, narration, and amounts.

beancount
plugin "beancount.plugins.noduplicates"

Options: None

onecommodity

Ensures each account only holds one commodity.

beancount
plugin "beancount.plugins.onecommodity"

2020-01-01 open Assets:Bank:USD   USD
2020-01-01 open Assets:Bank:EUR   EUR

Options: None

pedantic

Enables strict validation:

  • Requires explicit tags on all transactions
  • Requires payees on all transactions
  • Other strict checks
beancount
plugin "beancount.plugins.pedantic"

Options: None

sellgains

Automatically generates capital gains postings for sales.

beancount
plugin "beancount.plugins.sellgains"

2024-01-15 * "Sell AAPL"
  Assets:Brokerage  -10 AAPL {} @ 175.00 USD
  Assets:Cash       1750.00 USD
  ; Auto-generates:
  ; Income:CapitalGains   -250.00 USD

Options: None

split_expenses

Splits expenses among multiple people.

beancount
plugin "beancount.plugins.split_expenses" "mark,john 50,50"

2024-01-15 * "Dinner" #shared
  Expenses:Food      100.00 USD
  Assets:Cash

Options: "person1,person2 share1,share2"

unique_prices

Ensures only one price directive per commodity per day.

beancount
plugin "beancount.plugins.unique_prices"

Options: None


Additional Plugins

auto_tag

Automatically tags transactions based on account patterns.

beancount
plugin "beancount.plugins.auto_tag" "{'Expenses:Food:.*': 'food', 'Expenses:Transport:.*': 'transport'}"

2024-01-15 * "Coffee"
  Expenses:Food:Coffee   5.00 USD
  Assets:Cash
; Automatically gets #food tag

Options: Python dict mapping regex patterns to tag names.

box_accrual

Splits capital losses across multiple years based on synthetic_loan_expiry metadata. Used for tax purposes when losses must be recognized proportionally.

beancount
plugin "beancount.plugins.box_accrual"

2024-07-01 * "Sell synthetic"
  synthetic_loan_expiry: 2026-06-30
  Assets:Broker           1000 USD
  Income:Capital-Losses   -365 USD
; Splits the loss across 2024, 2025, 2026 proportionally

Options: None

check_average_cost

Validates that sales of positions use the correct average cost basis. Reports a warning if the cost used differs from the calculated average by more than the tolerance.

beancount
plugin "beancount.plugins.check_average_cost" "0.01"

; Buy 10 @ $100, then 10 @ $120 -> average = $110
; Selling at $100 cost would trigger a warning

Options: Tolerance as decimal (default: 0.01 = 1%)

close_tree

Automatically closes all descendant accounts when a parent account is closed.

beancount
plugin "beancount.plugins.close_tree"

2024-01-01 open Assets:Bank
2024-01-01 open Assets:Bank:Checking
2024-01-01 open Assets:Bank:Savings

2024-12-31 close Assets:Bank
; Automatically generates:
; 2024-12-31 close Assets:Bank:Checking
; 2024-12-31 close Assets:Bank:Savings

Options: None

check_closing

Inserts zero balance assertions when a posting has closing: TRUE metadata. The assertion is dated one day after the transaction.

beancount
plugin "beancount.plugins.check_closing"

2024-12-31 * "Close out position"
  Assets:Broker:AAPL  -10 AAPL {100 USD}
    closing: TRUE
  Assets:Cash          1000 USD
; Generates: 2025-01-01 balance Assets:Broker:AAPL 0 AAPL

Options: None

check_drained

Inserts zero balance assertions when balance sheet accounts (Assets, Liabilities, Equity) are closed. The assertion is dated one day after the close directive.

beancount
plugin "beancount.plugins.check_drained"

2024-01-01 open Assets:Bank USD
2024-12-31 close Assets:Bank
; Generates: 2025-01-01 balance Assets:Bank 0 USD

Options: None

commodity_attr

Validates that Commodity directives have required metadata attributes.

beancount
plugin "beancount.plugins.commodity_attr" "{'name': null, 'sector': ['Tech', 'Finance', 'Healthcare']}"

2024-01-01 commodity AAPL
  name: "Apple Inc"
  sector: "Tech"

Options: Python dict where:

  • Keys are attribute names
  • null means required but any value allowed
  • ['a', 'b'] means required and must be one of the listed values

currency_accounts

Automatically generates currency conversion postings through trading accounts when transactions involve multiple currencies.

beancount
plugin "beancount.plugins.currency_accounts"

2024-01-15 * "Buy EUR"
  Assets:Bank:EUR    100 EUR @ 1.10 USD
  Assets:Bank:USD   -110 USD
; Generates postings through Equity:Conversions:EUR and Equity:Conversions:USD

Options: None

document_discovery

Auto-discovers document files from directories. Scans directories specified in option "documents" for files matching the pattern {Account}/YYYY-MM-DD.description.*.

beancount
option "documents" "/path/to/documents"
plugin "beancount.plugins.document_discovery"

; File: /path/to/documents/Assets/Bank/Checking/2024-01-15.statement.pdf
; Generates:
; 2024-01-15 document Assets:Bank:Checking "/path/to/documents/Assets/Bank/Checking/2024-01-15.statement.pdf"

Options: None (uses option "documents" paths)

effective_date

Moves postings to their effective date using holding accounts. Add effective_date metadata to defer recognition.

beancount
plugin "beancount.plugins.effective_date"

2024-01-15 * "Invoice"
  Income:Consulting  -1000 USD
    effective_date: 2024-02-15
  Assets:Receivable   1000 USD
; Creates a holding entry until 2024-02-15

Options: None

forecast

Generates recurring transactions from templates. Use pattern syntax in the narration to specify recurrence.

beancount
plugin "beancount.plugins.forecast"

2024-01-01 # "Rent [MONTHLY]"
  Expenses:Rent      2000 USD
  Assets:Checking   -2000 USD

2024-01-01 # "Gym [WEEKLY REPEAT 12 TIMES]"
  Expenses:Gym        50 USD
  Assets:Cash        -50 USD

Patterns:

  • [MONTHLY] - Repeat monthly indefinitely
  • [WEEKLY] - Repeat weekly indefinitely
  • [DAILY] - Repeat daily indefinitely
  • [MONTHLY REPEAT N TIMES] - Repeat N times
  • [WEEKLY SKIP 2] - Repeat every 2 weeks
  • [MONTHLY UNTIL 2024-12-31] - Repeat until date

Options: None

gain_loss

Classifies capital gains transactions as gains or losses. Adds metadata gain_loss: "gain" or gain_loss: "loss" to transactions.

beancount
plugin "beancount.plugins.gain_loss"

2024-01-15 * "Sell AAPL"
  Assets:Brokerage  -10 AAPL {100 USD} @ 150 USD
  Assets:Cash        1500 USD
  Income:CapitalGains
; Transaction gets gain_loss: "gain" metadata

Options: None

generate_base_ccy_prices

Generates price entries in a base currency by applying exchange rates to existing prices.

beancount
plugin "beancount.plugins.generate_base_ccy_prices" "USD"

2024-01-01 price EUR 1.10 USD
2024-01-01 price ETH 2000 EUR
; Generates: 2024-01-01 price ETH 2200 USD (2000 * 1.10)

Options: Base currency code (e.g., "USD")

long_short

Classifies capital gains transactions as short-term or long-term based on holding period. Adds metadata holding_period: "short" or holding_period: "long".

beancount
plugin "beancount.plugins.long_short"

2024-01-15 * "Sell AAPL"
  Assets:Brokerage  -10 AAPL {100 USD, 2023-01-01} @ 150 USD
  Assets:Cash        1500 USD
  Income:CapitalGains
; Gets holding_period: "long" (held > 1 year)

Options: None

nounused

Reports warnings for accounts that are opened but never used in any transaction, balance, or other directive.

beancount
plugin "beancount.plugins.nounused"

2024-01-01 open Assets:OldAccount
; Warning: Account 'Assets:OldAccount' is opened but never used

Options: None

rename_accounts

Renames accounts using regex patterns. Useful for bulk account reorganization.

beancount
plugin "beancount.plugins.rename_accounts" "{'Expenses:Food:(.*)': 'Expenses:Dining:$1'}"

2024-01-01 open Expenses:Food:Groceries
; Becomes: 2024-01-01 open Expenses:Dining:Groceries

Options: Python dict mapping regex patterns to replacement strings. Use $1, $2 for capture groups.

rx_txn_plugin

Sets default metadata for Regular Expected Transactions (for beanahead integration). Transactions tagged #rx_txn get default final: None and roll: True metadata.

beancount
plugin "beancount.plugins.rx_txn_plugin"

2024-01-15 * "Monthly rent" #rx_txn
  Expenses:Rent      2000 USD
  Assets:Checking
; Gets metadata: final: None, roll: True

Options: None

unrealized

Calculates unrealized gains and losses for positions at market value.

beancount
plugin "beancount.plugins.unrealized" "Income:Unrealized"

2024-01-01 price AAPL 150 USD
; For positions bought at $100, generates unrealized gain entries

Options: Account for unrealized gains (e.g., "Income:Unrealized")

valuation

Tracks opaque fund values using synthetic commodities. Useful for retirement accounts or funds that don't provide per-share NAV.

beancount
plugin "beancount.plugins.valuation"

2024-01-01 * "401k contribution"
  Assets:401k:Fund    100 FUND401K {50 USD}
  Assets:Checking    -5000 USD

2024-06-30 custom "valuation" Assets:401k:Fund 6000 USD
; Creates synthetic position reflecting current value

Options: None

zerosum

Matches pairs of postings that sum to zero within a date range. Useful for tracking reimbursements, loans, or transfers.

beancount
plugin "beancount.plugins.zerosum" "Assets:Reimbursable 30"

2024-01-15 * "Expense to be reimbursed"
  Assets:Reimbursable   100 USD
  Assets:Cash          -100 USD

2024-01-20 * "Reimbursement received"
  Assets:Bank           100 USD
  Assets:Reimbursable  -100 USD
; These two postings match and are linked

Options: "Account:Pattern days" - Account pattern to match and maximum days between matching postings

Custom Plugins

For custom functionality, you can write plugins in two ways:

Write plugins in Rust, compile to WebAssembly, and use them without modifying rustledger.

Quick start:

bash
# Clone the template
git clone https://github.com/rustledger/rustledger
cp -r rustledger/examples/wasm-plugin-template my-plugin
cd my-plugin

# Build
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --release

# Use in your beancount file
# plugin "/path/to/target/wasm32-unknown-unknown/release/my_plugin.wasm"

See the Custom Plugins Guide for a complete walkthrough.

Native Rust Plugins (Contributors)

For adding plugins to rustledger itself, see Contributing Plugins.

Python Plugin Compatibility

Experimental Feature

Python plugin support is experimental and significantly slower than native plugins (10-100x). Use native equivalents when available.

rustledger can run some Python beancount plugins using CPython compiled to WebAssembly (WASI). This is primarily for migration purposes.

Supported Python Plugins

Most Python beancount plugins have native equivalents:

Python Pluginrustledger Equivalent
auto_accountsauto_accounts
implicit_pricesimplicit_prices
check_commoditycheck_commodity
leafonlyleafonly
noduplicatesnoduplicates
onecommodityonecommodity
sellgainssellgains
split_expensessplit_expenses
close_treeclose_tree
coherent_costcoherent_cost
pedanticpedantic
unique_pricesunique_prices
forecast (beanahead)forecast
effective_dateeffective_date
zerosumzerosum
unrealizedunrealized
rename_accountsrename_accounts
Custom PythonRewrite in Rust

Python Plugin Limitations

  • Performance: 10-100x slower than native plugins
  • First run: Downloads ~14MB CPython-WASI runtime
  • Compilation: First execution compiles WASM (~30 seconds)
  • Not all plugins work: C extensions and some stdlib modules unavailable
  • Debugging: Error messages may be less helpful

Migrating from Python Plugins

  1. Check if a native equivalent exists (see table above)
  2. If not, consider writing a WASM plugin
  3. As a last resort, Python plugins may work but with limitations

See Also