Skip to main content

Money Column: Modify Before/After Form

Premium
5:13

Text Version of the Lesson

In this lesson, I will show you how to customize the behavior with the field value, both in the form and table, with an example of the Money field.

We have a products.price DB field, which requires extra logic. According to all the DB theories, we shouldn't store monetary values as floats. Instead, we should use an integer field and store the value in cents.

It means that after someone enters 49.99 in the form, we need to automatically multiply it by 100 and save 4999 in the DB.

It also means that, from the other side, in the table, we need to take the 4999 value from the DB and automatically divide it by 100 to show 49.99 in the table column.

Let's see how to do both in Filament.


Mutate Value Before Creating

Filament has methods to modify the value between entering it in the form and saving it in the DB.

  • Method mutateFormDataBeforeCreate() for Create forms
  • Method mutateFormDataBeforeSave() for Edit forms

They both accept the array of field values, and we can modify that array however we want and return it from the function.

app/Filament/Resources/ProductResource/Pages/CreateProduct.php:

class CreateProduct extends CreateRecord
{
// ...
 
protected function mutateFormDataBeforeCreate(array $data): array
{
$data['price'] = $data['price'] * 100;
 
return $data;
}
}

Now, if we enter 123.45 in the create form field, it will show 12345 both in the database and in the table:

Let's fix that table column value.


Format Value in Table Column

We will add two modifier methods: dividing the value by 100 and formating the value with a currency.

For re-calculating the value...

The Full Lesson is Only for Premium Members

Want to access all of our courses? (30 h 09 min)

You also get:

55 courses
Premium tutorials
Access to repositories
Private Discord
Get Premium for $129/year or $29/month

Already a member? Login here

Comments & Discussion

YD
Yimy David ✓ Link copied!

On edit, i have this error The name has already been taken. which would have to be changed in this line of code: TextInput::make('name')->required()->unique(),

PK
Povilas Korop ✓ Link copied!

Yeah, I noticed that myself, will fix later in the tutorial, meanwhile try this trick

M
mathCube ✓ Link copied!

->unique(ignorable: fn ($record) => $record)

M
muuucho ✓ Link copied!

If I create a new product, I like to accept a comma separated price as well as a dot separated one. This means I have to str_replace the price in order to replace any comma with a dot before inserting the price to the db. But since my validation contains a numeric check, the str_replace method must kick in before validation, otherwise it will fail if the user uses comma as a float separator. Any ideas how I can accomplsh that?

PK
Povilas Korop ✓ Link copied!

I have seen people are doing that comma/dot automatic replacement in JavaScript on the front-end, before any back-end validation happening.

M
muuucho ✓ Link copied!

This worked for me:

TextInput::make('price')
->mask(RawJs::make(<<<'JS'
$input = $input.replace(/,/g, '.')
JS)),
CP
Claudio Pereira ✓ Link copied!

I'm Brazilian and we use a comma as a thousands separator. I followed the documentation suggestion at the link below and created a Cast class, which did all the work for me: https://filamentphp.com/docs/3.x/panels/getting-started#casting-the-price-to-an-integer

PK
Povilas Korop ✓ Link copied!

Thanks Claudio, I'm updating this course and now I've modified the lesson to add this option with Eloquent Casts.

D
dr_capins ✓ Link copied!

Hello, I have a question: in a Filament Form I have a manyToMany relationship rendered with a Checkboxlist. Before the db saving, I have to perform some "actions" with the values inside the list, but If i go in the methods mutateFormDataBeforeSave or handleRecordCreation the $data variable contains all the form data except the relationship one.

Possible that there is no way to handle that values?

N
Nerijus ✓ Link copied!

How are you definin chechbox list? Forms doesn't work with dot notation. Wrapping in a group and defining relationship there maybe would work.

D
dr_capins ✓ Link copied!

like this: Forms\Components\CheckboxList::make('vagoni') ->label('Vagoni') ->relationship( 'wagons', 'nome', fn (Builder $query) => $query->where('motrice', '=', false), )

D
dr_capins ✓ Link copied!

What do you mean with group?

DF
DJ Far ✓ Link copied!

I always use DECIMAL(8,2) for money fields in MySQL.

WV
Wilko van der Ploeg ✓ Link copied!

Pretty much what I do too, I only use DECIMAL(8,5) to prevent rounding differences with VAT.

J
Joe ✓ Link copied!

It's nice that Filament includes the mutate methods, but they look a bit clumsy to me when you consider that the same thing can be done in native Laravel on the model with just this:

public function price(): Attribute
{
return Attribute::make(
get: fn ($value) => $value / 100,
set: fn ($value) => $value * 100
);
}

..and the added advantage of doing this would seem to be that it works on the fontend too, not just in Filament forms/tables.

Is there any benefit you can think of to using the two Filament murate methods (mutateFormDataBeforeFill and mutateFormDataBeforeSave) as opposed to doing it on the Laravel model?

DF
DJ Far ✓ Link copied!

The attribute getter and setter is fine if that's what you need to do - you don't need the mutateFormDataBeforeSave method for that. But for me, I needed to add a couple of field values that are based on the value of other fields in the form. in my case:

protected function mutateFormDataBeforeCreate(array $data): array
{
$data['slug'] = Str::slug($data['name']);
$data['created_by'] = auth()->id;
return $data;
}
J
Joe ✓ Link copied!

Ok, yes, I see what you mean, thank you. It looks like it could be pretty useful for doing a load of form-specific processing that you might not necessarily want to do every time you save the model. So it's good to know the option is there.

PK
Povilas Korop ✓ Link copied!

Thanks Joe, I'm updating this course and now I've modified the lesson to add this option with Eloquent attributes.

PC
Paulo Cavalcanti ✓ Link copied!

I'm trying to use money format (MoneyCast like the documentation) and it works, but in the form TextInput, i'm able to put N decimal numbers, but I want to fix it to 2, using comma as thousands separator and dot as decimal separator. There is a native way to do in Filament 3?

This is my code:

Forms\Components\TextInput::make('tuition_average_fund')
->label(SegmentEnum::FUNDAMENTAL_I->getLabel())
->numeric()
->inputMode('decimal')
->prefix('R$')
M
Modestas ✓ Link copied!

This could help you:

TextInput::make('price')
->required()
->mask(RawJs::make(<<<'JS'
$money($input, '.', '', 2)
JS))
->placeholder('19.99')
->formatStateUsing(fn ($state) => ! $state ? null : number_format($state / 100, 2))
->dehydrateStateUsing(fn ($state) => (int) ($state * 100)),

This will create a mask input for your field

D
Daniel ✓ Link copied!
PK
Povilas Korop ✓ Link copied!

Thanks Daniel, I'm updating this course and now I've modified the lesson to add this option with Eloquent Casts.

We'd Love Your Feedback

Tell us what you like or what we can improve

Feel free to share anything you like or dislike about this page or the platform in general.