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 Modal Repeater Laravel Package

yousefaman/filament-modal-repeater

Filament Modal Repeater displays repeater items in a compact table with modal-based editing, ideal for forms with many fields. Supports boolean, badge, money, and custom-formatted columns. Requires PHP 8.2+ and Filament v4 or v5.

View on GitHub
Deep Wiki
Context7

Getting Started

Version gate first, before anything else. This package requires PHP 8.2+ and Filament v4 or v5 — there is no v3 path and no fallback. If you are on Filament 4.14+ or 5.8+, pin >=1.1.3. On earlier releases, ModalRepeater cannot even load, because Filament widened Component::schema() to accept Filament\Schemas\Schema and the package's override did not match the signature — you get a fatal error and a blank page, not a helpful exception.

composer require yousefaman/filament-modal-repeater
php artisan modal-repeater:install

Then optionally register the plugin in your panel provider (the README recommends it; the component itself works without it):

use YousefAman\ModalRepeater\ModalRepeaterPlugin;

public function panel(Panel $panel): Panel
{
    return $panel->plugin(ModalRepeaterPlugin::make());
}

Where to look first. The README's usage block is effectively the entire public API: ModalRepeater plus Column. There is no docs site, no API reference, no examples repo — when the README runs out, read src/ directly. Read the changelog before upgrading anything: v1.1.1 (relationship binding, dot-notation columns, pivot LogicException) and v1.1.3 (the Filament signature break) tell you exactly where the sharp edges are. v1.1.2 is a no-op for behaviour — archive slimming only.

First use case. Do not adopt this for a two-field repeater; the modal is pure overhead there. Pick a form where each item carries 5+ editable fields and users mostly scan the list: order line items, price tiers, product variants, FAQ blocks, address books, competency matrices. Swap the native Repeater for ModalRepeater, leave your existing schema() array untouched, and add tableColumns() for the summary:

ModalRepeater::make('items')
    ->tableColumns([
        Column::make('name')->label('Name'),
        Column::make('price')->money('USD'),
        Column::make('active')->boolean(),
    ])
    ->schema([
        TextInput::make('name')->required(),
        TextInput::make('price')->numeric()->required(),
        Toggle::make('active'),
    ]);

That is the whole migration. The schema you already wrote is what renders inside the add/edit modal, so incremental cost per additional field is essentially zero — you add the field to schema() and, if it matters for scanning, one line to tableColumns().

Implementation Patterns

Treat tableColumns() and schema() as two different products with two different jobs. The schema answers "what do I change?"; the columns answer "do I need to open this at all?". Pick columns that let a user decide without opening the row — status, price, name, a date. If users routinely open a row just to read a field, that field belongs in the table. If the table starts needing more than ~5 columns, you have probably outgrown a summary table.

The built-in column types cover most summary needs without custom code:

Column::make('name')->label('Product Name'),
Column::make('active')->boolean(),                 // checkmark / cross icon
Column::make('status')->badge('success'),          // colored badge
Column::make('price')->money('USD'),
Column::make('created_at')->formatUsing(fn ($value) => $value->diffForHumans()),
Column::make('name')->width('200px'),

Relationship repeaters are a real option, not a workaround. For child records — addresses, line items, competencies — bind to Eloquent and let Filament handle load/save/delete:

ModalRepeater::make('addresses')
    ->relationship('addresses')
    ->tableColumns([
        Column::make('street')->label('Street'),
        Column::make('city')->label('City'),
        Column::make('competency.name')->label('Competency'), // dot-notation, needs >=1.1.1
    ])
    ->schema([
        TextInput::make('street')->required(),
        TextInput::make('city')->required(),
        Select::make('competency_id')->relationship('competency', 'name'),
    ]);

Since v1.1.1 the modal grid is bound to the related model, not the parent form model, so a nested Select using ->relationship() resolves correctly and pivot relationships no longer throw a LogicException. This is frequently the difference between "we need a custom child-resource CRUD screen" and "this is a modal repeater on the parent form" — a materially smaller ticket.

Ship interaction polish as later one-liners. Reorder, clone, delete, extra per-row actions, and empty-state copy all come straight from Filament's standard repeater action API. Don't spec them in v1; add them when someone asks:

ModalRepeater::make('items')
    ->modalColumns(3)            // modal form grid columns
    ->modalWidth('4xl')          // 'sm' | 'lg' | '4xl' | '7xl' ...
    ->addActionLabel('Add Item')
    ->editAction(fn (Action $action) => $action->label('Edit')->icon('heroicon-o-pencil'))
    ->extraItemActions([
        Action::make('duplicate')->action(fn ($arguments) => ...),
    ])
    ->emptyLabel('No items added yet.')
    ->reorderable()
    ->cloneable()
    ->deletable(false);

Keep modal layout decoupled from table layout. modalColumns() and modalWidth() are the two knobs most likely to be needed on real forms — a three-column modal over a two-column table is normal and fine.

Gotchas and Tips

  • Pin >=1.1.3 and say so in composer.json, not just in a PR description. The schema() signature break is a hard fatal, and it appears only after a Filament minor upgrade — it will not show up in your dev environment if you upgraded the package first and Filament second.
  • The table renders from repeater item state, not from the model. On a non-relationship repeater, a formatUsing() callback receives the raw array value — a datetime field arrives as a string, so fn ($value) => $value->diffForHumans() will blow up unless the value is a castable/related model. On relationship repeaters the column resolves against the Eloquent record (v1.1.1+), which is why the README's example works there. Verify the type before writing the closure.
  • Edits are modal-scoped. Nothing reaches the table row until the modal's save action fires; closing or dismissing the modal discards the change. Validation errors surface inside the modal, which is usually good UX but can be confusing when the surrounding form looks "saved".
  • tableColumns() keys must match schema() field names. A typo is silent — you get a blank cell, not an exception. If a column renders empty, check the key against the schema before suspecting the package.
  • badge() takes a static color, not a mapping. For status-dependent coloring you need formatUsing() or a custom column render; there is no documented ->badge(fn ($state) => ...).
  • reorderable() needs somewhere to store order. As with the native repeater, back it with an orderColumn()/sort field on the relationship — the modal doesn't create one for you.
  • The install command's payload is undocumented. Run php artisan modal-repeater:install --help and check git status afterwards so you know whether it published config, assets, or nothing at all before committing.
  • Debugging shortcut: blank page or "could not be loaded" class error → version mismatch (see first point). Blank cells → wrong column key. Wrong values after edit → reading pre-save state or a missing cast.
  • Risk assessment. 18 stars, zero dependents, one maintainer, a handful of releases. That is fine for a presentation-layer form component — worst case you fork src/ and vendor it — but do not build core domain behaviour on top of its internals. Stick to the documented surface (ModalRepeater, Column, and inherited Filament repeater/action methods); if you find yourself subclassing or reaching into protected properties, you are taking on maintenance the package cannot promise.
  • Extension points, realistically: extraItemActions() for per-row operations, editAction()/addActionLabel() for UI copy, formatUsing() for display logic, and subclassing ModalRepeater for anything systemic. Everything else means reading the source.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core