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

SarahS's avatar
Level 12

Relationships and sub forms

I'm using Filament and I'm trying to build a simple invoice system. I have tables for clients, invoices and time logs. When I create an invoice for a client I want to choose and attach relevant time logs to it using a sub form. I thought that the relation manager might be the way to go but I'm not sure how to show a list of filtered time logs with the option to tick the ones I want to add. Is this possible or should I be using a different method?

0 likes
4 replies
LaryAI's avatar
Level 58

To achieve the functionality you're looking for in Filament, you can indeed use the Relation Manager to attach time logs to an invoice. However, to filter and select multiple time logs, you might need to customize the Relation Manager or use a different approach.

Here's a step-by-step solution to create a subform for attaching time logs to an invoice:

  1. Define the relationships in your Eloquent models if you haven't already done so. For example:
// Invoice model
public function timeLogs()
{
    return $this->belongsToMany(TimeLog::class);
}

// TimeLog model
public function invoice()
{
    return $this->belongsTo(Invoice::class);
}
  1. Create a custom Relation Manager for the timeLogs relationship in your Invoice resource. You can do this by running the php artisan make:filament-resource command and selecting the Invoice model.

  2. In the generated Relation Manager, override the getTableQuery() method to filter the time logs based on your criteria (e.g., only show time logs that are not already attached to an invoice).

protected function getTableQuery(): Builder
{
    return parent::getTableQuery()
        ->whereNull('invoice_id'); // Adjust this condition based on your database structure and needs
}
  1. Customize the table to include a checkbox column for selecting multiple time logs. You can use the CheckboxColumn provided by Filament.
protected function getTableColumns(): array
{
    return [
        // ... other columns
        CheckboxColumn::make('selected')
            ->label('Select')
            ->sortable(false),
    ];
}
  1. Override the actions() method in your Relation Manager to add a custom action that attaches the selected time logs to the invoice.
protected function getActions(): array
{
    return [
        // ... other actions
        Tables\Actions\Action::make('attachSelected')
            ->action(function (Invoice $record, array $data): void {
                $selectedTimeLogs = collect($data['selected'] ?? [])
                    ->filter(fn ($isSelected) => $isSelected)
                    ->keys();

                $record->timeLogs()->attach($selectedTimeLogs);
            })
            ->requiresConfirmation()
            ->color('success')
            ->icon('heroicon-s-check'),
    ];
}
  1. In your Invoice resource form, add the Relation Manager as a subform.
public function form(Form $form): Form
{
    return $form
        ->schema([
            // ... other fields
            Forms\Components\BelongsToManyMultiSelect::make('timeLogs')
                ->relationship('timeLogs', 'name'), // Replace 'name' with the attribute you want to display
        ]);
}

This is a high-level solution and may require further customization based on your specific needs and the structure of your application. Make sure to adjust the code snippets to fit your actual model attributes and logic.

jaseofspades88's avatar

This is quite achievable in filament, yes. Within your InvoiceResource you could add a Select field to your form, which contains all of the relevant time logs, then when creating you can choose multiple. On filament you can override the create process so that once you have created the new invoice, you can attach those time logs easily.

A TimelogRelationManager could be used to manage these time logs post creation.

I assume you have a CreateInvoice class referenced from your resource. Add something like this to override the process of creating the resource from the 'out of the box' functionality of filament.

    protected function handleRecordCreation(array $data): Model
    {
        $invoice = parent::handleRecordCreation(Arr::except($data, ['time_logs']));

        $invoice->timeLongs()->createMany(Arr::get($data, 'time_logs', []));

        return $invoice;
    }

In this example, time_logs refers to the field name you would add in your create invoice form. I hope this helps.

jaseofspades88's avatar

The whole process of overriding the creation process means you don't need to leverage the relationships if you wish to customise the attaching of time logs to invoice. That's what I was suggesting with the code snippet that you let filament handle the process of creating the invoice first, without the time logs from the form... then you attach them accordingly.

I apologise, the example I took above uses createMany but in this example the Select should return an array of ids from your time logs. So instead it should read...

$invoice->timeLogs()->attach(Arr::get($data, 'time_logs', []));

Please or to participate in this conversation.