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

Filament Currency Laravel Package

ariaieboy/filament-currency

A Filament PHP package for Laravel that provides currency form fields and formatting helpers. Easily handle locale-aware monetary input and display in Filament resources, forms, and tables.

View on GitHub
Deep Wiki
Context7

Getting Started

This package gives Filament forms a currency-aware input that formats values for humans while keeping the stored value machine-friendly (minor units). The current line is 3.x; 3.0.2 is a patch release on that line — it fixes the currency mask echoing programmatic updates back to Livewire (PR #58). There are no API changes, no new config keys, and no deprecations. If you're on 3.0.0/3.0.1, upgrading is a composer update.

Minimal setup:

  1. composer require ariaieboy/filament-currency
  2. Confirm the package major matches your Filament major (3.x tracks the current Filament release; the 2.x line is legacy and stays behind on Laravel 13 / older Filament). Check the constraint matrix on Packagist — the release tag alone doesn't tell you about Filament compatibility.
  3. Publish the config if you need to change the default currency or formatting: php artisan vendor:publish --tag="filament-currency-config"
  4. Swap one existing price field:
use Ariaieboy\FilamentCurrency\Forms\Components\CurrencyInput; // verify namespace against the README

CurrencyInput::make('price')
    ->currency('USD')
    ->required();

First use case worth doing: take a single price field on your most-edited resource, back it with an integer price_cents column, and verify the round-trip (edit → save → reload) before rolling it out anywhere else. The whole value of the package is in how it converts between displayed decimals and stored minor units, so prove that path first.

Where to look first: the published config (default currency, locale, allowed currencies), then the field class itself to see its formatStateUsing/dehydrateStateUsing behaviour. That pair is where every surprise lives.

Implementation Patterns

Store minor units, always. Treat the field as a presentation-layer widget over an integer column. In the model, use a money cast (the package builds on the Akaunting/MoneyPHP family) rather than hand-rolling $value * 100 accessors:

protected function casts(): array
{
    return ['price' => \Akaunting\Money\Money::class . ':USD'];
}

Per-record currency. For multi-currency resources, make the currency dynamic rather than hardcoded:

CurrencyInput::make('price')
    ->currency(fn (?Model $record) => $record?->currency ?? 'USD');

Pair it with a Select for currency and ->live() so the symbol updates when the user switches. Avoid letting currency change after the amount is entered without an explicit confirmation — silently reinterpreting 100 JPY as 100 USD is a data-integrity bug, not a UX quirk. On 3.0.2+ this reactive pairing is reliable; on older releases the mask could echo state back and fight your select (see Gotchas).

Programmatic state is now safe to set. ->state(), fillForm(), afterStateUpdated, and dependent-select updates no longer bounce back through the mask into Livewire. That means you can freely drive the field from bulk actions, "duplicate record", or a calculateTotal() reactive callback without guarding it.

Table and infolist display. Don't reuse the form field for output. Format in the column:

TextColumn::make('price')
    ->money('USD', divideBy: 100)   // if stored in minor units
    ->sortable();

divideBy is the single most common source of "why is everything 100x" bugs. Decide once per column whether the value is minor or major units and be consistent.

Repeaters and relation managers. The field works inside repeaters, but each row needs its own currency context. Pass ->currency(fn (array $state) => $state['currency'] ?? 'USD') from the sibling select, or you'll format every row with the panel default.

Validation. ->rules() and ->minValue() see the stored value. If you store minor units and want "at least $1.00", the rule is min:100, not min:1. Do the multiplication in code or assert on the dehydrated state, not on what the user typed.

Testing. Assert against the dehydrated/form state, not the displayed string — assertFormSet compares raw state. For Livewire form tests, fill with the human format and assert the saved integer:

Livewire::test(ProductForm::class)
    ->fillForm(['price' => '19.99'])
    ->call('save')
    ->assertHasNoFormErrors();

$this->assertSame(1999, Product::first()->price_cents);

Gotchas and Tips

  • The 100x factor is the whole ballgame. A decimal(10,2) column plus a minor-unit field means you're dividing by 100 twice. Pick integer _cents columns or decimal columns, never both.
  • Client-side masking is cosmetic. The field's JS formatting can be bypassed by paste, autofill, or a disabled JS bundle. Always validate numerically server-side; never trust the formatted string.
  • Mask echo loops (fixed in 3.0.2). Before 3.0.2, the mask could push its formatted value back to Livewire after a programmatic update, causing extra round-trips, overwriting freshly-set state, or a reactive select apparently "reverting" the amount. If you worked around it with ->live(false), ->debounce(), a wire:ignore wrapper, or a manual afterStateHydrated, remove those hacks when you upgrade — they'll now mask the real behaviour.
  • Locale drift across panels. Currency symbol and separators come from config/Intl. A panel with a different locale can render $1,234.56 as 1.234,56 $. Set it per panel deliberately rather than inheriting whatever the server's intl default is.
  • Config cache in deploys. Unpublished config uses package defaults; after config:cache changes to the published file need a re-cache. If defaults seem ignored in production, this is why.
  • Don't install two money libraries. The package pulls in one; if your app already uses brick/money or raw MoneyPHP, you'll get duplicate helper functions and confusing casts. Standardise on the one the package ships with.
  • Upgrading Filament means upgrading this package's major. 3.x is the line that tracks current Filament; 2.x is maintenance-only for legacy apps staying on the older Filament line (that's what the 2.1.0 Laravel 13 constraint bump was for). Plan the Filament + package major bump together, not separately.
  • Extension points: subclass the field to change defaults globally, override the Blade view with ->view() for custom markup, and use ->formatStateUsing() for one-off display tweaks. Prefer a small custom field class over repeating the same three chained calls across a dozen resources.
  • Sorting and filtering on formatted columns. Table sorting uses the raw column value, not the formatted one — that's what you want with minor units, but confirm your column's sortable() behaves as expected on a 100x-scaled integer rather than assuming.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource