Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Firefly Iii Laravel Package

grumpydictator/firefly-iii

Firefly III is a free, self-hosted personal finance manager. Track income and expenses with double-entry bookkeeping, budgets, categories and tags, automate bank imports with rules, and explore clear reports, all in a privacy-friendly PHP/Laravel app with a JSON REST API.

View on GitHub
Deep Wiki
Context7

Getting Started

Firefly III is not a library you composer require into an existing app — it's a full Laravel application you run and talk to. Day-to-day leverage means deploying it as a service and consuming its REST API, webhooks, and data formats from your own code.

Two paths, pick one first:

  1. Docker (fastest): grab the firefly-iii/firefly-iii compose example, bring up the app + a MySQL/PostgreSQL container, set APP_KEY, DB_*, SITE_OWNER. Open the UI, register the first user.
  2. Composer (for hacking the source): composer create-project grumpydictator/firefly-iii, set .env, php artisan key:generate, php artisan migrate --seed, php artisan firefly-iii:upgrade-database on upgrades.

First place to look in the repo:

  • routes/api.php — the real API surface; everything the UI does is a resource call.
  • app/Api/V1/Controllers/ — request/response shapes.
  • config/firefly-iii.php and config/api.php — the knobs people actually change (throttling, allowed hosts, OAuth paths).
  • resources/lang/ + app/Handlers/ — useful for understanding rules/webhooks.

First use case — script a transaction in:

  1. Log in → Profile → OAuth → Personal Access Tokens → Create.
  2. Hit the API with a bearer token:
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     https://firefly.example.com/api/v1/about
  1. Then create a withdrawal:
curl -X POST https://firefly.example.com/api/v1/transactions \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{
    "error_if_duplicate_hash": true,
    "transactions": [{
      "type": "withdrawal",
      "date": "2026-10-20T09:00:00+00:00",
      "amount": "12.34",
      "description": "Coffee",
      "source_id": "1",
      "destination_name": "Blue Bottle",
      "category_name": "Food",
      "tags": ["imported"]
    }]
  }'

If that round-trips, you understand 80% of the package. Everything else is more endpoints with the same JSON:API envelope.

Implementation Patterns

Treat it as a JSON:API backend. Responses are {"data": {"type": "...", "id": "...", "attributes": {...}, "links": {...}}}. Pagination lives in data.links and data.meta.pagination. Write one thin client, not fifty endpoints.

A reusable Laravel HTTP client is the single highest-value thing you'll build:

// config/services.php
'firefly' => [
    'base_url' => env('FIREFLY_URL'),
    'token'    => env('FIREFLY_TOKEN'),
],
Http::baseUrl(config('services.firefly.base_url').'/api/v1')
    ->withToken(config('services.firefly.token'))
    ->acceptJson()
    ->withHeaders(['X-Trace-Id' => (string) Str::uuid()]) // shows up in Firefly logs
    ->retry(3, 200, throw: false)
    ->timeout(30);

OAuth2 for multi-user integrations. Firefly ships Laravel Passport. Register a client under Profile → OAuth → Clients, then run the standard authorization-code flow (/oauth/authorize, /oauth/token). Use client-credentials only if you're a single shared integration, and remember every token is scoped to one user's dataset — there is no admin-wide token.

Bulk over single. The /api/v1/transactions store endpoint accepts an array of transaction splits in one journal, and there are dedicated bulk endpoints for updates/deletes. Importers should batch by journal, not fire one request per row. Related: /api/v1/transactions search with Firefly's own query syntax (amount_more:10 date_after:2026-01-01) is often faster than pulling and filtering locally.

Idempotency + duplicates. Set error_if_duplicate_hash: true on imported transactions and catch the 422 — that's Firefly's built-in duplicate guard (hash of type/date/amount/description/accounts). Keep your own external-ID mapping table in your database too; Firefly has no natural key for "this came from Stripe charge X" other than tags/notes.

Rules vs. your code. Firefly has a rules engine that runs on transaction creation (apply_rules: true). Resist reimplementing categorization in your importer — push raw transactions with apply_rules: true and let users manage rules in the UI. Use tags/external_url for the metadata the rules engine can't express.

