Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Omer Mahdi's avatar

how to validate two column unique in filament-php?

I have a table like this:-

$table->foreignId('student_id');
$table->foreignId('year_id');
$table->unique(['year_id', 'student_id']);

and this is the resource:-

Forms\Components\Select::make('student_id')
                ->searchable()
                ->preload()
                ->relationship('student','name')
                ->label(trans_choice('student',1))
                ->required(),
Forms\Components\Select::make('year_id')
                ->searchable()
                ->preload()
                ->relationship('year','date')
                ->label(trans_choice('year',1))
                ->required(),

and I want to validate if the year and the student are unique together

0 likes
9 replies
LaryAI's avatar
Level 58

To validate the uniqueness of two columns in Filament PHP, you can create a custom validation rule. Here's an example:

  1. Create a new validation rule class:
<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\DB;

class UniqueTogether implements Rule
{
    protected $table;
    protected $columns;

    public function __construct($table, $columns)
    {
        $this->table = $table;
        $this->columns = $columns;
    }

    public function passes($attribute, $value)
    {
        $count = DB::table($this->table)
            ->where($this->columns[0], $value[0])
            ->where($this->columns[1], $value[1])
            ->count();

        return $count === 0;
    }

    public function message()
    {
        return 'The :attribute has already been taken.';
    }
}
  1. In your resource class, add the validation rule to the fields:
use App\Rules\UniqueTogether;

...

public function fields(Request $request)
{
    return [
        Forms\Components\Select::make('student_id')
            ->searchable()
            ->preload()
            ->relationship('student','name')
            ->label(trans_choice('student',1))
            ->required()
            ->rules([
                new UniqueTogether('your_table_name', ['year_id', 'student_id']),
            ]),
        Forms\Components\Select::make('year_id')
            ->searchable()
            ->preload()
            ->relationship('year','date')
            ->label(trans_choice('year',1))
            ->required()
            ->rules([
                new UniqueTogether('your_table_name', ['year_id', 'student_id']),
            ]),
    ];
}

Replace your_table_name with the name of your table.

This will validate that the combination of year_id and student_id is unique in the table.

Omer Mahdi's avatar

Lary reply is using decrypted code and I can't get the values of the fields inside Filament Resource to pass them to a custom validator so I didn't find an optimal solution but this is what I came up with in the create class I used mutateFormDataBeforeCreate() function

protected function mutateFormDataBeforeCreate(array $data): array
    {
        $not_unique = Registration::where([
					['year_id','=', $data['year_id']], 
					['student_id', '=', $data['student_id']]
		])->exists();

        if ($not_unique) {
            Notification::make()
                ->title('combination is not unique')
                ->danger()
                ->send();
                
                throw ValidationException::withMessages(['student and year combination is not unique']);
        }
        
        return $data;

    }

this sends back an error notification and the data won't be saved to the database

1 like
Danakin's avatar

As per the Filament Documentation, you can pass it a callback.

https://filamentphp.com/docs/2.x/forms/validation#unique

I ran into the same problem today. SItuation: A school can create fiscal years that have a unique name per year. So, in the FiscalYear model, I have a 3 column unique: ['school_id', 'year', 'name']. For example, school 1 has year 2023, name first term, second term, year 2024, name first term, second term.

Note: I use the $get callable, to access fields on my form. https://filamentphp.com/docs/2.x/forms/advanced#using-closure-customization

This is how I solved it:

Forms\Components\TextInput::make('name')
    ->maxLength(255)
    ->unique(callback: function (Unique $rule, callable $get) { // $get callable is used 
        return $rule
            ->where('school_id', $get('school_id')) // get the current value in the 'school_id' field
            ->where('year', $get('year'))
            ->where('name', $get('name'));
        }, ignoreRecord: true) // ignore current record when editing
        ->required(),

I think this won't be a problem with only 2 fields, but for future reference when somebody needs to do this check with more fields, with Select::make()->relationship I had a problem that it complained about the name being not unique, so I filled the options with ->options(fn () => School::pluck('name', 'id')->toArray()) instead. Basically, it first calls ->saveRelationship() and tries to update only the school_id, and tries to update the name in a separate step afterwards, but at that point MySQL already threw an error that the new index was already used. Took me a while to figure this one out...

4 likes
oussamanh's avatar

@Danakin Hi sir ,your code didn't work for me i got : App\Filament\Resources\ProfessionalNetworkResource::App\Filament\Resources{closure}(): Argument #1 ($rule) must be of type App\Filament\Resources\Unique, Illuminate\Validation\Rules\Unique given, called in /home/vagrant/projects/myportfolio/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php on line 36 and this is my code :

			 Toggle::make('isprincipal')
            ->onColor('success')
            ->offColor('danger')
            ->unique(callback: function (Unique $rule, callable $get) { 
            return $rule
                    ->where('isprincipal', true)
                    ->where('user_id', $get('user_id'));
            }, ignoreRecord: true)
            ->required()

can you help me!

Danakin's avatar

@oussamanh

Sure! The error says you (or rather your editor) imported the wrong Unique class. It must be of type Illuminate\Validation\Rules\Unique;. On the top of your file, change your import:

use Illuminate\Validation\Rules\Unique; // <- correct import

not

use App\Filament\Resources\Unique; // <- wrong import
3 likes
bhattji's avatar

@Danakin This works in Filament Resource, but doesn't work in RelationManager Resource.

How to make it work in RelationManager?

Lij-EDI's avatar

For any future viewers of this thread, please note that some slight changes were made to the method signature in v3.

The $callback parameter's name has been changed to $modifyRuleUsing.

So instead of writing

->unique(callback: function (Unique $rule, callable $get) {
    // manipulate rule
}

your code must be

->unique(modifyRuleUsing: function (Unique $rule, callable $get) {
    // manipulate rule
}
3 likes

Please or to participate in this conversation.