Version

Theme

Upgrade guide

NOTE

If you see anything missing from this guide, please do not hesitate to make a pull request to our repository! Any help is appreciated!

New requirements

  • PHP 8.2+
  • Laravel v11.28+
  • Tailwind CSS v4.0+, if you are currently using Tailwind CSS v3.0 with Filament. This does not apply if you are just using a Filament panel without a custom theme CSS file.
  • Filament no longer requires doctrine/dbal, but if your application still does, and you do not have it installed directly, you should add it to your composer.json file.

Running the automated upgrade script

NOTE

The upgrade script is not a replacement for the upgrade guide. It handles many small changes that are not mentioned in the upgrade guide, but it does not handle all breaking changes. You should still read the manual upgrade steps to see what changes you need to make to your code.

The first set to upgrade your Filament app is to run the automated upgrade script. This script will automatically upgrade your application to the latest version of Filament and make changes to your code, which handles most breaking changes:

composer require filament/upgrade:"^4.0" -W --dev

vendor/bin/filament-v4

NOTE

If installing the upgrade script fails, make sure that your PHPStan version is at least v2, or your Larastan version is at least v3. The script uses Rector v2, which requires PHPStan v2 or higher.

Make sure to carefully follow the instructions, and review the changes made by the script. You may need to make some manual changes to your code afterwards, but the script should handle most of the repetitive work for you.

You can now composer remove filament/upgrade as you don’t need it anymore.

NOTE

Some plugins you’re using may not be available in v4 just yet. You could temporarily remove them from your composer.json file until they’ve been upgraded, replace them with a similar plugins that are v4-compatible, wait for the plugins to be upgraded before upgrading your app, or even write PRs to help the authors upgrade them.

Cleaning up your code style after upgrading to v4

The automated upgrade script uses Rector to make changes to your code. Sometimes, the tool may change how your code is formatted or introduce references to classes that are not yet imported.

Filament suggests using Laravel Pint or PHP CS Fixer to clean up your code style after running the upgrade script.

Specifically, the fully_qualified_strict_types rule and global_namespace_import rule in these tools will fix any references to classes that are not yet imported, which is a common issue after running the upgrade script.

This is the Laravel Pint configuration that Filament uses for its own codebase if you would like a good starting point:

{
    "preset": "laravel",
    "rules": {
        "blank_line_before_statement": true,
        "concat_space": {
            "spacing": "one"
        },
        "fully_qualified_strict_types": {
            "import_symbols": true
        },
        "global_namespace_import": true,
        "method_argument_space": true,
        "single_trait_insert_per_statement": true,
        "types_spaces": {
            "space": "single"
        }
    }
}

Publishing the configuration file

Some changes in Filament v4 can be reverted using the configuration file. If you haven’t published the configuration file yet, you can do so by running the following command:

php artisan vendor:publish --tag=filament-config

Firstly, the default_filesystem_disk in v4 is set to the FILESYSTEM_DISK variable instead of FILAMENT_FILESYSTEM_DISK. To preserve the v3 behavior, make sure you use this setting:

return [

    // ...

    'default_filesystem_disk' => env('FILAMENT_FILESYSTEM_DISK', 'public'),

    // ...

]

v4 introduces some changes to how Filament generates files. A new file_generation section has been added to the v4 configuration file, so that you can revert back to the v3 style if you would like to keep new code consistent with how it looked before upgrading. If your configuration file doesn’t already have a file_generation section, you should add it yourself, or re-publish the configuration file and tweak it to your liking:

use Filament\Support\Commands\FileGenerators\FileGenerationFlag;

return [

    // ...

    'file_generation' => [
        'flags' => [
            FileGenerationFlag::EMBEDDED_PANEL_RESOURCE_SCHEMAS, // Define new forms and infolists inside the resource class instead of a separate schema class.
            FileGenerationFlag::EMBEDDED_PANEL_RESOURCE_TABLES, // Define new tables inside the resource class instead of a separate table class.
            FileGenerationFlag::PANEL_CLUSTER_CLASSES_OUTSIDE_DIRECTORIES, // Create new cluster classes outside of their directories.
            FileGenerationFlag::PANEL_RESOURCE_CLASSES_OUTSIDE_DIRECTORIES, // Create new resource classes outside of their directories.
            FileGenerationFlag::PARTIAL_IMPORTS, // Partially import components such as form fields and table columns instead of importing each component explicitly.
        ],
    ],

    // ...

]

Breaking changes that must be handled manually