Webhooks instead of polling. Register at Profile → Webhooks for events like STORE_TRANSACTION, UPDATE_TRANSACTION, DESTROY_TRANSACTION. Deliveries are POSTs with a JSON body and a signature header derived from your webhook secret — verify it, respond 200 fast, and queue the actual work. Webhooks fire on UI-entered transactions too, which makes them the cleanest way to mirror data into a warehouse.

Companion tools as patterns, not deps. The separate firefly-iii/data-importer project is the canonical example of a real integration: map CSV columns → Firefly fields, dry-run, then post. Its mapping approach is worth copying for any source. Likewise the export/import commands (php artisan firefly-iii:export-data) show the intended backup shape.

Extending the app itself. If you're self-hosting and forking, extension points are: custom rules/handlers under app/Handlers/, event listeners on Firefly's domain events, custom commands in app/Console/Commands/, and adding columns to transactions_journal/transactions via migration. Keep customizations in a small set of touched files — you'll be rebasing on every upgrade.

Deployment pattern. Behind a reverse proxy with TRUSTED_PROXIES set, APP_URL correct, and a cron entry for php artisan schedule:run. Back up the DB plus the storage/ directory (attachments and the encryption key matters — see gotchas).

Gotchas and Tips

Money is a string, and signs are semantic. amount comes back as "12.34" and the sign is derived from type (withdrawal is negative in reporting, but you always send positive amounts). Don't float it; use brick/math or integer cents. Never assume two decimals — Firefly supports currencies with 0 and 3 decimals.

Account and currency IDs must exist first. source_id/destination_id are strings of existing accounts. Posting a currency that isn't enabled for the user fails. Resolve accounts by GET /api/v1/accounts?type=asset once and cache; don't create them on the fly in an import loop.

IDs are strings. JSON:API spec — "id": "42", not 42. Loose comparisons bite you in strict PHP typing; cast deliberately.

Dates need offsets. Use ISO 8601 with timezone (2026-10-20T09:00:00+02:00). Bare Y-m-d works but silently lands at midnight in the user's timezone, which shifts monthly reports.

APP_KEY is forever. It encrypts stored OAuth secrets and certain config. Changing it after install breaks tokens and encrypted fields. Back it up with the DB. Related: firefly-iii:decrypt-all exists for migration scenarios — read the docs before touching it.

Throttling is real and default. The API applies Laravel throttling; batch importers hit 429 quickly. Handle 429 with backoff, keep concurrency low, and raise limits in config only if it's your own instance.

Search syntax is its own language. The query parameter isn't free text or SQL — it's Firefly's rule-like grammar with field:value tokens and modifiers. Bad queries return 422 with a message; log the offending query, because the error text doesn't echo it.

Webhook retries are not infinite. Firefly retries failed deliveries a bounded number of times, then gives up silently as far as your app is concerned. Always have a reconciliation job that re-fetches UPDATE_TRANSACTION-era records by date range.

Rules run in UI order and can surprise you. A rule that rewrites destination on import will make your error_if_duplicate_hash behave differently than in dry-run. Test imports with apply_rules: false first, then enable.

AGPL-3.0 is a real constraint. If you modify Firefly and expose it over a network to users, you must offer them the modified source. Fine for internal use; a legal review item if you're shipping a hosted product on top of it. The API boundary is the safe seam.

Upgrades are the sharpest edge. Always: stop the queue workers, back up the DB, php artisan down, pull, run php artisan firefly-iii:upgrade-database, clear caches, php artisan up. Running migrations without the firefly-iii: upgrade command on major versions leaves data in an inconsistent state.

Debugging tips. Send X-Trace-Id on every request and grep storage/logs/laravel.log — Firefly logs the trace ID against the request, which turns "which of my 400 imports failed?" into a one-liner. APP_DEBUG=true on a staging instance is your friend; on production it leaks credentials in stack traces, so never.

Look at /api/v1/about and /api/v1/configuration first in any new environment — they tell you the version, enabled features, and available currencies, which explains most 422s before you read a single log line.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections