DEV Community

Cover image for The 4 Composer Conflicts That Block Most Laravel 13 Upgrades (And How to Find Yours)
Hafiz
Hafiz

Posted on • Originally published at hafiz.dev

The 4 Composer Conflicts That Block Most Laravel 13 Upgrades (And How to Find Yours)

Originally published at hafiz.dev


Most Laravel apps don't fail upgrades because of breaking changes. They fail because composer update throws a wall of red text and the developer closes the terminal.

Laravel 13 shipped with "zero breaking changes" to application code. That's true. Your routes, controllers, and Eloquent models don't need touching. But your composer.json is a different story. Somewhere in your 30-50 dependencies, there's almost certainly a version constraint that won't resolve against laravel/framework:^13.0. And finding it manually means running composer why-not, reading cryptic output, fixing one conflict, discovering the next, and repeating until you either succeed or give up and decide to "upgrade later."

Four specific conflicts catch most developers. After looking at how these surface in real composer.json files, in GitHub issues, and in r/laravel threads, the same patterns keep showing up. Here's what each one looks like, why it happens, and how to fix it.

1. Your PHP Constraint Is Too Loose

This is the most common blocker and the easiest to miss. Your composer.json probably says something like this:

"require": {
    "php": "^8.1"
}
Enter fullscreen mode Exit fullscreen mode

Laravel 13 requires PHP 8.3 as the minimum. That constraint above technically allows 8.1, 8.2, 8.3, and 8.4. Two things can go wrong here.

First, if your server actually runs PHP 8.2, Composer will refuse to install Laravel 13 regardless of what your composer.json says. The error looks like this:

Your requirements could not be resolved to an installable set of packages.

Problem 1
  - laravel/framework v13.0.0 requires php ^8.3 -> your php version (8.2.28)
    does not satisfy that requirement.
Enter fullscreen mode Exit fullscreen mode

Second, even if your server runs 8.3, a loose constraint like ^8.1 means your app could be deployed on 8.1 or 8.2, where Laravel 13 won't work. Tightening the constraint protects you from that.

The fix: Run php -v on production first. If it returns anything below 8.3, upgrade PHP before touching Composer. Then tighten your constraint to match:

"require": {
    "php": "^8.3"
}
Enter fullscreen mode Exit fullscreen mode

Don't skip this. Every other fix in this post is pointless if your PHP version is too low.

2. The Symfony 8 Surprise

This one is newer and won't hit you on day one. Laravel 13.0 through 13.2 work fine on PHP 8.3. But starting with Laravel 13.3, the framework allows Symfony 8 components (symfony/error-handler, symfony/console) that require PHP 8.4.

If you're on PHP 8.3 and you run composer update after the initial upgrade, you might get hit by this weeks later:

Problem 1
  - laravel/framework v13.3.0 requires symfony/error-handler ^7.4.0 || ^8.0.0
    -> satisfiable by symfony/error-handler[v8.0.8].
  - symfony/error-handler v8.0.8 requires php >=8.4
    -> your php version (8.3.30) does not satisfy that requirement.
Enter fullscreen mode Exit fullscreen mode

The fix: You have two options. Either upgrade to PHP 8.4 (the better long-term choice), or pin Symfony to 7.4 in your composer.json:

composer require symfony/console:"^7.4" symfony/error-handler:"^7.4"
Enter fullscreen mode Exit fullscreen mode

This keeps you on Symfony 7.4 while running Laravel 13.3+. It works, but it's a temporary workaround. PHP 8.4 is where you want to be.

If you're reading the full Laravel 13 upgrade guide, this particular conflict isn't covered there because it appeared after the initial release. It's exactly the kind of thing that catches you between "I upgraded successfully" and "why is production broken after composer update?"

3. Spatie Packages Pinned to Old Major Versions

