double-entry-generator turns payment statements into Beancount and Ledger
Rule-based double-entry bookkeeping importer (from Alipay/WeChat/Huobi etc. to Beancount/Ledger).
At a glance
- What is it?
- deb-sig/double-entry-generator is a Go command line tool that reads bank, payment platform and exchange statement exports and rewrites them as double-entry bookkeeping in Beancount or Ledger syntax. It covers a long list of Chinese and Canadian providers, and it leans on a YAML rule file to decide where each transaction lands.
- Who is it for?
- double-entry-generator suits a personal finance workflow that already exports CSV or XLSX statements from Chinese payment platforms and Canadian banks and wants double-entry output without hand-writing transactions. It does not suit a bookkeeping system of record, and it will not rescue a statement type it does not recognise, since an unknown transaction type stops the run unless the version is at least v2.10.0 and the ignore flag is passed.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 19 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Statement exports in, double-entry lines out
The problem this tool addresses is specific and unglamorous. Payment platforms and banks hand you a spreadsheet export whose columns mean nothing outside that platform: transaction id, counterparty name, a status string, a category label the platform invented. Turning that into a double-entry ledger means writing code, once, for every institution you use, and keeping it working as those exports change shape.
double-entry-generator does that conversion as a rule set instead of a program. You describe the accounts and the classification rules in YAML, point the tool at an export file, and it writes a Beancount file or a Ledger file. The output targets are the two widely used plain-text accounting formats, so the result can be read by either toolchain without further conversion.
The intended user is a person keeping personal finances in plain text files who also pays for everything through a phone: the provider list is dominated by Chinese banks and payment platforms, plus a handful of Canadian banks such as Bank of Montreal, TD and HSBC Hong Kong. There is also a crypto exchange provider and two securities brokers, which widens the scope beyond daily spending to transfers and trades. The project is written in Go and released under Apache-2.0.
The pipeline runs provider, IR, compiler, analyser
The architecture diagram in the project documentation shows a straight line of stages: a translate command feeds a provider, the provider feeds an intermediate representation, the intermediate representation feeds a compiler, and the compiler feeds an analyser. Provider names such as alipay, wechat, huobi and ccb sit under one end of the line, the output dialects beancount and ledger sit in the middle, and the analysers sit at the other end.
That shape explains why the tool can serve so many institutions with one configuration file. The intermediate representation is the pivot: a provider only has to turn a platform-specific spreadsheet into that neutral form, and a compiler only has to turn the neutral form into one output dialect. The extension points follow the same split. Adding a new bill format means implementing a new provider under `pkg/provider`, and adding a new bookkeeping language means implementing a new compiler under `pkg/compiler`.
The repository is laid out as a normal Go project with `cmd/`, `pkg/`, `main.go`, a `Makefile`, a `test/` directory and per-provider directories under `example/` holding sample statements and sample configs. Two extras stand out. There is a `wasm/` directory, so the conversion logic can be built for the browser, and a `flake.nix` and `flake.lock`, so the Nix users among you can pin the build environment rather than chasing Go version drift.
Installing with Go, Homebrew or a release binary
Three installation routes are documented. The Go toolchain route is the one the project recommends:
go install github.com/deb-sig/double-entry-generator/v2@latestOn macOS there is a Homebrew tap:
brew install deb-sig/tap/double-entry-generatorThe third route is to download a prebuilt binary for your architecture from the GitHub Release page, which is also where the versioned builds live.
Whichever route you take, the project suggests confirming which build you actually have, since a `go install` puts the binary on your Go bin path and can coexist with an older copy. The command it points to is `go version -m $(which double-entry-generator)`, which prints the module version embedded in the binary and is the quickest way to tell a Homebrew install from a `go install` one. The module path carries a `/v2` suffix, which matters if you write your own Go code against it or pin it in a Makefile.
Running translate against a statement file
The command shape is the same for every provider: `translate`, a provider flag, a target flag, and the path to the statement file. The documented examples cover the three common cases:
double-entry-generator translate -p alipay -t beancount alipay_records.csv
double-entry-generator translate -p wechat -t ledger wechat_records.xlsx
double-entry-generator translate -p ccb -t beancount ccb_records.xlsThe provider flag selects the parser, the target flag selects the output dialect, and the file extension follows the format that provider actually exports. The examples in the repository show the long form as well, where a config file is named explicitly and the result is written to a chosen output path:
double-entry-generator translate \
--config ./example/alipay/config.yaml \
--output ./example/alipay/example-alipay-output.beancount \
./example/alipay/example-alipay-records.csvThe WeChat example in the repository adds a `--provider wechat` flag alongside the config file, which is worth noticing because it shows that the provider can be given either as a short flag or as a named option. If you only run the short form without a config file, the tool has no rules for categorising transactions, so the per-provider default accounts are what you get.
Rules match on category, time window and payee
Classification happens in YAML, and the structure is provider scoped. A config file starts with the fallback accounts for the two sides of a transaction plus a currency:
defaultMinusAccount: Assets:FIXME
defaultPlusAccount: Expenses:FIXME
defaultCurrency: CNYAfter that comes a block per provider, each with a list of rules. A rule can match on the platform's own category label, on a time window, or on the counterparty name, and each rule names the account the matched money should end up in. The documented example maps a lunch category limited to a midday window onto a food and drink expense account, and maps a ride-hailing counterparty onto a transport account.
This is the part of the design that will decide whether the tool is useful to you. Rules are evaluated against fields the platform export already contains, which means a platform that hides the counterparty or rewrites its category labels can defeat a rule. There is no documented regular expression support, no amount threshold and no per-rule fallback chain in what the project publishes, so a statement whose categories do not match any rule falls through to the default accounts, and you end up with `Assets:FIXME` and `Expenses:FIXME` entries that still need manual attention.
Provider coverage is wide but format specific
The provider list is the strongest argument for the tool, and it is where you should look first. Banks include China Construction Bank, Bank of China, ICBC, CITIC, China Merchants Bank, Bank of Communications debit, Agricultural Bank of China debit, CGB credit, HSBC Hong Kong, Bank of Montreal and TD. Payment platforms are Alipay and WeChat. Securities brokers are Haitong and Huaxi. Crypto is covered by Huobi and OKLink, the latter for multi-chain token transfers including ERC20 and TRC20. Life services are Meituan for food delivery and in-store spending, and JD for shopping.
Format support is narrower than provider coverage, and that is the trap. China Construction Bank accepts CSV, XLS and XLSX, Alipay accepts CSV only, and WeChat accepts CSV and XLSX. Bank of China handles both debit card and credit card statements, and ICBC and China Merchants Bank detect the card type automatically, while CITIC is listed for credit card statements only.
There is also a wrinkle worth knowing about before you point it at a real statement. The project documents a failure mode with the message `Failed to get the tx type`, which happens when a transaction type is not recognised. The documented handling is to report it as an issue, and, for expense transactions on version v2.10.0 or later, to pass the `--ignore-invalid-tx-types` flag so the run continues past those rows. Ignoring them is convenient, but it also means the output file will be incomplete in a way that is easy to miss.
Releases, maintenance and what an upgrade can break
The project is not archived and the last push was on 2026-10-04. Recent releases show a project that ships, but not on a fixed cadence: v2.15.0 on 2026-01-12, v2.15.1 on 2026-03-19, and v2.16.0 on 2026-10-03, so the gap between the patch release and the feature release is about seven months. Release artifacts are produced by a `.goreleaser.yml` file in the repository, and the Makefile drives cross-provider test configurations such as alipay against both beancount and ledger, which is a reasonable sign that output for each provider dialect is checked rather than assumed.
The licence is Apache-2.0, which includes an explicit patent grant and requires you to keep any notices when redistributing. That is a reasonable choice for a personal tool you might later vendor into a team workflow, though the licence says nothing about the accuracy of the output.
The upgrade cost is where the caution belongs. There is no documented changelog format in what the project publishes, no schema versioning for the YAML config, and no migration guide. Because the config file is your only record of which account each category maps to, a change in how a provider reports its categories can silently move transactions into different accounts rather than failing loudly. The safest habit is to keep your config under version control and to diff the generated file before and after a version bump.
Editorial conclusion
double-entry-generator suits a personal finance workflow that already exports CSV or XLSX statements from Chinese payment platforms and Canadian banks and wants double-entry output without hand-writing transactions. It does not suit a bookkeeping system of record, and it will not rescue a statement type it does not recognise, since an unknown transaction type stops the run unless the version is at least v2.10.0 and the ignore flag is passed. Before adopting it, check that your provider appears in the provider list with your file format, since CCB accepts CSV, XLS and XLSX while Alipay accepts CSV only, and note that v2.16.0 shipped on 2026-10-03 after a seven month gap from v2.15.1.
Frequently asked questions
How do I install double-entry-generator on macOS?
The project documents a Homebrew tap, `brew install deb-sig/tap/double-entry-generator`, and a Go route, `go install github.com/deb-sig/double-entry-generator/v2@latest`. Prebuilt binaries per architecture are also published on the GitHub Release page.
Which statement formats can double-entry-generator read?
Format support differs by provider: China Construction Bank accepts CSV, XLS and XLSX, Alipay accepts CSV, and WeChat accepts CSV and XLSX. The provider list also covers Bank of China, ICBC, HSBC Hong Kong, BMO, TD, Huobi, OKLink, Meituan and JD.
What does the "Failed to get the tx type" error mean and how do I skip it?
It appears when a transaction type in the statement is not recognised. The documented handling is to report the case as an issue, and on version v2.10.0 or later to pass the `--ignore-invalid-tx-types` flag so expense transactions of that type are skipped.
How does double-entry-generator decide which account a transaction goes to?
Rules live in a YAML config file under the provider name, and each rule can match on the platform category, a time window or the counterparty name, then name a target account. Transactions matching no rule fall back to the default accounts set in the config.
Can double-entry-generator write Ledger files as well as Beancount files?
Yes. The target flag selects the output dialect, so the same provider can be written as beancount or ledger. Adding a new output language means implementing a new compiler under `pkg/compiler`.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/deb-sig-double-entry-generator)Was this useful?