Hex.pm Version HexDocs Hex.pm License CI Coverage

An idiomatic Elixir interface to Beancount.

beancount_ex is not a General Ledger. It is a compatibility layer and behavioral oracle: it constructs Beancount directives as typed Elixir structs, renders them to deterministic .bean text, and validates them through a configurable engine. The default engine wraps real Beancount (bean-check / bean-query); the native Beancount.Engine.Elixir can replace it without changing the public API. Optional Ecto storage (Beancount.Storage) persists directives to SQLite when you need it.

Why this library exists

A native Elixir General Ledger needs something trustworthy to be validated against. Beancount is a mature, battle-tested double-entry accounting system. By wrapping it behind a stable Elixir API, beancount_ex:

  • gives applications an idiomatic way to construct and check ledgers today, and
  • becomes the oracle that the native engine must agree with.
                 Public API: Beancount.*
                          |
            +-------------+-------------+
            v                           v
      Directive DSL              Engine Behaviour
            |                           |
            v                           v
        Renderer               Beancount.Engine
            |                  +--------+--------+
            |                  v                 v
            |             Engine.CLI      Engine.Elixir
            |             (bean-check)    (native booking)
            |
            v  (opt-in)
        Ecto Storage
        (SQLite/Postgres)
        Storage / Queries

Installation

def deps do
  [
    {:beancount_ex, "~> 0.6"},
    # optional: Explorer DataFrames for report tables (see guides/accounting/running_reports.md)
    {:explorer, "~> 0.11"}
  ]
end

To run checks and queries you also need Beancount installed (only required at runtime for Beancount.check/1, Beancount.query/2 and friends):

pip install beancount beanquery

bean-check comes from the beancount package; bean-query comes from the separate beanquery package (required for Beancount v3 query support).

Usage

ledger = [
  Beancount.open(~D[2026-01-01], "Assets:Bank", ["USD"]),
  Beancount.open(~D[2026-01-01], "Income:Salary", ["USD"]),
  Beancount.transaction(~D[2026-01-31], "*", "Employer", "Salary", [
    Beancount.posting("Assets:Bank", Decimal.new("5000"), "USD"),
    Beancount.posting("Income:Salary", Decimal.new("-5000"), "USD")
  ])
]

# Deterministic rendering
bean = Beancount.render(ledger)

# 2026-01-01 open Assets:Bank USD
#
# 2026-01-01 open Income:Salary USD
#
# 2026-01-31 * "Employer" "Salary"
#   Assets:Bank     5000 USD
#   Income:Salary  -5000 USD

# Validation through the configured engine
{:ok, result} = Beancount.check(ledger)

# Parse `.bean` text
{:ok, directives} = Beancount.parse_text(bean)

# Query (BQL) and reports
{:ok, result} = Beancount.query(ledger, "SELECT account, sum(position) GROUP BY account")
{:ok, balances} = Beancount.balances(ledger)
{:ok, income} = Beancount.income_statement(ledger)

# Optional: Explorer DataFrame (requires {:explorer, "~> 0.11"})
# df = Beancount.Explorer.to_dataframe(balances)

# Optional: persist directives to the built-in SQLite store
{:ok, 3} = Beancount.Storage.store(ledger)
loaded = Beancount.Storage.load()
Beancount.Queries.list_opens(prefix: "Assets")

The public API is Beancount. There is intentionally no public BeancountEx module and you never need to reference the internal Beancount.Directives namespace.

Configuration

config :beancount_ex,
  engine: Beancount.Engine.CLI,
  bean_check_path: "bean-check",
  bean_query_path: "bean-query"

Testing

mix test passes without Beancount installed: unit, property and golden-file rendering tests have no external dependency.

mix test                      # unit + property + golden (no Beancount needed)
mix test --include beancount  # also runs integration tests (needs bean-check)
mix beancount.golden.update   # regenerate golden fixtures

Guides

Accounting (build a UI or ledger)

For programmers building accounting features:

Library (internals and testing)

License

Released under the MIT License.