firefly-iii/csv-importer
A command-line importer for bringing CSV bank statements into Firefly III. It maps CSV columns to transaction fields, supports multiple formats, validates and converts data, and can run interactively or from configuration files.
The first thing to internalize: firefly-iii/csv-importer is a standalone Laravel application, not a Composer library you require into an existing project. You don't add it to a composer.json alongside your app — you run it as its own service that talks to a Firefly III instance over the REST API. Treat it that way from day one and most of the surprises disappear.
Minimal setup:
docker run -d --name csv-importer \
-p 8081:8080 \
-e FIREFLY_III_URL=https://firefly.example.com \
-e FIREFLY_III_ACCESS_TOKEN=<token> \
-e APP_KEY=base64:... \
fireflyiii/csv-importer:latest
If you're building from source, composer install && cp .env.example .env && php artisan key:generate && php artisan serve works, but the container is the supported path.https://firefly.example.com/api/v1/about with the token via curl. If that fails, the importer will fail in opaque ways later.http://localhost:8081. The UI is the fastest way to learn the column-mapping model without writing config by hand.Where to look first in the repo: routes/, app/Http/Controllers/, and the import command under app/Console/Commands/. The mapping logic lives in app/Services/ — that's where the CSV → Firefly III transaction model happens.
Web-first mapping, CLI-first execution. Design your workflow so the web UI is used once to produce a mapping config, then the CLI is used forever after. This is the intended happy path and keeps you out of trial-and-error territory.
Version-control your mapping configs. The importer persists mapping definitions (typically JSON) under storage/app/. Copy these into your repo and mount or symlink them back into the container. A mapping config is code: it encodes field transformations, date formats, and account resolution rules. When a bank changes its export format, you want to see that as a diff, not discover it in a failed import.
CLI import for automation. The real leverage is scheduled imports:
docker exec csv-importer php artisan importer:import /path/to/file.csv
Wire this into cron/systemd timers. Combine with a step that first pulls the CSV from wherever your bank drops it (email attachment, S3, etc.).
Routine imports. For recurring bank feeds, use the importer's routine concept — define a routine that pairs a file location with a mapping, then trigger it on a schedule. This is the "set and forget" pattern and is what makes the tool genuinely useful day-to-day rather than a one-off tool.
Token rotation as a first-class concern. Personal access tokens in Firefly III can expire. Store the token in your secret manager, not in a compose file that gets committed. When the token rotates, the importer container must be recreated (env vars are not hot-reloaded).
Idempotency via Firefly III's duplicate detection. Rather than deduplicating CSVs yourself, rely on Firefly III's built-in detection keyed on date + amount + description. Re-run the same file twice and verify nothing is duplicated — if it is, your column mapping is probably dropping or mangling the description.
Integration pattern: pre-process, then import. The importer is deliberately not a data-cleaning tool. Do your concatenation, encoding fixes, and column trimming with a small script (Python/awk) upstream, then hand the importer a clean, stable CSV. This keeps the mapping config tiny and the failure surface small.
composer require it. This is the single most common misunderstanding. There is no meaningful library API surface — its output is HTTP calls to Firefly III, not PHP methods you call.latest on both sides is a recipe for a silent schema mismatch after an update.file -i your.csv and head -c 3 your.csv | xxd before debugging the importer itself.03/04/2024 will import — as the wrong date — without erroring. Always set the explicit format in the mapping and verify with a small sample file whose true dates you know.storage/ or you'll lose your carefully-built mappings on the next docker rm.How can I help you explore Laravel packages today?