Coming from Beancount
Zhang reads beancount ledgers directly. You can point it at your main.bean and keep editing your files as before: Zhang shows them in its web UI, and what you record there is written back in beancount syntax.
This page shows how to serve a beancount ledger and lists, in Compatibility, where Zhang and beancount differ.
Serve a beancount ledger
Section titled “Serve a beancount ledger”Give zhang serve the directory of your ledger and the name of its main file:
zhang serve /path/to/ledger --endpoint main.beanWith Docker, mount the directory at /data and add --endpoint after the image name:
docker run --name zhang -v "/path/to/ledger:/data" -p "8000:8000" kilerd/zhang:latest --endpoint main.bean- A main file ending in
.bean,.bcor.beancountmakes Zhang read the whole ledger as beancount: the main file and every file it includes, whatever their extension. A ledger is either beancount or Zhang syntax, never a mix. - Open
http://localhost:8000. The Overview item in the sidebar shows how many problems Zhang found, and the Overview page lists them. Go through them first: most come from the differences below. - What you record in the web UI goes to a file with your main file’s extension, by default
data/{{year}}/{{month_str}}.bean, written in beancount syntax. See Recording Transactions.
Installation lists every option of zhang serve.
Posting metadata
Section titled “Posting metadata”Zhang reads the metadata of a transaction in a beancount file the way beancount and Fava do: metadata before the first posting belongs to the transaction, and every metadata line after a posting belongs to that posting, however it is indented.
2024-01-02 * "Cafe" "lunch" invoice: "2024-001" ; the transaction's Assets:Cash -10 CNY receipt: "r-17" ; the Assets:Cash posting's Expenses:Food 10 CNY category: "meals" ; the Expenses:Food posting'sOlder versions of Zhang wrote a transaction’s metadata after its postings. Such a file now reads differently in Zhang: that metadata belongs to the last posting, which is how Fava has always read it. Two keys keep working as before:
time: atimeafter the last posting, at the postings’ indentation as older Zhang wrote it, is still the transaction’s time of day, unless the transaction has atimeof its own or another posting has one. Atimeindented deeper than its posting stays the posting’s.document: a document on a posting is a document of its transaction, so documents attached to a transaction stay attached.
When Zhang writes a transaction, for example after you edit it in the web UI, it writes the transaction’s metadata before the postings and each posting’s metadata right under it, so the file reads the same in Zhang, beancount and Fava. See Transaction.
Compatibility
Section titled “Compatibility”Directives
Section titled “Directives”| Beancount | In Zhang |
|---|---|
open |
Read, with its commodities and booking method (open Assets:Broker HOOL "FIFO"). The commodities must be declared, but postings in other commodities are not reported. |
close |
Read. Closing an account that still holds something is reported as CloseNonZeroAccount. |
commodity |
Read, and required: see Commodities must be declared. |
| transactions | Read, with the flags *, ! and the other Beancount flags, on the transaction and on a posting, the txn keyword, tags, links, costs ({}, {{}}, with a date and a label) and prices (@, @@). |
balance |
Read, with an optional ~ tolerance. Exact without one: see Balance assertions are exact. |
pad |
Read, paired with the balance it serves: see Pads. |
note, event |
Read. |
document |
Read. The path is relative to the file that holds the directive, as in beancount: see Document paths. |
price |
Read, for valuations in queries and the Commodities page. |
query |
Read: the queries appear in the Saved menu of the Query page. |
custom |
Read. custom "budget" … defines budgets: see Budgets. |
option |
Read. Only some options have an effect: see Options. |
plugin |
Python plugins do not run: see Plugins. |
include |
Read, including * patterns such as include "2024/*.bean". |
pushtag, poptag, pushmeta, popmeta |
Read. |
A time: "HH:MM:SS" metadata entry gives a directive a time of day, except a balance or a pad, whose time Zhang ignores, as beancount does: see Balance times.
The metadata values beancount reads without quotes, an account, a currency, a number or an arithmetic expression, an amount, a date, a tag, TRUE, FALSE and NULL, are read as written and kept as text, on a transaction, on a posting and on every other directive. counterpart: Assets:Bank is the text Assets:Bank, limit: 10.00 USD the text 10.00 USD, and 1 + 2 stays 1 + 2, where beancount computes 3. A tag value keeps its # and TRUE, FALSE and NULL stay those words, where beanquery shows True, False and an empty value. Zhang writes every such value back as it was written.
Zhang cannot read the following. A file using them does not load at all:
- a cost with only a date or a label, such as
{2024-01-01}or{"lot-1"}. Write the cost first:{100.00 USD, 2024-01-01}; - compound costs,
{100 # 9.95 USD}, and{*}.
Options
Section titled “Options”Zhang uses these options of a beancount ledger:
title, shown in the web UI;operating_currency, but only one: Zhang keeps the last one and reports every other asMultipleOperatingCurrencyDetect;account_previous_balances,account_previous_earnings,account_previous_conversions,account_current_earnings,account_current_conversionsandconversion_currency, in the accounting periods of queries.
Every other beancount option, such as booking_method, inferred_tolerance_default or documents, is listed on the Settings page and otherwise ignored. Zhang has options of its own, such as default_booking_method and timezone, which beancount ignores in turn. See Options.
Behavior differences
Section titled “Behavior differences”Commodities must be declared
Section titled “Commodities must be declared”Every commodity a posting weighs in, a price names or an open lists must have a commodity directive. The operating currency is declared by its option. Beancount does not require commodity directives; Zhang reports a missing one as CommodityDoesNotDefine. Add one per commodity, dated before its first use:
1970-01-01 commodity HOOLBalance assertions are exact
Section titled “Balance assertions are exact”Beancount lets a balance pass when the difference is within a tolerance it derives from the number of decimals: balance Assets:A 10.00 USD passes on a balance of 10.004 USD. Zhang does not: an assertion holds only on the exact amount, or within the tolerance you write after ~, such as 10.00 ~ 0.005 USD. See Balances and Padding.
Transactions balance at the commodity’s precision
Section titled “Transactions balance at the commodity’s precision”A transaction balances when, in each commodity, its weights add up to zero once rounded at the commodity’s precision: the precision metadata of its commodity directive, 2 decimals without one. Beancount derives this tolerance from the numbers written in the transaction instead. Give commodities with finer amounts, such as BTC, a precision of their own. See Lots and Cost Basis.
Booking
Section titled “Booking”Zhang books with FIFO unless told otherwise, while beancount’s default is STRICT. For beancount’s behavior, add option "default_booking_method" "STRICT"; beancount’s own booking_method option is not read. NONE, AVERAGE and AVERAGE_ONLY are not implemented: an account using one gets an error and books with the default method. Lot labels select lots as in beancount: a sale written {, "first"} reduces the lot labelled first. See Lots and Cost Basis.
Zhang pairs each pad with the balance entries it serves as beancount does: the next balance of the account in each commodity on a later day, up to the account’s next pad, never one on the same day as the pad. The padding transaction is dated on the pad, the pad and its balance may be in different files, and a pad that serves no balance is reported as UnusedPad. The differences:
- A pad brings the account to exactly the asserted amount, even within an explicit
~tolerance, where beancount pads nothing. - Only an assertion on the padded account itself uses the
pad: beancount also lets an assertion on a sub-account use up thepadof its parent account. - Of two
pads of an account on the same day in different files, the one Zhang orders last pads, which may not be the one beancount uses.
See The pad directive, and Balance for the cases of parent accounts and sub-accounts.
Balance times
Section titled “Balance times”Zhang checks a balance at the start of its date, before the transactions of that day, as beancount does, and ignores its time metadata. Earlier versions of Zhang checked it at that time, after the transactions of the day before it: where that changes what a balance checks, it is listed with a BalanceTimeIgnored notice. Date such a balance on the next day to check it after the day’s transactions.
Document paths
Section titled “Document paths”Zhang reads the path of a document relative to the file that holds it, as beancount does, and the documents you upload are written that way. Earlier versions of Zhang wrote the paths of uploaded documents relative to the ledger root, into files like data/2026/10.bean, which beancount reports as missing. Zhang still opens those documents. On the local disk, it lists a DocumentPathRelativeToRoot notice on each, with the path to write instead, and reports a document found nowhere as DocumentNotFound. See Document.
Prices
Section titled “Prices”Only price directives give prices. The prices written on postings with @ and @@ do not, as they would with beancount’s implicit_prices plugin.
Other checks
Section titled “Other checks”- Closing an account that still holds something is reported as an error. Beancount allows it.
- Postings in a commodity that the account’s
opendoes not list are not reported. Beancount reports them. - Account names must start with
Assets,Liabilities,Equity,IncomeorExpenses. Thename_assets… options, which rename them in beancount, are not read. - In a quoted string, a backslash that does not start an escape is kept:
"\d"stays\d, where beancount drops the backslash.
Not supported
Section titled “Not supported”Plugins
Section titled “Plugins”Beancount’s plugins are Python code, and Zhang does not run them, including the ones that ship with beancount, such as auto_accounts or implicit_prices. Without them:
- open every account explicitly, instead of relying on
auto_accounts; - check what other plugins did for you, and do it in the ledger or with a query.
plugin lines stay harmless as long as the ledger does not enable Zhang’s own plugins: with option "features.plugin" "true", Zhang tries to load every plugin as a WASM module, and a Python module name makes the whole ledger fail to load. Zhang’s plugins are WASM modules: see Using Plugins.
Budgets
Section titled “Budgets”Zhang reads budgets written as custom directives in the form beancount accepts: custom "budget" "Food" "CNY", custom "budget-add" "Food" 2000 CNY, custom "budget-transfer" "Fun" "Food" 300 CNY and custom "budget-close" "Food", with a quoted type and quoted names, and writes budgets that way, so bean-check and Fava accept the ledger. The unquoted form earlier versions of Zhang wrote, custom budget Food CNY, is still read, but beancount rejects it with Invalid token: switch to the quoted form. See Budget.
Zhang-only features
Section titled “Zhang-only features”These work in Zhang and have no equivalent in beancount:
- budgets, as above;
- a time of day on entries, which a beancount ledger carries in
timemetadata; - Zhang’s WASM plugins and its query language extensions, such as the
#budgetsand#errorstables.
A Zhang ledger (main.zhang) has more syntax of its own, such as balance … with pad and dates with a time; beancount cannot read it. Zhang has no command to convert between the two syntaxes, so keep your ledger in beancount syntax if you want to keep using beancount tools.
