kantorge/yaffa
YAFFA is a self-hosted personal finance manager built with Laravel. Track accounts, transactions, budgets, and investments; import bank statements; categorize spending; and generate reports through a clean, responsive web interface with multi-currency support.
Yaffa is a self-hosted Laravel personal-finance application (accounts, transactions, schedules, budgets, reports, AI document import) that you deploy and extend rather than composer require. v4.1.0 keeps the Laravel 13 + PHP ext-bcmath floor from 4.0, so confirm both before anything else — a missing bcmath extension fails late and confusingly, usually the first time a money value is cast.
Two viable entry paths:
docker compose up -d. The container generates and persists its own APP_KEY on first boot, so no .env editing is required. Add --profile https if you want the bundled Caddy reverse proxy.composer install, copy .env.example, php artisan key:generate, set the DB connection, then php artisan migrate --seed.Where to look first, in order:
UPGRADE.md (section 3.x to 4.x) — the 4.0 data migrations are one-way; read this before you touch a production database. If you are coming from 4.0, also review the 4.1 migration that moves the preferred import profile from account_entities to accounts.routes/api.php and the app/Http/Controllers/Api/V1 namespace — this is the surface most integrations use.app/Models/{Transaction,Budget,Schedule,Account} — the 4.0 domain model changed materially, and 4.1 moves import-profile state onto Account.config/ plus .env.example — several values must be pinned explicitly under Laravel 13; if you use AI document import, check the configured OpenAI model is still supported.resources/js and the new payee/category show views if you extend the UI — Select2 has been replaced by Tom Select.A good first use case is a read-only API client: create a token in User settings → API tokens with the read scope, then pull /api/v1/transactions and confirm your JSON parser handles money as decimal strings. That single step exercises the auth model, the API contract and the precision change at once.
If you are coming from 3.x: upgrade to the newest 3.x first, take a database backup, then run php artisan app:check:budget-migration and act on its output before installing 4.x. If you are already on 4.0, back up before 4.1 as well if you have custom import code touching account_entities.
Treat recurrence as an RFC 5545 string, not columns. Schedules and budgets now store recurrence in a single rrule column; frequency, interval, count and end_date are gone. The UI and API fields are unchanged, so write through the model's accessors/casts rather than reassembling the old columns:
$schedule->rrule = 'FREQ=MONTHLY;BYDAY=1WE'; // first Wednesday of every month
$schedule->rrule = 'FREQ=MONTHLY;BYDAY=-1FR'; // last Friday of the month
New 4.0 patterns — "N days before month end", "last business day of month", "skip to nearest future occurrence" — are all expressible this way, so a custom importer or seeder can generate them without touching the UI.
Query Budget directly instead of the old flag. transactions.budget no longer exists. Budget-only transactions were migrated into Budget rows (one per category) and deleted. Any custom report, dashboard widget or SQL view you own must join budgets rather than filter where('budget', true). Budget management now lives under Reports → Schedules and Budgets.
Money is exact — keep it exact in your own code. The core uses decimal arithmetic throughout. On the API boundary, money and quantity fields in /api/v1/* responses are decimal strings. Parse with bcmath or a money value object, never (float):
$total = '0.00';
foreach ($payload['transactions'] as $tx) {
$total = bcadd($total, $tx['amount'], 2); // amount is "12.34", not 12.34
}
Report endpoints are exempt from the string change — they were already strings — so a single normalization helper at your client boundary keeps both shapes honest.
Update import automation for the new profile location. The preferred import profile now lives on accounts, not account_entities. Custom importers, seeders or direct DB queries should read/write through the Account model or relation; do not keep querying the old location.
Handle AI import outcomes explicitly. 4.1 adds AI document duplicate detection and a warning when an import returns no drafts. Background jobs and importers should treat an empty draft set as a valid outcome and inspect duplicate status rather than assuming every upload produces a new transaction.
Scope API tokens by intent. Tokens carry read, write and settings scopes. Issue one token per integration: a dashboard/BI job needs only read; an importer needs write; only an admin sync tool should hold settings. Pair with per-user TOTP and recovery codes for interactive logins — both are managed from user settings and are the replacement story for the removed reCAPTCHA on the human-facing flows.
Wire into the reporting layer rather than duplicating it. Find Transactions gained transaction-type and investment filters, a category waterfall, and item-level breakdown filtering; the dashboard waterfall widget deep-links into the report. 4.1 adds payee and category show pages, so check those before building custom drill-down views. If you need a bespoke view, compose existing report endpoints/controllers and filter server-side — reimplementing aggregation client-side will drift from the inflation-adjusted forecasting and split-total logic.
Use Tom Select for UI extensions. Select2 is gone in 4.1. If you maintained custom JS, styling, or browser tests around Select2 events/DOM, migrate them to Tom Select rather than shimming the old API.
Sentry is opt-in again. 4.1 removes the sentry/sentry-laravel dependency. If you need error reporting, require and configure it explicitly; otherwise remove stale Sentry references from custom code and config.
Deployment hygiene. Pin CACHE_PREFIX, REDIS_PREFIX and SESSION_COOKIE in .env before moving to Laravel 13; the framework changed the defaults, and un-pinned values silently orphan your cache namespace and log every user out. For HTTPS, start Caddy with docker compose --profile https up -d and set APP_PORT if 443 is taken. Re-apply any hand edits you previously made to a commented-out Caddy block — that block is now a live profile service.
Testing and style. PHPUnit 13 for unit/integration coverage, Pest 5 browser tests for UI flows, ESLint-clean, 2-space JS/Vue formatting. If you maintain a fork or a private module, match the formatting and update Select2-based browser tests to Tom Select to keep diffs reviewable.
migrate:rollback will not restore transactions.budget or the dropped schedule columns. Back up, run app:check:budget-migration on the latest 3.x, and only then upgrade. 4.1's move of the preferred import profile from account_entities to accounts is another schema change — review it before upgrading from 4.0.Number(tx.amount) or json_decode(...)->amount + 1 in PHP will now produce string concatenation or NaN. Audit every consumer, including Blade/Vue formatters, exports and webhook payloads.12.345 to a 2-decimal field you get a validation error where 3.x silently truncated. Validate and quantize on your side; the API will not do it for you.GET /api/v1/transactions/scheduled-items dropped its type parameter. Remove type=budget|budget_only|both|any from clients — it is no longer accepted, and budget-vs-actual responses now carry budgetBreakdown and scheduleBreakdown arrays.RECAPTCHA_* variables are read nowhere; delete them from .env to avoid false confidence. Confirm login/registration throttling is configured, since that is now your only automated abuse control.docker-php-ext-install bcmath or the app fails on the first monetary operation.sentry/sentry-laravel, 4.1 will not install it. Either re-add the package and wire it deliberately or remove the references.gpt-4o or gpt-4o-mini, switch to a currently supported model; otherwise document import may fail at the provider boundary.account_entities must point at accounts after 4.1.EXPLAIN on your heaviest custom reports.rrule, policies for the new Budget entity, report controllers for custom widgets, the new payee/category show controllers/views, Tom Select configuration for UI extensions, and the per-user AI document retention policy for housekeeping jobs. Prefer these over patching migrations — with irreversible data migrations in play, a clean upgrade path is worth more than a convenient hack.How can I help you explore Laravel packages today?