The go-to resource for learning PHP, Laravel, Symfony, and your dependencies.

Upgrading from Laravel 9 to Laravel 10: A Practical Guide


Laravel, first released in 2011 by Taylor Otwell, has grown to become one of PHP’s most popular frameworks. Over more than a decade, Laravel has progressed through a predictable yearly release cycle—each annual release bringing new features and improvements, while security fixes continue for a defined period. This predictable cadence means that, as a Laravel developer, you’ll likely face upgrade decisions regularly.

If you’re maintaining a Laravel 9 application in 2025 or later, you’re already behind the support timeline. Laravel 9 entered security-fixes-only mode in February 2024 and reached its end-of-life in February 2025. Staying on an unsupported version means missing security patches—a risk your production applications likely cannot accept. Upgrading to Laravel 10, then, is not merely about accessing new features; it’s about maintaining a secure, supported foundation for your day-to-day work.

This guide walks you through the upgrade process from Laravel 9 to Laravel 10. We’ll start with the broader context of Laravel’s versioning strategy, then move into concrete steps for updating your application. Along the way, we’ll discuss alternatives like automated upgrade tools, examine the specific changes you’ll need to make, and outline how to verify that your upgraded application works as expected.

Understanding Laravel’s Release Strategy

Before we get into the specific upgrade steps, let’s take a step back and discuss Laravel’s versioning strategy—because understanding this framework affects your upgrade timing and overall approach.

Laravel follows an annual release cycle. Each year, typically in the first quarter, a new major version is released. These major releases occur roughly every 12 months and include new features and significant changes. After release, each major version receives:

  • Bug fixes: Approximately 18 months from release date
  • Security fixes: Approximately 24 months from release date

For Laravel 10, released on February 14, 2023, this means:

  • Bug fixes ended around August 2024
  • Security fixes will end around February 2025 (though as of 2026, you’re well past this date)

This schedule means that applications on Laravel 9 after February 2025 no longer receive any security updates. If you’re reading this in 2025 or later, upgrading is not optional for production applications—it’s a security necessity.

Laravel’s releases also follow a predictable dependency pattern. Each major version typically requires a newer minimum PHP version. Laravel 10 requires PHP 8.1 or higher, compared to Laravel 9’s PHP 8.0.7+ requirement. This alignment reflects PHP’s own release cycle: PHP 8.1 introduced features like enums, readonly properties, and fibers—features Laravel can now leverage throughout its codebase.

Why Consider Laravel 10 Specifically?

While the primary driver for upgrading is staying on a supported version, Laravel 10 brings meaningful improvements that affect your day-to-day development workflow. Let’s examine what you gain beyond security support.

  • Native Type Declarations: The framework’s internal code now uses native PHP type hints extensively. This doesn’t change how you write your application code, but it means the framework itself is more robust and better understood by IDEs and static analysis tools.
  • Laravel Pennant: A first-party feature flagging system. If you’ve used third-party packages like Laravel Feature Flags or spatie/laravel-feature-flags, Pennant provides an official alternative that integrates tightly with the framework.
  • Process Interaction Improvements: The new Process facade and related classes provide a cleaner interface for running external commands—something many applications do for deployments, queue workers, or system integrations.
  • Deprecation Cleanup: Several features deprecated in Laravel 9 were removed. This may require code changes, but it also means a leaner framework with less legacy baggage.
  • Routine Improvements: Dozens of smaller quality-of-life changes—improved error messages, better column types, streamlined helpers—that accumulate to a better developer experience.

Preparing for the Upgrade: A Checklist

A successful upgrade depends on preparation. Let’s walk through what you should have in place before touching your composer.json file.

Essential Prerequisites

Before you begin, confirm you’ve addressed the following:

  1. Read the official Laravel upgrade guide. The Laravel documentation at https://laravel.com/docs/10.x/upgrade contains the authoritative list of breaking changes and deprecations. We’ll reference specific changes throughout this guide, but the official guide remains your final authority. Of course, this guide aims to complement that documentation with practical walkthroughs and real-world considerations.

  2. Ensure your Laravel 9 application is stable and tested. If you have a test suite—and you should—run it on Laravel 9 and verify all tests pass. If you don’t have a test suite, consider adding at least a few integration tests for critical paths before upgrading. Of course, writing tests during an upgrade can be risky, but if your application has critical business logic, having some baseline verification makes it easier to spot regressions.

  3. Commit all pending changes and create an upgrade branch. We recommend creating a dedicated branch for the upgrade, such as upgrade/laravel-10. If you use Git—which, at this stage, you almost certainly do—make sure your working directory is clean and all changes are committed. This allows you to track upgrade-specific changes separately and revert if necessary.

  4. Back up your production database. Some upgrade steps, particularly if you need to modify database columns to match new Laravel conventions, may require schema changes. While we’ll be conservative in our approach, having a recent database backup is essential. You can typically create a backup through your hosting provider’s control panel or via command line tools like mysqldump or pg_dump.

  5. Verify your PHP environment meets Laravel 10 requirements. Laravel 10 requires PHP 8.1.0 or higher. Before proceeding, check your current PHP version:

    php --version

    If you’re still on PHP 8.0 or earlier, you must upgrade PHP first. Note that some Laravel 9 applications may have been running on PHP 8.0.7 minimum; Laravel 10 bumps this requirement to PHP 8.1.0 because Laravel 10 uses features like readonly properties and enum types that require PHP 8.1.

  6. Document your current Composer dependencies. Before modifying your composer.json file, it’s helpful to know what you’re starting with. You can generate a complete list of installed packages with their versions:

    composer show --outdated

    We’ll use this information later to determine which packages need version updates. Strictly speaking, not all packages require updates for Laravel 10 compatibility, but knowing which ones are currently installed helps you verify the upgrade’s completeness.

  7. Review third-party packages for Laravel 10 compatibility. Laravel 10 is compatible with Laravel 9-era packages in many cases, but some packages—particularly those that extend Laravel’s core or interact with its internals—may require specific versions. Check the documentation or GitHub releases for critical packages like:

    • laravel/sanctum (if used for API authentication)
    • laravel/passport (if used for OAuth)
    • laravel/scout (if used for search)
    • laravel/telescope (if used for debugging)
    • Any queue drivers, cache drivers, or custom service providers

    Though we’ll list the most common package updates later in this guide, you should verify compatibility for any specialized packages your application depends on.

Choosing Your Upgrade Approach

Before diving into the manual steps, let’s consider the landscape of upgrade methods. You might be wondering: do I need to do this entirely by hand? Are there tools to automate the process? The answer depends on your application’s complexity and your available time.

There are three main approaches to upgrading from Laravel 9 to Laravel 10:

Manual upgrade (the approach we’ll detail): You update the composer.json dependencies, run Composer commands, review deprecated code, and make necessary changes yourself. This approach gives you complete control and understanding of every change—something that matters if you need to troubleshoot issues later.

Laravel Shift (automated tool): Laravel Shift (https://laravelshift.com) is a commercial service that automates Laravel upgrades. For a fee—typically around $39 for a Laravel 9 to 10 upgrade as of 2025—Shift makes the Composer changes, updates deprecated code patterns, and creates a pull request you can review. Shift works well for applications that follow Laravel conventions closely; highly customized applications may require manual work afterward anyway.

Tool-assisted manual upgrade: You can use tools like phpstan with Laravel extensions or larastan to statically analyze your code for compatibility issues before upgrading. Running these tools helps catch problems early, though they don’t fix them automatically.

Of course, we don’t need to discuss all possible approaches here—our focus is the manual upgrade, which provides the deepest understanding and works regardless of your budget. That said, if you’re upgrading a large, mission-critical application and have the budget, Laravel Shift can be a worthwhile investment, especially when combined with your own testing afterward. Indeed, many developers use Shift for the initial conversion and then manually address any remaining issues.

The Manual Upgrade Process: A Walkthrough

We’ll now walk through the manual upgrade process step by step. We recommend following these steps in order, starting from a fresh branch with all work committed. Let’s begin.

Step 1: Verify Your Starting Point

First, let’s confirm your application is actually on Laravel 9 and ready to upgrade. We’ll check the current Laravel version and PHP version:

# Check your current Laravel version through Composer
composer show laravel/framework | grep versions

# Check your PHP version
php --version

You should see something like:

versions : * 9.*

And your PHP version should be at least 8.0.7 for Laravel 9. If you’re not on Laravel 9, adjust these instructions accordingly—though upgrading from Laravel 8 directly to Laravel 10 is possible, it often requires additional intermediate steps.

One may wonder: what if my application is on Laravel 8 or earlier? Generally, it’s safer to upgrade one major version at a time. If you’re on Laravel 8, consider upgrading to Laravel 9 first, then to Laravel 10. The Laravel documentation provides guides for each major version transition.

Step 2: Update Your PHP Version

As previously noted, Laravel 10 requires PHP 8.1.0 or higher. Before updating Composer dependencies, your local PHP environment must meet this requirement. Otherwise, Composer will refuse to install Laravel 10 packages.

Update your local PHP installation first. How you do this depends on your operating system:

  • macOS with Homebrew: brew upgrade php
  • Ubuntu/Debian: sudo apt update && sudo apt install php8.1
  • Windows with Chocolatey: choco upgrade php
  • Windows with WSL: Follow the Linux instructions for your distribution

After updating, verify:

php --version

You should see something like PHP 8.1.x or higher. Note that Laravel 10 also works with PHP 8.2 and PHP 8.3—we recommend using the latest stable PHP 8.2 for best performance and feature set.

On your production server, you’ll need to coordinate PHP upgrades separately. This guide focuses on the code-level changes; server configuration is beyond our scope, though we note that you should upgrade production PHP before deploying your upgraded Laravel 10 code.

Step 3: Update Core Framework Dependencies

Now we’ll update your composer.json file. The core change is the laravel/framework dependency. Open your composer.json and look for the laravel/framework entry. Change it from a Laravel 9 constraint to ^10.0:

"require": {
    "php": "^8.1",
    "laravel/framework": "^10.0",
    ...
}

Of course, you’ll also need to adjust the php requirement to ^8.1 if it’s still set to ^8.0.

But upgrading the framework alone isn’t enough—you also need to update other Laravel-related packages. Let’s examine the common ones.

Updating First-Party Laravel Packages

Laravel’s first-party packages maintain semantic versioning aligned with the framework. Here are the packages you’re likely to have and their Laravel 10-compatible versions:

PackageLaravel 9 VersionLaravel 10 Version
laravel/passport^10.0^11.0
laravel/sanctum^3.0^3.2
laravel/scout^9.0^10.0
laravel/telescope^4.0^4.15
laravel/horizon^5.0^5.20
nunomaduro/collision^6.1^7.0
spatie/laravel-ignition^1.0^2.0

If you use any of these, update their version constraints. For example:

-        "laravel/sanctum": "^3.0",
+        "laravel/sanctum": "^3.2",

Strictly speaking, you may not need to update all of these immediately—some packages maintain backward compatibility. However, it’s best practice to bring all Laravel-related packages to versions officially tested with Laravel 10.

Third-Party Package Considerations

Third-party packages may have their own version constraints. Some package authors update their packages promptly; others may lag. Before upgrading, we recommend:

  1. Run composer why-not laravel/framework ^10.0 to see which packages block the upgrade.
  2. Review each conflicting package to determine if updates are available.
  3. If a critical package has no Laravel 10-compatible version, you may need to:
    • Find an alternative package
    • Temporarily remove the feature
    • Contribute a compatibility patch upstream (if open source)

We’ll discuss handling package conflicts further in the troubleshooting section.

Step 4: Install the Updated Dependencies

With your composer.json updated, it’s time to install the new versions. Run:

composer update

This command resolves dependencies and installs the latest versions allowed by your version constraints. You can add the --with-dependencies flag to ensure all nested dependencies are also updated according to their constraints:

composer update --with-dependencies

The update may take several minutes, depending on your package count and network speed. You’ll see output like:

Updating dependencies (including require-dev)                                                                 
Packageoperations: 23 installs, 0 updates, 0 removals
  - Installing laravel/framework (v10.48.4): Downloading (100%)         
  - Installing nunomaduro/collision (v7.10.0): Downloading (100%)       
  ...

Once complete, your composer.lock file will be updated with the new versions. Commit this file to preserve the exact versions.

Step 5: Verify Installation and Check for Immediate Issues

Let’s verify that Laravel 10 is now installed:

composer show laravel/framework | grep version

You should see something like versions : * 10.*. At this point, your application may still have compatibility issues, but at least the framework itself has been upgraded.

Before proceeding, we recommend running Laravel’s built-in compatibility checker (if you have the laravel/pint or larastan packages installed) or simply checking for immediate autoload errors:

php artisan --version

This should output something like Laravel Framework 10.x.x. If you see errors about missing classes, a package may be incompatible—we’ll troubleshoot this shortly.

Step 6: Address Breaking Changes and Deprecations

Laravel 10 removes several features that were deprecated in Laravel 9. We’ll cover the most common ones; you should also review the official Laravel 10 upgrade guide for a complete list.

Removed Methods and Properties

The following Laravel 9 deprecations were fully removed in Laravel 10:

  • Route::home() method (use a named route instead)
  • Illuminate\Support\Arr::undot() (use native methods)
  • Model::removeGlobalScopes() (use other methods)
  • filesystem.getVisibility() and filesystem.setVisibility() (use direct permission checks)

Example: Replacing Route::home

If your codebase uses Route::home()—a helper that returns the URL to your home route—you’ll need to replace it. The recommended approach is to define a named route for your home page:

// In routes/web.php
Route::get('/', function () {
    return view('welcome');
})->name('home');

Then, wherever you used Route::home(), replace with route('home'):

- redirect()->to(Route::home());
+ redirect()->route('home');

This change is relatively direct, but it does require searching your codebase for Route::home occurrences. You can find them with:

grep -r "Route::home" app/ routes/ resources/

Doctrine DBAL No Longer Included

Laravel 10 removed the doctrine/dbal package from its default dependencies. This package was previously included for advanced database schema operations like renaming columns. If your application uses schema builder operations that require DBAL—specifically, change(), renameColumn(), or dropColumn() in migrations—you’ll need to install it separately:

composer require doctrine/dbal

You can check whether you need DBAL by searching your migrations:

grep -r "->change()" database/migrations/
grep -r "->renameColumn()" database/migrations/
grep -r "->dropColumn()" database/migrations/

If you find matches, installing DBAL resolves these. Of course, if none of your migrations use these methods, you can skip this step.

Validation Rule Changes

The dates validation rule, which validated that a value was a valid date representation, was removed in Laravel 10. Use the date rule instead:

- $request->validate(['date_field' => 'required|dates']);
+ $request->validate(['date_field' => 'required|date']);

The dates rule accepted non-string values like DateTime objects; date validates that the value is a valid date string. If you were relying on the non-string behavior, you may need additional validation logic.

Other Notable Breaking Changes

Several other changes affect specific use cases:

  • Route caching changes: The route:cache command now uses a more efficient caching mechanism. If you previously used route caching in production (common in larger applications), you should clear and rebuild your route cache after upgrading:

    php artisan route:clear
    php artisan route:cache

    You may notice this step is quick—it’s one of the performance improvements in Laravel 10.

  • Model factory definitions: If you use model factories, the way you define factories changed from class-based to Factory classes with define() methods in Laravel 9, and further refined in Laravel 10. Review your factory definitions against the Laravel 10 documentation; you may need to update how you structure them.

  • Minor helper changes: Several helper functions were removed, including str_replace_start(), str_replace_end(), and str_starts_with(). Most are now available through the Str facade or as PHP 8 string functions. Code using these removed helpers will fail.

  • Middleware signatures: Some middleware method signatures changed. If you have custom middleware extending Laravel’s base classes, check that method parameters match the new signatures.

This list isn’t exhaustive—you should review the full Laravel 10 upgrade guide. In our experience, though, these are the most commonly encountered issues.

Step 7: Search Your Own Code for Deprecated Patterns

Beyond the framework’s changes, your own application code may contain patterns that worked in Laravel 9 but break in Laravel 10. We recommend a systematic sweep:

  1. Search for removed methods: Use grep or your IDE’s search to find usages of methods known to be removed. The table above gives you a starting point. You can also look for deprecation warnings if your Laravel 9 application had debug mode enabled—those warnings point directly to code that needs updating.

  2. Check config files: Configuration files sometimes reference old class names or configuration keys that changed. Pay special attention to config/app.php, config/database.php, and any custom service provider registrations.

  3. Review custom service providers: The register() and boot() methods in your service providers may contain code that relied on removed methods or properties.

  4. Check tests: Tests often contain assumptions about behavior that changed. If your tests reference specific error messages, class behaviors, or method outputs, they may need updates too.

Of course, manual code review has limits—it’s easy to miss something. That’s why having a test suite is valuable; running tests after the upgrade will catch many issues automatically.

Step 8: Clear All Caches

After making code changes and updating dependencies, clear Laravel’s caches to ensure the new code is loaded:

php artisan optimize:clear

This single command clears multiple caches:

  • Application cache
  • Configuration cache
  • Route cache
  • View cache
  • Event cache

If you use route caching in production, you should rebuild it after clearing:

php artisan route:cache

We also recommend clearing Composer’s autoloader cache to ensure all class mappings are fresh:

composer dump-autoload

Step 9: Run Your Test Suite

Now we arrive at the most critical step: running your tests. This is your safety net—the primary way to verify that the upgrade didn’t break anything.

Run your full test suite:

php artisan test

Or, if you prefer PHPUnit directly:

./vendor/bin/phpunit

Watch the output carefully. Any test failures indicate areas where your application behavior no longer matches expectations. These failures may be due to:

  • Deprecated method calls you missed
  • Changes in Laravel’s default behavior
  • Third-party packages that aren’t compatible yet
  • Your own code that relied on undefined behavior (which happens to work in one version but not another)

Fix failing tests systematically. Start with the most fundamental failures—autoload errors or framework-level issues—before moving to functional tests. Sometimes a single missing class or interface can cause dozens of test failures.

If you don’t have a test suite, now is the time to run your application manually and exercise key user flows: logging in, performing CRUD operations, running queued jobs, etc. Consider adding at least a few tests before upgrading a production application, though we recognize this isn’t always feasible.

Step 10: Update Your Production Deployment Process

Assuming all tests pass (or you’ve manually verified the application works), you’re ready to deploy to production. But before you do, ensure your deployment process accounts for the Laravel 10 changes:

  • Composer should install Laravel 10 dependencies: Verify your production deployment runs composer install (not composer update) after pulling the new code, and that the updated composer.lock is deployed.

  • PHP version on production must be 8.1+: As noted earlier, Laravel 10 won’t run on PHP 8.0. Coordinate with your hosting provider or server administrator to upgrade PHP if needed. This may require separate maintenance windows.

  • Artisan commands after deployment: Run the cache-clearing commands we mentioned:

    php artisan optimize
    php artisan config:cache
    php artisan route:cache

    These optimize the application for production.

  • Database migrations: If your upgrade involved database changes (unlikely for Laravel 9→10 specifically, but possible if you’re also making app changes), run migrations:

    php artisan migrate

Deploy during a low-traffic period, and have a rollback plan ready: keep the previous release directory (if using zero-downtime deployments) and be prepared to roll back if critical issues emerge.

Step 11: Monitor After Deployment

After deploying, monitor your application closely for at least 24 hours. Watch for:

  • Increased error rates in your logs or monitoring system
  • Slow query times or database errors
  • Queue failures if you use queues
  • Email delivery issues if you send emails
  • Third-party API failures if your app integrates with external services

If you discover a compatibility issue you missed, you may need to roll back and address it before trying again.

Troubleshooting Common Issues

Even with careful preparation, you may encounter problems. Let’s address common scenarios.

Composer Resolution Failures

If composer update fails with messages about conflicts or unmet requirements, you have a few options:

  1. Identify the blocking packages: Run:

    composer why-not laravel/framework ^10.0

    This shows which packages prevent Laravel 10 from being installed.

  2. Update or replace problematic packages: Often, the solution is to update to a newer version of the conflicting package. If no compatible version exists, you may need to find an alternative package or temporarily disable the feature.

  3. Use composer update with specific packages: Sometimes, updating packages individually helps:

    composer update laravel/framework nunomaduro/collision --with-dependencies

    Then address remaining packages separately.

  4. Clear Composer’s cache: Corrupted cache can cause false conflicts:

    composer clear-cache

Runtime Class Not Found Errors

If after updating you see Class 'X' not found errors when running Artisan commands, you likely have a package that’s not yet compatible with Laravel 10. The class may have been removed or renamed.

To troubleshoot:

  1. Check the error message—what class is missing?
  2. Search your code and vendor directory for references to that class.
  3. If it’s from a third-party package, check if a newer version of that package exists.
  4. If no update exists, consider replacing the package or implementing the needed functionality yourself.

Test Failures After Upgrade

If tests fail after upgrading, systematic debugging helps:

  1. Run one test file at a time to isolate the problem:

    php artisan test --filter=UserTest
  2. Read the failure message carefully. It often points directly to the problematic code—either your test expectations need updating, or your application code needs to change.

  3. Check for database schema changes: Laravel 10 may change default column types or behaviors in migrations. If your tests use an in-memory SQLite database, some types may not map correctly. Consider using the same database driver in testing as in production.

  4. Don’t ignore failures. Each failing test represents a potential production issue.

Performance Degradation

If your application feels slower after upgrading—unlikely, but possible—start by checking:

  • Route caching: ensure you ran php artisan route:cache in production
  • Config caching: php artisan config:cache
  • OPcache settings: PHP 8.1 may require different OPcache tuning
  • Database query counts: Laravel 10’s eager loading behavior may have subtle changes

Benchmark your key pages before and after using tools like Clockwork or Laravel Telescope to identify regressions.

When All Else Fails

If you’ve exhausted troubleshooting and still have issues, consider:

  • Starting fresh with a new Laravel 10 installation and gradually porting your code
  • Engaging Laravel community forums (https://laravel.io/forums) with specific error messages
  • Hiring a Laravel specialist for a few hours of troubleshooting—sometimes an external perspective helps

Remember: upgrading is rarely a one-day task for complex applications. Allocate time for research, testing, and iteration.

Conclusion and Next Steps

Upgrading from Laravel 9 to Laravel 10 involves updating your PHP version, modifying Composer dependencies, addressing breaking changes, and thorough testing. You’ve now seen each step in detail, along with approaches for handling common problems.

What we’ve presented here covers the essential workflow. After completing these steps and deploying to production, we recommend:

  • Keep your Laravel 10 application on the latest 10.x releases for security patches
  • Start planning for the next Laravel upgrade—Laravel 11 was released in March 2024, and Laravel 12 will follow in 2025
  • Continue maintaining a robust test suite; this makes future upgrades much easier

Laravel’s predictable release cycle means upgrades are a normal part of application maintenance. By approaching them methodically—understanding the changes, making deliberate updates, and verifying thoroughly—you can keep your application current without undue risk.

Sponsored by Durable Programming

Need help with your PHP application? Durable Programming specializes in maintaining, upgrading, and securing PHP applications.

Hire Durable Programming