To begin, filter the upgrade guide for your specific needs by selecting only the packages that you use in your project:

This package is also often used in a panel, or using the tables or actions package.

This package is also often used in a panel, or using the tables or actions package.

This package is also often used in a panel.

This package is also often used in a panel.

This package is also often used in a panel.

This package is also often used in a panel.

High-impact changes

Custom themes need to be upgraded to Tailwind CSS v4

Previously, custom theme CSS files contained this:

@import '../../../../vendor/filament/filament/resources/css/theme.css';

@config 'tailwind.config.js';

Now, they should contain this:

@import '../../../../vendor/filament/filament/resources/css/theme.css';

@source '../../../../app/Filament';
@source '../../../../resources/views/filament';

This will load Tailwind CSS. The @source entries tell Tailwind where to find the classes that are used in your app. You should check the content paths in your old tailwind.config.js file, and add them as @source entries like this. You don’t need to include vendor/filament as a @source, but check plugins you have installed to see if they require @source entries.

Finally, you should use the Tailwind upgrade tool to automatically adjust your configuration files to use Tailwind v4, and install Tailwind v4 packages to replace Tailwind v3 ones:

npx @tailwindcss/upgrade

The tailwind.config.js file for your theme is no longer used, since Tailwind CSS v4 defines configuration in CSS. Any customizations you made to the tailwind.config.js file should be added to the CSS file.

Changes to table filters are deferred by default

The deferFilters() method from Filament v3 is now the default behavior in Filament v4, so users must click a button before the filters are applied to the table. To disable this behavior, you can use the deferFilters(false) method.

use Filament\Tables\Table;

public function table(Table $table): Table
{
    return $table
        ->deferFilters(false);
}

TIP

You can preserve the old default behavior across your entire app by adding the following code in the boot() method of a service provider like AppServiceProvider:

use Filament\Tables\Table;

Table::configureUsing(fn (Table $table) => $table
    ->deferFilters(false));
The Grid, Section and Fieldset layout components now do not span all columns by default

In v3, the Grid, Section and Fieldset layout components consumed the full width of their parent grid by default. This was inconsistent with the behavior of every other component in Filament, which only consumes one column of the grid by default. The intention was to make these components easier to integrate into the default Filament resource form and infolist, which uses a two column grid out of the box.

In v4, the Grid, Section and Fieldset layout components now only consume one column of the grid by default. If you want them to span all columns, you can use the columnSpanFull() method:

use Filament\Schemas\Components\Fieldset;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;

Fieldset::make()
    ->columnSpanFull()
    
Grid::make()
    ->columnSpanFull()

Section::make()
    ->columnSpanFull()

TIP

You can preserve the old default behavior across your entire app by adding the following code in the boot() method of a service provider like AppServiceProvider:

use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Section;

Fieldset::configureUsing(fn (Fieldset $fieldset) => $fieldset
    ->columnSpanFull());

Grid::configureUsing(fn (Grid $grid) => $grid
    ->columnSpanFull());

Section::configureUsing(fn (Section $section) => $section
    ->columnSpanFull());
The unique() validation rule behavior for ignoring Eloquent records

In v3, the unique() method did not ignore the current form’s Eloquent record when validating by default. This behavior was enabled by the ignoreRecord: true parameter, or by passing a custom ignorable record.

In v4, the unique() method’s ignoreRecord parameter defaults to true.

If you were previously using unqiue() validation rule without the ignoreRecord or ignorable parameters, you should use ignoreRecord: false to disable the new behavior.

TIP

You can preserve the old default behavior across your entire app by adding the following code in the boot() method of a service provider like AppServiceProvider:

use Filament\Forms\Components\Field;

Field::configureUsing(fn (Field $field) => $field
    ->uniqueValidationIgnoresRecordByDefault(false));
The all pagination page option is not available for tables by default

The all pagination page method is now not available for tables by default. If you want to use it on a table, you can add it to the configuration:

use Filament\Tables\Table;

public function table(Table $table): Table
{
    return $table
        ->paginationPageOptions([5, 10, 25, 50, 'all']);
}

Be aware when using all as it will cause performance issues when dealing with a large number of records.

TIP

You can preserve the old default behavior across your entire app by adding the following code in the boot() method of a service provider like AppServiceProvider:

use Filament\Tables\Table;

Table::configureUsing(fn (Table $table) => $table
    ->paginationPageOptions([5, 10, 25, 50, 'all']));
The official Spatie Translatable Plugin is now deprecated

Last year, the Filament team decided to hand over maintenance of the Spatie Translatable Plugin to the team at Lara Zeus, who are trusted developers of many Filament plugins. They have maintained a fork of the plugin ever since.

The official Spatie Translatable Plugin will not recieve v4 support, and is now deprecated. You can use the Lara Zeus Translatable Plugin as a direct replacement. The plugin is compatible with the same version of Spatie Translatable as the official plugin, and has been tested with Filament v4. It also fixes some long-standing bugs in the official plugin.

The automated upgrade script suggests commands that uninstall the official plugin and install the Lara Zeus plugin, and replaces any references in your code to the official plugin with the Lara Zeus plugin.

Medium-impact changes

Automatic tenancy global scoping and association

When using tenancy in v3, Filament only scoped resource queries to the current tenant: to render the resource table, resolve URL parameters, and fetch global search results. There were many situations where other queries in the panel were not scoped by default, and the developer had to manually scope them. While this was a documented feature, it created a lot of additional work for developers.

In v4, Filament automatically scopes all queries in a panel to the current tenant, and automatically associates new records with the current tenant using model events. This means that you no longer need to manually scope queries or associate new Eloquent records in most cases. There are still some important points to consider, so the documentation has been updated to reflect this.

The Radio inline() method behavior

In v3, the inline() method put the radio buttons inline with each other, and also inline with the label at the same time. This is inconsistent with other components.

In v4, the inline() method now only puts the radio buttons inline with each other, and not with the label. If you want the radio buttons to be inline with the label, you can use the inlineLabel() method as well.

If you were previously using inline()->inlineLabel(false) to achieve the v4 behavior, you can now simply use inline().

TIP

You can preserve the old default behavior across your entire app by adding the following code in the boot() method of a service provider like AppServiceProvider:

use Filament\Forms\Components\Radio;

Radio::configureUsing(fn (Radio $radio) => $radio
    ->inlineLabel(fn (): bool => $radio->isInline()));
Import and export job retries

In Filament v3, import and export jobs were retries continuously for 24 hours if they failed, with no backoff between tries by default. This caused issues for some users, as there was no backoff period and the jobs could be retried too quickly, causing the queue to be flooded with continuously failing jobs.

In v4, they are retried 3 times with a 60 second backoff between each retry.

This behavior can be customized in the importer and exporter classes.

Low-impact changes

The color shade customization API has been removed

The API to customize color shades, FilamentColor::addShades(), FilamentColor::overrideShades(), and FilamentColor::removeShades() has been removed. This API was replaced with a more advanced color system in v4 which uses different shades for components based on their contrast, to ensure that components are accessible.

The isSeparate parameter of ImageColumn::limitedRemainingText() and ImageEntry::limitedRemainingText() has been removed

Previously, users were able to display the number of limited images separately to an image stack using the isSeparate parameter. Now the parameter has been removed, and if a stack exists, the text will always be stacked on top and not separate. If the images are not stacked, the text will be separate.

The RichEditor component’s disableGrammarly() method has been removed

The disableGrammarly() method has been removed from the RichEditor component. This method was used to disable the Grammarly browser extension acting on the editor. Since moving the underlying implementation of the editor from Trix to TipTap, we have not found a way to disable Grammarly on the editor.

Overriding the Field::make(), MorphToSelect::make(), Placeholder::make(), or Builder\Block::make() methods

The signature for the Field::make(), MorphToSelect::make(), Placeholder::make(), and Builder\Block::make() methods has changed. Any classes that extend the Field, MorphToSelect, Placeholder, or Builder\Block class and override the make() method must update the method signature to match the new signature. The new signature is as follows:

public static function make(?string $name = null): static

This is due to the introduction of the getDefaultName() method, that can be overridden to provide a default $name value if one is not specified (null). If you were previously overriding the make() method in order to provide a default $name value, it is advised that you now override the getDefaultName() method instead, to avoid further maintenance burden in the future:

public static function getDefaultName(): ?string
{
    return 'default';
}

If you are overriding the make() method to pass default configuration to the object once it is instantiated, please note that it is recommended to instead override the setUp() method, which is called immediately after the object is instantiated:

protected function setUp(): void
{
    parent::setUp();

    $this->label('Default label');
}

Ideally, you should avoid overriding the make() method altogether as there are alternatives like setUp(), and doing so causes your code to be brittle if Filament decides to introduce new constructor parameters in the future.

Overriding the Entry::make() method

The signature for the Entry::make() method has changed. Any classes that extend the Entry class and override the make() method must update the method signature to match the new signature. The new signature is as follows:

public static function make(?string $name = null): static

This is due to the introduction of the getDefaultName() method, that can be overridden to provide a default $name value if one is not specified (null). If you were previously overriding the make() method in order to provide a default $name value, it is advised that you now override the getDefaultName() method instead, to avoid further maintenance burden in the future:

public static function getDefaultName(): ?string
{
    return 'default';
}

If you are overriding the make() method to pass default configuration to the object once it is instantiated, please note that it is recommended to instead override the setUp() method, which is called immediately after the object is instantiated:

protected function setUp(): void
{
    parent::setUp();

    $this->label('Default label');
}

Ideally, you should avoid overriding the make() method altogether as there are alternatives like setUp(), and doing so causes your code to be brittle if Filament decides to introduce new constructor parameters in the future.

Overriding the Column::make() or Constraint::make() methods

The signature for the Column::make() and Constraint::make() methods has changed. Any classes that extend the Column or Constraint class and override the make() method must update the method signature to match the new signature. The new signature is as follows:

public static function make(?string $name = null): static

This is due to the introduction of the getDefaultName() method, that can be overridden to provide a default $name value if one is not specified (null). If you were previously overriding the make() method in order to provide a default $name value, it is advised that you now override the getDefaultName() method instead, to avoid further maintenance burden in the future:

public static function getDefaultName(): ?string
{
    return 'default';
}

If you are overriding the make() method to pass default configuration to the object once it is instantiated, please note that it is recommended to instead override the setUp() method, which is called immediately after the object is instantiated:

protected function setUp(): void
{
    parent::setUp();

    $this->label('Default label');
}

Ideally, you should avoid overriding the make() method altogether as there are alternatives like setUp(), and doing so causes your code to be brittle if Filament decides to introduce new constructor parameters in the future.

Overriding the ExportColumn::make() or ImportColumn::make() methods

The signature for the ExportColumn::make() and ImportColumn::make() methods has changed. Any classes that extend the ExportColumn or ImportColumn class and override the make() method must update the method signature to match the new signature. The new signature is as follows:

public static function make(?string $name = null): static

This is due to the introduction of the getDefaultName() method, that can be overridden to provide a default $name value if one is not specified (null). If you were previously overriding the make() method in order to provide a default $name value, it is advised that you now override the getDefaultName() method instead, to avoid further maintenance burden in the future:

public static function getDefaultName(): ?string
{
    return 'default';
}

If you are overriding the make() method to pass default configuration to the object once it is instantiated, please note that it is recommended to instead override the setUp() method, which is called immediately after the object is instantiated:

protected function setUp(): void
{
    parent::setUp();

    $this->label('Default label');
}

Ideally, you should avoid overriding the make() method altogether as there are alternatives like setUp(), and doing so causes your code to be brittle if Filament decides to introduce new constructor parameters in the future.

Authenticating the user inside the import and export jobs

In v3, the Illuminate\Auth\Events\Login event was fired from the import and export jobs, to set the current user. This is no longer the case in v4: the user is authenticated, but that event is not fired, to avoid running any listeners that should only run for actual user logins.

Overriding the can*() authorization methods on a Resource, RelationManager or ManageRelatedRecords class

Although these methods, such as canCreate(), canViewAny() and canDelete() were not documented, if you are overriding those to provide custom authorization logic in v3, you should be aware that they are not always called in v4. The authorization logic has been improved to properly support policy response objects, and these methods were too simple as they are just able to return booleans.

If you can make the authorization customization inside the policy of the model instead, you should do that. If you need to customize the authorization logic in the resource or relation manager class, you should override the get*AuthorizationResponse() methods instead, such as getCreateAuthorizationResponse(), getViewAnyAuthorizationResponse() and getDeleteAuthorizationResponse(). These methods are called when the authorization logic is executed, and they return a policy response object. If you remove the override for the can*() methods, the get*AuthorizationResponse() methods will be used to determine the authorization response boolean, so you don’t have to maintain the logic twice.

European Portuguese translations

The European Portuguese translations have been moved from pt_PT to pt, which appears to be the more commonly used language code for the language within the Laravel community.

Nepalese translations

The Nepalese translations have been moved from np to ne, which appears to be the more commonly used language code for the language within the Laravel community.

Norwegian translations

The Norwegian translations have been moved from no to nb, which appears to be the more commonly used language code for the language within the Laravel community.

Edit on GitHub

Still need help? Join our Discord community or open a GitHub discussion