Storage and import/export for Beancount directives via Ecto.
Beancount.Storage is the high-level persistence API. It converts
Beancount.Directives.* structs to Beancount.Schemas.* rows (and back),
backed by Beancount.Repo. Use it to load a ledger into the database, read
it back, or round-trip through .bean files.
For SQL queries over stored rows, use Beancount.Queries. For balance
reports that require inventory booking, use Beancount.Report (which runs
BQL through the configured engine).
Configuration
The default backend is SQLite in-memory (:memory:), which requires no setup
and is cleared when the OS process exits. Keep pool_size: 1 with
:memory: — each connection in the pool gets its own empty database.
config :beancount_ex, Beancount.Repo,
database: ":memory:",
pool_size: 1For a persistent ledger, point :database at a file:
config :beancount_ex, Beancount.Repo, database: "ledger.db"The migration in priv/repo/migrations creates one table per directive type;
the application supervisor runs it on startup.
Public functions
| Function | Description |
|---|---|
import_file/1 | Parse a .bean file and store the directives |
store/1 | Replace all rows with a directive list |
load/0 | Rebuild directive structs from the database |
export_file/1 | Render stored directives to a .bean file |
clear/0 | Delete every row from all directive tables |
store/1 and import_file/1 run in a transaction: existing rows are
cleared first, then each directive is inserted in source order (file_order).
Example: store, query, export
ledger = [
Beancount.open(~D[2026-01-01], "Assets:Bank", ["USD"]),
Beancount.transaction(~D[2026-01-31], "*", "Employer", "Salary", [
Beancount.posting("Assets:Bank", Decimal.new("100"), "USD"),
Beancount.posting("Income:Salary", Decimal.new("-100"), "USD")
])
]
{:ok, 2} = Beancount.Storage.store(ledger)
[%Beancount.Directives.Open{account: "Assets:Bank"} | _] =
Beancount.Storage.load()
Beancount.Queries.list_opens(prefix: "Assets")
:ok = Beancount.Storage.export_file("out.bean")Import / export
{:ok, count} = Beancount.Storage.import_file("ledger.bean")
:ok = Beancount.Storage.export_file("out.bean")Future backends: PostgreSQL (via postgrex), Mnesia (via ecto_mnesia).
Summary
Functions
Clear all directives from the database.
Export all directives from the database to a .bean file.
Import directives from a .bean file into the database.
Load all directives from the database.
Store a list of directives into the database.
Functions
@spec clear() :: :ok
Clear all directives from the database.
Deletes every row from all directive tables. Always returns :ok.
Examples
:ok = Beancount.Storage.clear()
[] = Beancount.Storage.load()
Export all directives from the database to a .bean file.
Loads every stored directive (see load/0) and renders it to Beancount text
at path.
Examples
:ok = Beancount.Storage.export_file("out.bean")
@spec import_file(Path.t()) :: {:ok, non_neg_integer()} | {:error, term()}
Import directives from a .bean file into the database.
Parses the file and stores the resulting directives, replacing any existing rows. Returns the number of directives stored.
Examples
{:ok, count} = Beancount.Storage.import_file("ledger.bean")
# => {:ok, 128}
@spec load() :: [Beancount.Directive.t()]
Load all directives from the database.
Rebuilds Beancount.Directives.* structs from the stored rows. Dated
directives are returned in date order; undated directives (option,
include, plugin, pushtag, poptag) sort ahead of dated ones.
Examples
Beancount.Storage.store([Beancount.open(~D[2026-01-01], "Assets:Bank", ["USD"])])
[%Beancount.Directives.Open{account: "Assets:Bank"}] = Beancount.Storage.load()
@spec store([Beancount.Directive.t()]) :: {:ok, non_neg_integer()} | {:error, term()}
Store a list of directives into the database.
Runs in a transaction that first clears all existing rows, then inserts each
directive into its matching table. Returns {:ok, count} where count is the
number of directives actually stored; entries that are not recognized
directives are skipped and not counted.
Examples
ledger = [
Beancount.open(~D[2026-01-01], "Assets:Bank", ["USD"]),
Beancount.transaction(~D[2026-01-31], "*", "Employer", "Salary", [
Beancount.posting("Assets:Bank", Decimal.new("100"), "USD"),
Beancount.posting("Income:Salary", Decimal.new("-100"), "USD")
])
]
{:ok, 2} = Beancount.Storage.store(ledger)