News

Deferred Schema Loading in Filament v5.8

Published
Deferred Schema Loading in Filament v5.8 image

Filament v5.8.0 adds deferred loading for child schemas, so a section, tab, wizard step, or repeater item can render its fields in a separate request when it scrolls into view. The release also includes table grouping changes, lifecycle hooks defined in traits, and more:

Deferred Schema Loading

This release adds a deferLoading() method to Schema, contributed by Kian Acquoij. By default, Filament renders every component in a schema on the first page load, including fields in collapsed sections and inactive tabs that the user may never open. With deferLoading(), the child schema renders a loading indicator first. When that indicator enters the viewport, Filament renders only that schema in a new request.

To defer a child schema, pass a Schema object to the component's schema() method instead of an array:

use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
 
Section::make('Customer details')
->key('customerDetails')
->schema(
Schema::make()
->components([
TextInput::make('name'),
TextInput::make('email')
->email(),
])
->deferLoading(),
)
->collapsed()

The section header renders right away. Its content does not load until the user expands the section and the content enters the viewport. Tabs follow the same rule: an inactive tab loads when it is selected, and a later wizard step loads when the user reaches it.

Repeater items and builder blocks can defer their schemas too, which is useful when items are collapsed by default:

use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Schema;
 
Repeater::make('members')
->schema(
Schema::make()
->components([
TextInput::make('name'),
TextInput::make('email')
->email(),
])
->deferLoading(),
)
->collapsed()

PR: #19867

Persist Table Grouping in the Session

In a contribution from @IGedeon, tables get a persistGroupInSession() method. Sort, search, filters, and columns could already be stored in the session, and the selected grouping is now restored on the user's next visit too:

use Filament\Tables\Table;
 
public function table(Table $table): Table
{
return $table
->groups([
'status',
'category',
])
->persistGroupInSession();
}

The PR also fixes a Livewire hook that never fired because its name did not match the $tableGrouping property. Changing the grouping now resets the table to the first page.

PR: #20165

Default Grouping Direction

The defaultGroup() method now accepts a direction argument, thanks to TinaH. Before this release, the default grouping was always sorted in ascending order. The new argument matches the signature of defaultSort():

use Filament\Tables\Table;
 
public function table(Table $table): Table
{
return $table
->defaultGroup('status', direction: 'desc');
}

PR: #20267

Lifecycle Hooks in Traits

Pages and importers now support lifecycle hooks named after a trait, from @oddvalue. Before this release, a trait that defined a hook such as afterSave() was overridden as soon as the page defined its own afterSave(). You can now suffix the hook name with the trait name, which follows Eloquent's boot{TraitName}() convention:

use Filament\Resources\Pages\EditRecord;
 
trait HandlesDrafts
{
protected function afterSaveHandlesDrafts(): void
{
// Runs in addition to the page's afterSave() hook.
}
}
 
class EditUser extends EditRecord
{
use HandlesDrafts;
 
protected function afterSave(): void
{
// Runs first, then afterSaveHandlesDrafts().
}
}

The page's own hook runs first, followed by each trait hook. The change applies to panel pages (including the create, edit, and view record pages) and to importers, and the same trait can be used on several pages without each page calling it from its own hook.

PR: #20189

Placeholders for Stats

A stat in the stats overview widget can now show a placeholder when its value is blank. @ahmed-rashad-alnaggar contributed the feature, which uses the placeholder() method:

use Filament\Widgets\StatsOverviewWidget\Stat;
 
Stat::make('Unique views', $uniqueViews)
->placeholder('-')

To use the same placeholder for every stat, call Stat::configureUsing() in a service provider's boot() method. Individual stats can still override it.

PR: #20176

Reusable Multi-Factor Challenge

The one-time code challenge from the login page now lives in a MultiFactorChallenge class, extracted by Bartłomiej Gajda. You can use it to ask for a second factor in your own Livewire components, for example before a destructive action:

use Filament\Auth\MultiFactor\MultiFactorChallenge;
 
$multiFactorChallenge = MultiFactorChallenge::make();
 
abort_unless($multiFactorChallenge->hasEnabledProviders($user), 403);
 
$schema
->components($multiFactorChallenge->getSchemaComponents($user))
->statePath('multiFactorData');

The class also has methods for rate limiting attempts and for running a provider's setup before the challenge, such as emailing a code. The documentation notes that validating an empty schema succeeds, so check hasEnabledProviders() again immediately before you validate.

PR: #20353

Editor Height Controls

The RichEditor and MarkdownEditor fields have new minHeight() and maxHeight() methods, contributed by Dennis Koch. Both accept any CSS length. The editor has a minimum height of 10rem by default, and once the content passes maxHeight(), the editor stops growing and scrolls:

use Filament\Forms\Components\RichEditor;
 
RichEditor::make('content')
->minHeight('12rem')
->maxHeight('24rem')

PR: #20258

Custom Export Downloaders

Exporters can now use a custom downloader, a change from Brett B. The default downloaders return a streamed response, which some hosts, such as Laravel Vapor, block for large files. Override getDownloader() on an exporter to return a different downloader per format:

use App\Filament\Exports\Downloaders\CustomCsvDownloader;
use Filament\Actions\Exports\Downloaders\Contracts\Downloader;
use Filament\Actions\Exports\Enums\Contracts\ExportFormat as ExportFormatInterface;
use Filament\Actions\Exports\Enums\ExportFormat;
 
public static function getDownloader(ExportFormatInterface $format): Downloader
{
return match ($format) {
ExportFormat::Csv => app(CustomCsvDownloader::class),
default => $format->getDownloader(),
};
}

A downloader is an invokable class that receives the Export model and returns a Symfony Response, so it can stream the file, return it, or redirect to a temporary URL on a remote disk. Importers can override getFailedRowsDownloader() the same way for the failed rows CSV. For an introduction to the import action, see Handling Bulk Imports in Filament.

PR: #20285

Boost Guidelines as an Agent Skill

Filament's Laravel Boost guidelines are now an Agent Skill, converted by Ryan Hayle. Boost used to add the full guidelines to the agent's context in every session. The skill loads only when the agent works on Filament code. To install it, run php artisan boost:install, select Agent Skills, then select filament/filament (skills). Running boost:update alone does not add it. See the Laravel Boost v2.0 release for background on skills support in Boost.

PR: #20338

References

Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Sponsored

acquaintsoft logo
Acquaint Softtech

Hire Laravel developers with AI expertise at $20/hr. Get started in 48 hours.

Visit Acquaint Softtech

The latest

View all →
Sloppy: Static Analysis for AI-Written PHP image

Sloppy: Static Analysis for AI-Written PHP

Read article
Mastering Laravel Testing: A Video Course on Laravel Tests image

Mastering Laravel Testing: A Video Course on Laravel Tests

Read article
NativePHP Mobile Security Advisories: Update to 4.6.1 image

NativePHP Mobile Security Advisories: Update to 4.6.1

Read article
Volker Dusch on the PHP RCE Vulnerabilities and Disclosure image

Volker Dusch on the PHP RCE Vulnerabilities and Disclosure

Read article
Decide with Jev: Turn AI Answers into Decisions with PHP Rules image

Decide with Jev: Turn AI Answers into Decisions with PHP Rules

Read article
Skuul: Open-Source School Management Built on Laravel image

Skuul: Open-Source School Management Built on Laravel

Read article