defstudio/filament-money
defstudio/filament-money adds money-aware form fields and table columns to Filament, with formatting, casting, and storage helpers for consistent currency handling in Laravel admin panels.
defstudio/filament-money is a thin, focused Filament bridge for brick/money. It gives you a MoneyInput form component and a MoneyColumn table component so you stop hand-rolling number formatting and currency parsing in every resource.
composer require defstudio/filament-money
There is no required publishing step — the package ships sensible defaults. Publish the config only if you want to change the package-wide fallback currency/locale:
php artisan vendor:publish --tag="filament-money-config"
Where to look first
README.md — the usage surface is small enough that the README is genuinely the reference.config/filament-money.php — global default currency and locale. Set these once to your app's base currency and you can omit the arguments in 90% of your fields.src/MoneyInput.php and src/MoneyColumn.php — the two classes you will actually be using. Both are short; read them before assuming behaviour around parsing and null values.First use case — a product price on a resource form plus its table listing:
use DefStudio\FilamentMoney\Forms\Components\MoneyInput;
use DefStudio\FilamentMoney\Tables\Columns\MoneyColumn;
MoneyInput::make('price')
->currency('EUR')
->locale('it_IT')
->required(),
MoneyColumn::make('price')
->currency('EUR')
->locale('it_IT'),
Prior to v5.0.4 the table column did not accept currency/locale, so formatted output in tables had to fall back to the global config or a manual formatStateUsing(). With v5.0.4 you can format per-column, which is what makes the package usable in a multi-currency admin panel.
Let config carry the default, override at the edge. Put your primary currency/locale in the config file, then only pass ->currency() / ->locale() where a record genuinely differs. This keeps resources readable and makes a future currency switch a one-line change.
Multi-currency per record. The common real-world shape is a price + currency column pair. Read the currency off the record rather than hard-coding it:
MoneyInput::make('price')
->currency(fn ($record) => $record?->currency ?? 'EUR')
->locale(fn () => app()->getLocale()),
MoneyColumn::make('price')
->currency(fn ($record) => $record->currency)
->locale(fn () => app()->getLocale()),
Because v5.0.4 added currency/locale support to the table column, the form and the table can now be driven by the same closures — previously the table would silently format in the default currency and disagree with the form.
Keep brick/money as the domain layer. Treat the Filament components as pure UI. Cast the persisted value to a Brick\Money\Money on your Eloquent model (a custom cast or an accessor) and do arithmetic, allocation and conversions there. The field should never be the place where money math happens.
Locale is presentation, not data. Store one canonical representation and vary locale for display only. If you support it_IT (comma decimal) and en_US (dot decimal), the same underlying value should format both ways without any migration.
Testing. Filament form/table components are Livewire-backed; use livewire()->fillForm([...])->call('save') and assert against the persisted raw value rather than the formatted string. For the column, assertTableColumnFormattedStateSet() is the assertion that catches locale regressions.
Upgrade posture for v5.0.4. This is a patch release: one feature (currency and locale on the table component), a CHANGELOG update and a styling pass. No migrations, no signature changes, no deprecations. Update with confidence.
->currency('EUR')->locale('en_US') renders €1,234.56; the reverse ordering of assumptions produces 1.234,56 €. Decide the pair deliberately, and set the locale explicitly in the column now that v5.0.4 allows it — a mismatch between form and table is the number-one bug report for packages like this.MoneyInput parses a comma decimal against your locale before going live; if you accept free text, add a validation rule so a mis-typed value fails loudly instead of being silently reinterpreted as thousands.decimal(12,2)) and never widen it loosely. Getting this wrong is expensive to fix once rows exist.null and 0 are not the same in a table. An empty column cell should read as a dash, not €0.00. Use the column's placeholder/empty-state option rather than a formatStateUsing() closure that coerces nulls.currency on existing rows does not convert anything. It changes labels. If you need conversion, do it as an explicit migration with a recorded FX rate.MoneyFormatter can't express, subclass MoneyColumn and override the formatting method rather than reaching into the base component's internals.$record->getRawOriginal('price')) to confirm whether the problem is storage, casting, or formatting — the component sits at the end of that chain and is rarely the culprit.How can I help you explore Laravel packages today?