# From Firefly III to Beancount and Fava
Date: 2026-10-29
Canonical: https://bytesdust.com/posts/firefly-reports-beancount/
Tags: firefly-iii, beancount, python, finance
Summary: Firefly Reports 2.0 can export your Firefly III transactions to a Beancount v3 ledger you can open in Fava. How the mapping works, what the output looks like and what it can&rsquo;t do.


Firefly III collects my transactions. {{< elink url="https://beancount.github.io/docs/" text="Beancount" >}} keeps them in a plain-text ledger I can check and put under version control, and {{< elink url="https://beancount.github.io/fava/" text="Fava" >}} gives that ledger a web interface. Between the two I needed a bridge, so Firefly Reports 2.0 got one: `firefly-reports export beancount`.

Why bother, if Firefly III already has the data? Because databases change: schema migrations, relations, interdependencies between tables. A plain text file is forever. I'd rather keep a copy of my finances in a format I can still read and diff long after any particular tool is gone.

For the rest of the release, see the [2.0 announcement](/posts/firefly-reports-2/).

{{< project-button name="firefly-reports" >}}

## What lands on disk

The command reads your transactions from Firefly III and writes a Beancount v3 ledger. By default the layout is yearly: one `accounts.beancount` plus a `firefly-YYYY.beancount` for each year. The yearly files carry no timestamp and don't change until that year's data does, so a version-controlled ledger only shows diffs where something actually changed. `accounts.beancount` carries the generation date, unless you pass `--no-timestamp`. A `single` layout writes everything to one file.

Each transaction keeps its payee, tags, notes, links between transactions, IBAN and, for foreign currency, the original amount. Every entry also carries its Firefly ID as metadata, so you can trace it back.

## Mapping in two passes

Expense and income accounts in Beancount are derived from the Firefly **category**. If you organise your data by counterparty account instead, use `--classify-by account`.

The mapping is built in two passes, so you can review it before committing to it. The first pass only writes a draft:

```bash
firefly-reports export beancount --scaffold --map beancount_map.yaml
```

The draft lists every Firefly account and category with the Beancount account it would get. For categories it also shows how many splits use each one:

```yaml
accounts:
  "Checking Account": "Assets:Checking-Account"  # automatic
  "Home Mortgage": "Liabilities:Home-Mortgage"  # automatic

categories:
  expense:
    "Groceries": "Expenses:Groceries"  # split: 1
    "Travel": "Expenses:Travel"  # split: 1
  income:
    "Fees": "Income:Fees"  # split: 2
```

Edit the right-hand sides to rename or group accounts (several categories may share one), then run the export:

```bash
firefly-reports export beancount --init --validate --out ledger --map beancount_map.yaml
```

Other useful flags: `--dry-run` shows what would be written, `--validate` runs `bean-check` on the result (install `firefly-iii-reports[beancount]` for that), `--init` creates `main.beancount`, an investments file and a prices file, and `--no-timestamp` leaves out the generation date.

## What the output looks like

All the examples in this post come from a made-up data set, not from a real instance. A consulting invoice, with its tags:

```beancount
2025-01-15 * "Acme Consulting LLC" "Invoice #001" #invoice #taxes-2025
  firefly_id: "2"
  Income:Fees                    -3200.00 EUR
  Assets:Checking-Account         3200.00 EUR
```

An income in a foreign currency, kept with its original amount and the amount actually received:

```beancount
2025-03-10 * "Freelance USA Inc" "Consulting"
  firefly_id: "9"
  Income:Fees                     -200.00 USD @@ 184.20 EUR
  Assets:Checking-Account         184.20 EUR
```

A transfer between two of your own accounts, with no payee:

```beancount
2025-03-15 * "Broker deposit"
  firefly_id: "10"
  Assets:Checking-Account        -1000.00 EUR
  Assets:Acme-Investments         1000.00 EUR
```

The generated files start with a `GENERATED - DO NOT EDIT BY HAND` comment. With `--init` the `main.beancount` includes them, and it also sets Fava's language to match `--lang`.

## Opening it in Fava

```bash
fava ledger/main.beancount
```

The Journal shows payees, narrations and tags as imported:

![Fava's Journal page listing generated transactions with payee, narration and tags such as invoice, bills and holiday](fava-journal.webp)

And the Income Statement, which sums up income and expenses by account and month:

![Fava's Income Statement page: a monthly chart of income and expenses and tables of income and expenses by account](fava-income-statement.webp)

## What it doesn't do

Only cash movements are exported. Securities, quantities and prices aren't stored in Firefly III, so they can't come from there: you keep them by hand, in the investments and prices files.

Also, generated files are replaced on every run, so don't edit them. Put your own entries in separate files, and keep in mind that `main.beancount` is created only with `--init` and never overwritten afterwards. That's where your own includes and options belong.

## Install

```bash
pip install "firefly-iii-reports[beancount]"
firefly-reports export beancount --help
```

The {{< elink url="https://github.com/fran33git/firefly-reports/wiki/Beancount-Export" text="Beancount Export page on the wiki" >}} has the full set of options, the example output and some queries to run in Fava.

Firefly III and its database will keep changing. The ledger this command writes is just text, and that's the point: a copy of your finances you can still open, read and diff years from now, whatever happens to the tools around it.

{{< github-button repo="fran33git/firefly-reports" >}}

{{< pypi-button package="firefly-iii-reports" >}}

