Deterministic rendering of directive streams into Beancount text.
Rendering is intentionally pure and deterministic: rendering the same directive stream twice always produces byte-identical output. This property is essential for golden-file regression testing and for using Beancount as a behavioral oracle.
The module also exposes the low-level formatting helpers
(format_date/1, format_decimal/1, quote_string/1, ...) used by the
individual Beancount.Directive implementations.
Summary
Functions
Format a Date as an ISO-8601 YYYY-MM-DD string.
Format a Decimal using plain (non-scientific) notation.
Render a scalar value as used in metadata and custom directives.
Join header text, metadata lines and body lines into a single fragment.
Quote and escape a string the way Beancount expects.
Render a list of directives into a complete .bean document.
Render a metadata map into indented key: value lines.
Render the postings of a transaction as aligned, indented lines.
Render #tag and ^link suffixes for a transaction header.
Functions
Format a Date as an ISO-8601 YYYY-MM-DD string.
Examples
iex> Beancount.Renderer.format_date(~D[2026-01-31])
"2026-01-31"
Format a Decimal using plain (non-scientific) notation.
Examples
iex> Beancount.Renderer.format_decimal(Decimal.new("-5000"))
"-5000"
iex> Beancount.Renderer.format_decimal(Decimal.new("12.50"))
"12.50"
Render a scalar value as used in metadata and custom directives.
- binaries become quoted strings
Decimaland numbers become bare numbersDatebecomes an ISO date- booleans become
TRUE/FALSE - atoms become barewords (useful for accounts/currencies)
Beancount.Value.Account,Tag, andAmountfor custom directives
Examples
iex> Beancount.Renderer.format_value("hello") == Beancount.Renderer.quote_string("hello")
true
iex> Beancount.Renderer.format_value(Decimal.new("42"))
"42"
iex> Beancount.Renderer.format_value(~D[2026-01-01])
"2026-01-01"
iex> Beancount.Renderer.format_value(Beancount.account_value("Assets:Bank"))
"Assets:Bank"
Join header text, metadata lines and body lines into a single fragment.
Used by directives that span multiple lines (such as transactions).
Examples
iex> Beancount.Renderer.lines_to_fragment(["header", " posting"])
"header\n posting"
Quote and escape a string the way Beancount expects.
Examples
iex> Beancount.Renderer.quote_string(~S(a "quoted" value))
~S("a \"quoted\" value")
@spec render([Beancount.Directive.t()]) :: binary()
Render a list of directives into a complete .bean document.
Directives are separated by a single blank line and the document ends with a trailing newline.
Examples
iex> ledger = [
...> Beancount.open(~D[2026-01-01], "Assets:Bank", ["USD"]),
...> Beancount.close(~D[2026-12-31], "Assets:Bank")
...> ]
iex> Beancount.Renderer.render(ledger)
"2026-01-01 open Assets:Bank USD\n\n2026-12-31 close Assets:Bank\n"
@spec render_metadata(map(), non_neg_integer()) :: [binary()]
Render a metadata map into indented key: value lines.
Keys are emitted in deterministic (sorted) order. Returns an empty list when there is no metadata.
Examples
iex> lines = Beancount.Renderer.render_metadata(%{"note" => "reviewed"})
iex> hd(lines) =~ "note:"
true
iex> Beancount.Renderer.render_metadata(%{})
[]
@spec render_postings([Beancount.Directives.Posting.t()]) :: [binary()]
Render the postings of a transaction as aligned, indented lines.
Amounts are right-aligned so that decimal values line up, matching the conventional Beancount layout. Posting-level metadata is rendered indented beneath its posting.
Examples
iex> postings = [
...> Beancount.posting("Assets:Bank", Decimal.new("100"), "USD"),
...> Beancount.posting("Income:Salary", Decimal.new("-100"), "USD")
...> ]
iex> lines = Beancount.Renderer.render_postings(postings)
iex> Enum.all?(lines, &String.starts_with?(&1, " "))
true
Render #tag and ^link suffixes for a transaction header.
Tags are rendered before links, each in sorted order for determinism.
Examples
iex> Beancount.Renderer.render_tags_and_links(["trip"], ["invoice-1"])
" #trip ^invoice-1"