If you use any Spatie packages (and most Laravel apps do), check their version constraints carefully. Older major versions often don't support Laravel 13. The most common offenders:

  • spatie/laravel-permission v5 doesn't support Laravel 13. You need at least v6.
  • spatie/laravel-medialibrary older major versions don't support Laravel 13. Check your installed version against the latest release.
  • spatie/laravel-activitylog requires at least v4.12 for Laravel 13 support. Earlier v4 releases won't resolve.

The error looks like a typical version mismatch:

Problem 1
  - spatie/laravel-permission v5.11.1 requires illuminate/database ^9.0|^10.0|^11.0|^12.0
    -> found illuminate/database v13.0.0 but it does not match ^9.0|^10.0|^11.0|^12.0.
Enter fullscreen mode Exit fullscreen mode

The fix: Upgrade each Spatie package to the latest major version before upgrading Laravel. Check the GitHub releases page for each one. Most have migration guides. Spatie generally ships Laravel support within days of a new release, and they've even backported Laravel 13 compatibility to some older branches so third-party packages have time to catch up.

One tip: run composer outdated --major to see which packages have major version jumps available. That command shows you the gap without trying to resolve anything.

4. Testing Packages That Quietly Block Everything

PHPUnit and Pest are required by almost every Laravel app, but they sit in require-dev and tend to get ignored during upgrades. They shouldn't be.

Laravel 13 requires phpunit/phpunit:^11.5.50 or ^12.0. If your composer.json still has "phpunit/phpunit": "^10.0", that's a blocker. Same with Pest: you need at least Pest 3.8.5 for Laravel 13 support (earlier 3.x releases pin a PHPUnit version that's too low).

The error often looks something like this, showing up in require-dev where some developers skip reading:

Problem 1
  - phpunit/phpunit 10.5.46 requires sebastian/comparator ^5.0
    -> found sebastian/comparator 6.3.1 but it does not match ^5.0.
Enter fullscreen mode Exit fullscreen mode

That's PHPUnit 10 conflicting with a transitive dependency that Laravel 13's newer Symfony components pull in. It's not obvious at all from the error message.

The fix: Update your testing packages first:

composer require --dev phpunit/phpunit:^12.0

# Or if you use Pest:
composer require --dev pestphp/pest:^3.0
Enter fullscreen mode Exit fullscreen mode

If you've been putting off the Pest 4 migration, now is the time. Pest 4 ships with full Laravel 13 support and a cleaner API.

Or Skip All of This

Every conflict above follows the same pattern: something in your composer.json doesn't match what Laravel 13 expects, and finding it requires running commands, reading error output, and debugging one conflict at a time.

There's a faster way. Paste your composer.json into the Laravel Upgrade Analyzer and it'll show you exactly which dependencies need attention. It checks 33 packages (including PHP version, Symfony components, and testing tools) against Laravel 13's requirements and flags each one as Blocker (stops the upgrade entirely), Breaking (needs a major version bump), or Watch (minor bump, low risk). The whole thing takes about 5 seconds and nothing gets stored.

If you went through the Laravel 12 to 13 upgrade guide and already upgraded, the analyzer still catches stale package versions and constraint mismatches you might have missed.

And if you don't want to deal with any of this yourself, I do Laravel upgrades. Send me your composer.json and I'll scope it.

FAQ

Does the Upgrade Analyzer work for older upgrades like Laravel 11 to 12?

Right now it's focused on Laravel 13 specifically. The rules check PHP version requirements, first-party Laravel packages, and 33 of the most common third-party packages against Laravel 13 compatibility. Support for older upgrade paths may come later.

What if one of my packages isn't in the analyzer's rules?

The analyzer covers 33 packages (25 auto-derived from Packagist, 8 hand-curated). If your package isn't covered, you can check manually by running composer why-not laravel/framework:^13.0 or checking the package's GitHub releases for Laravel 13 support. Found a package that should be included? Let me know and I'll add it.

Is my composer.json stored or shared?

No. The analyzer processes your file server-side and discards it immediately. Nothing is stored, logged, or shared. You can verify this in the page footer.

Top comments (0)