#!/usr/bin/env php
Filament v4 Upgrade
Welcome to the Filament v4 upgrade process!
This script will attempt to handle most of the breaking changes for you.
If you have any questions, please reach out to us on Discord or GitHub.
To begin, please ensure that you are using a version control system such as Git.
We will make changes directly to your files, and you will need to be able to revert them if something goes wrong.
Please commit any changes you have made to your project before continuing.

HTML); require __DIR__ . '/../src/check-compatibility.php'; $directories = $argv[1] ?? ask(<< Please provide a comma-separated list of directories containing Filament code to upgrade (e.g. app, app-modules, src).
You can skip this if you have a normal Laravel app structure: HTML) ?: 'app'; render(<< Starting upgrade...

HTML); $rectorScriptPath = implode(DIRECTORY_SEPARATOR, ['vendor', 'bin', 'rector']); foreach (explode(',', $directories) as $directory) { $directory = trim($directory); $directory = trim($directory, DIRECTORY_SEPARATOR); render(<< Start processing /{$directory} to fix code affected by breaking changes.

HTML); exec("{$rectorScriptPath} process {$directory} --config vendor/filament/upgrade/src/rector.php --clear-cache"); render(<< Finished processing /{$directory}.

HTML); } $requireCommands = []; foreach (json_decode(file_get_contents('composer.json'), true)['require'] as $package => $version) { if ($package === 'filament/upgrade') { continue; } if (! str_starts_with($package, 'filament/')) { continue; } if ($package === 'filament/spatie-laravel-translatable-plugin') { $requireCommands[] = "composer remove filament/spatie-laravel-translatable-plugin -W --no-update"; $requireCommands[] = "composer require lara-zeus/spatie-translatable -W --no-update"; continue; } $isWindows = str_starts_with(php_uname(), 'Windows'); if (! $isWindows) { $requireCommands[] = "composer require {$package}:\"^4.0\" -W --no-update"; } else { $requireCommands[] = "composer require {$package}:\"~4.0\" -W --no-update"; } } $requireCommandsHtml = implode("
", $requireCommands); // Suggest commands for third-party Filament plugins that have v4-compatible releases $thirdPartyCommands = []; try { // Prefer shared cache generated by check-compatibility.php $shared = $GLOBALS['FILAMENT_UPGRADE_PACKAGIST'] ?? null; // Fallback detection if cache is not available if ($shared === null || empty($shared['plugins'])) { $composer = json_decode(file_get_contents('composer.json'), true); $deps = $composer['require'] ?? []; $allPackages = array_keys($deps); $plugins = array_filter($allPackages, function ($plugin) { if (str_starts_with($plugin, 'filament/')) { return false; } try { $composerPath = "vendor/{$plugin}/composer.json"; if (! file_exists($composerPath)) { return false; } $composerContent = file_get_contents($composerPath); $composer = json_decode($composerContent, true); if (! $composer || ! is_array($composer)) { return false; } $requires = $composer['require'] ?? []; foreach ($requires as $key => $value) { if (str_starts_with($key, 'filament/')) { return true; } } } catch (Throwable $exception) {} return false; }); // Create a minimal shared structure to continue without extra HTTP (none yet cached though) $shared = [ 'plugins' => array_values($plugins), 'compatibility' => [], 'versions' => [], ]; } $plugins = $shared['plugins'] ?? []; $isWindows = str_starts_with(php_uname(), 'Windows'); foreach ($plugins as $plugin) { // Try to reuse computed compatibility first $compatibility = $shared['compatibility'][$plugin] ?? null; // ['version' => string, 'isPrerelease' => bool] // If not computed, try to evaluate using cached versions arrays (no HTTP) if ($compatibility === null) { $stableVersions = $shared['versions'][$plugin]['stable'] ?? []; foreach ($stableVersions as $checkingVersion) { $requires = $checkingVersion['require'] ?? []; foreach ($requires as $dep => $constraint) { if (!str_starts_with($dep, 'filament/')) { continue; } if (preg_match("/\^\s*4(?:\.|$)|~\s*4(?:\.|$)|>=\s*4(?:\.|$)/", (string) $constraint)) { $compatibility = [ 'version' => (string) ($checkingVersion['version'] ?? ''), 'isPrerelease' => false, ]; break; } } } if ($compatibility === null) { $devVersions = $shared['versions'][$plugin]['dev'] ?? []; foreach ($devVersions as $checkingVersion) { $requires = $checkingVersion['require'] ?? []; foreach ($requires as $dep => $constraint) { if (!str_starts_with($dep, 'filament/')) { continue; } if (preg_match("/\^\s*4(?:\.|$)|~\s*4(?:\.|$)|>=\s*4(?:\.|$)/", (string) $constraint)) { $compatibility = [ 'version' => (string) ($checkingVersion['version'] ?? ''), 'isPrerelease' => true, ]; break; } } } } } // As a last resort, if we still don't have compatibility and there are no cached versions, do HTTP queries if ($compatibility === null && empty($shared['versions'][$plugin])) { try { $url = "https://repo.packagist.org/p2/{$plugin}.json"; $json = @file_get_contents($url); if ($json) { $data = json_decode($json, true); $versions = $data['packages'][$plugin] ?? []; $shared['versions'][$plugin]['stable'] = $versions; foreach ($versions as $checkingVersion) { $requires = $checkingVersion['require'] ?? []; foreach ($requires as $dep => $constraint) { if (!str_starts_with($dep, 'filament/')) { continue; } if (preg_match("/\^\s*4(?:\.|$)|~\s*4(?:\.|$)|>=\s*4(?:\.|$)/", (string) $constraint)) { $compatibility = [ 'version' => (string) ($checkingVersion['version'] ?? ''), 'isPrerelease' => false, ]; break; } } } } if ($compatibility === null) { $devUrl = "https://repo.packagist.org/p2/{$plugin}~dev.json"; $devJson = @file_get_contents($devUrl); if ($devJson) { $devData = json_decode($devJson, true); $devVersions = $devData['packages'][$plugin] ?? []; $shared['versions'][$plugin]['dev'] = $devVersions; foreach ($devVersions as $checkingVersion) { $requires = $checkingVersion['require'] ?? []; foreach ($requires as $dep => $constraint) { if (!str_starts_with($dep, 'filament/')) { continue; } if (preg_match("/\^\s*4(?:\.|$)|~\s*4(?:\.|$)|>=\s*4(?:\.|$)/", (string) $constraint)) { $compatibility = [ 'version' => (string) ($checkingVersion['version'] ?? ''), 'isPrerelease' => true, ]; break; } } } } } } catch (Throwable $exception) { // ignore } } if ($compatibility !== null) { // Persist back for other consumers during this run $GLOBALS['FILAMENT_UPGRADE_PACKAGIST']['compatibility'][$plugin] = $compatibility; $version = ltrim($compatibility['version'], 'v'); $isPrerelease = $compatibility['isPrerelease']; if ($version === 'unknown') { // Skip suggesting install command for plugins with unknown version (e.g., private/non-Packagist) continue; } $constraint = null; if (! $isPrerelease) { // Prefer using major.minor for pre-v1 packages to avoid ^0.0 or ~0.0 if (preg_match('/^(\d+)\.(\d+)/', $version, $m)) { $major = (int) $m[1]; $minor = (int) $m[2]; if ($major === 0) { // For 0.x.y, constrain to the minor series, e.g. ^0.12 or ~0.12 $constraint = $isWindows ? "~0.{$minor}" : "^0.{$minor}"; } else { // For 1.x and above, stick to major series, e.g. ^4.0 or ~4.0 $constraint = $isWindows ? "~{$major}.0" : "^{$major}.0"; } } elseif (preg_match('/^(\d+)/', $version, $m)) { // Fallback: if only a major could be parsed $major = (int) $m[1]; $constraint = $isWindows ? "~{$major}.0" : "^{$major}.0"; } } if ($constraint === null) { $constraint = $version; } $thirdPartyCommands[] = "composer require {$plugin}:\"{$constraint}\" -W --no-update"; } } } catch (Throwable $exception) { // If anything goes wrong, we just won't print third-party suggestions } $thirdPartyCommandsHtml = $thirdPartyCommands ? '
' . implode("

", $thirdPartyCommands) : ''; render(<< Now you're ready to update your Composer dependencies!

First require new versions of Filament packages:
{$requireCommandsHtml}{$thirdPartyCommandsHtml}

And then run:
composer update

If you have any questions, please reach out to us on Discord or GitHub. HTML);