First, know exactly where you are starting from
Every honest upgrade plan starts with an inventory, not a command. Record your current Laravel version, your PHP version everywhere it runs (local machines, continuous integration, staging and production), your database version, and every Composer package with its current version. Run a simple outdated check and note which packages are abandoned, unmaintained, or silent about supporting newer Laravel versions.
This sounds basic, and it is where most surprises are found cheaply. An application described as “a bit behind” sometimes turns out to be several major versions behind, on a PHP version that the target Laravel release no longer supports, with two or three packages that will decide the whole shape of the work. Finding that out in an afternoon of reading is very different from finding it out halfway through a failed update.
Write the inventory down. It becomes the first page of the upgrade plan, the thing you estimate against, and the baseline you compare with at the end.
Why staying put stops being the safe option
Older Laravel versions feel stable because nothing changes. That stability is real, right up until it is not. Laravel follows a predictable policy: each major release receives bug fixes for a limited period and security fixes for a longer one, and after that the patches simply stop. The Laravel release cycle and support dates are public, so you can see exactly where your version sits without guessing.
In practical terms, that means an application on an end-of-life version is not standing still; it is accumulating unpatched risk while the ecosystem moves on. Packages drop support for old versions, hosting platforms raise their minimum PHP versions, and new hires increasingly expect a current stack. None of these forces arrives with a warning letter. They arrive as a package you cannot install, or a security issue you cannot patch.
The calm conclusion is not to upgrade in a panic. It is to upgrade on a schedule you choose, while you still have a security window to work inside. A planned upgrade done in stages is routine work; the same upgrade forced by an incident is not.
Upgrade one major at a time, and understand why
The most useful rule in any Laravel upgrade guide is also the least exciting: move one major version at a time. If you are on Laravel 10 and the current release is several versions ahead, your path is a sequence of smaller upgrades, each with its own upgrade guide, its own list of breaking changes, and its own green test run before you proceed.
This is not ceremony for its own sake. Each major version's changes are documented against the previous one. When you follow them in order, a failure points at one known set of changes. When you jump, the same failure could belong to any of several versions, and the time you saved by skipping steps comes back, with interest, as debugging time.
Stopping at each major version also gives you checkpoints where the application is in a known, working state. If priorities change mid-project, you can pause on a version that runs, rather than being stranded half-upgraded.
PHP usually moves before Laravel does
Major Laravel releases regularly raise the minimum PHP version, so for many older applications the first real task is not Laravel at all. It is PHP. Check the PHP requirement of your target version, compare it with what your servers and your local setup actually run, and treat any gap as a separate, earlier step in the plan.
Wherever possible, get the application running on the newer PHP version while it is still on the older Laravel version, and fix what surfaces there first. Deprecation warnings and small incompatibilities are much easier to read when only one thing has changed. If you change PHP and Laravel in the same afternoon, every new error has two possible parents.
Remember to move every environment together: local development, your test pipeline, staging and production. An upgrade that works on a developer's machine and fails in the pipeline has not failed mysteriously; it has found a PHP version difference you had not written into the inventory.
Packages decide whether the upgrade is easy or painful
In our experience, the framework itself is rarely the hard part of an older application's upgrade. The packages are. Authentication scaffolding, admin panels, payment integrations, reporting tools and media handling all carry their own Laravel version requirements, and an abandoned package has no version that supports your target at all.
For each package in your inventory, decide one of three things: upgrade it to a version that supports both where you are and where you are going, replace it with a maintained alternative, or bring the small piece of functionality it provides into your own codebase. Make those decisions before you touch the framework version, because they determine the order of everything else.
Pay particular attention to anything that touches money, files or personal data. Those integrations deserve the closest re-testing after each step. This is where a Laravel development service that works on existing applications earns its keep: not in running the update, but in knowing which package decisions will hurt later.
Work through the breaking changes, then prove it with tests
Each major version publishes an upgrade guide that lists its breaking changes and rates how likely each one is to affect a typical application. Work through the relevant guide literally, item by item, searching your codebase for the patterns it names. Most items will not apply to you; the value is in knowing that, rather than hoping it. The Laravel 12 release notes show how recent releases describe this approach, with an emphasis on minimising breaking changes.
Then let the tests speak. Run the full suite after each major version, not just the files you touched, and get it green before moving on. If your suite is thin, this is the moment to strengthen it around the flows that matter most commercially. We covered what to look for in that safety net in our buyer's checklist for choosing a Laravel development company, because upgrade discipline is one of the clearest signs of a team that maintains software properly.
Automated tools can draft some of the mechanical changes for you. Treat their output the way you would treat a junior developer's first pass: useful, welcome, and read line by line before it is trusted. The tool does not know which behaviour your customers depend on. You do.
Config, queues and the quiet things that break silently
The failures that make upgrades memorable are rarely the loud ones. A loud failure stops a deployment and gets fixed. A quiet one lets the deployment succeed while something in the background changes behaviour: a queued job that no longer retries the way it did, a scheduled task that stops running, a cache that starts missing, or a configuration default that differs between your files and a fresh installation of the new version.
So build a short, deliberate checklist for the quiet layer. Compare your configuration files with the new version's defaults and note every difference you intend to keep. Send every important queued job through staging and watch it complete. Trigger the scheduler's key tasks by hand. Check that sessions, logins and password resets behave normally, and that emails your users rely on are actually sent, not merely generated.
None of this is glamorous, and all of it is cheaper on staging than in production. The applications that upgrade smoothly are not lucky; they are the ones whose teams wrote this list before they needed it.
A safe go-live is a plan, not a hope
By go-live, most of the work should already be proven. Deploy to staging with data that resembles production, run your critical paths by hand as well as through the test suite, and watch the logs for new warnings while real-looking work flows through the system. Background work deserves a second look here, because queues and scheduled jobs are where upgrades most often fail quietly after an otherwise clean release.
Agree the rollback before you need it. Know what you would restore, in what order, and what happens to data written after the upgrade if you do roll back. A rollback you have rehearsed is a safety net; one invented at midnight is a second incident.
Finally, keep feature work out of the upgrade release. An upgrade that also ships three new features gives every post-release problem two possible causes. Ship the upgrade as its own, boring, well-watched release, then return to features on a stack you can now patch, extend and hire for.
A practical checklist you can hand to your team
Before you start: write down your Laravel, PHP and database versions in every environment, list every Composer package and its support status, confirm a restorable backup and a staging copy, and make sure your existing tests pass. During the upgrade: move PHP first where required, resolve blocking packages, then go one major version at a time, reading that version's upgrade guide in full and getting the whole test suite green before the next step.
Before go-live: compare configuration with the new defaults, exercise queues, scheduled jobs, logins, file handling and key integrations on staging, agree and rehearse the rollback, and ship the upgrade without unrelated features. After go-live: watch logs and failed jobs closely for the first days, and put the next upgrade in the calendar while this one is still fresh, so the application never again drifts several versions behind.
If that list feels manageable, your application is probably in better shape than you feared, and the upgrade is routine work done carefully. If several items are unknowns, start with the audit and let the findings set the scope. Either way, an older Laravel application is not a liability to apologise for. It is a working asset, and a planned upgrade is simply how you keep it one.
