Drupal upgrades have a reputation built largely on one painful transition. Moving from Drupal 7 to Drupal 8 meant rebuilding a site, and that experience still shapes how people budget for upgrades a decade later.
Modern Drupal is different. Going from Drupal 10 to 11 is a dependency and deprecation exercise, not a rebuild. But it is not automatic either, and the sites that suffer are almost always the ones that let several versions pass while assuming the work would stay the same size.
This page describes what a modern Drupal upgrade actually involves, what determines whether yours takes days or months, and how to find that out before committing to a date.
The Two Kinds of Upgrade People Mean
"Upgrade" gets used for two very different jobs, and conflating them produces bad estimates.
- A minor update — moving within a major version, or applying a security release. Usually a Composer update, a database update, and a test pass. This should be routine and frequent.
- A major version upgrade — moving from one major to the next. This requires every contributed module to have a compatible release and every piece of custom code to have removed the APIs that were dropped.
A third case still exists: sites on Drupal 7 or earlier. That is not an upgrade in the modern sense — the architecture changed fundamentally, and the honest description is a rebuild that reuses your content, not a version bump. Anyone describing it as a routine upgrade has not looked at the site.
Why Upgrades Got Easier, and Where They Still Bite
Since Drupal 8, core has followed a pattern that makes major upgrades tractable: APIs are deprecated with a replacement available for at least one full major version before removal. Code written against current APIs, kept current, moves forward with modest effort.
The catch is that this only works if you actually move. Deprecations accumulate silently — code that uses a deprecated function keeps working perfectly, logging nothing a normal user would see. Three years later the function is gone, and all that deferred work arrives in one release.
The other pressure is contributed modules. Core can be ready and your site still cannot move, because a module you depend on has not been updated. That is not a Drupal problem so much as a dependency problem, and it is the single most common reason an upgrade slips.
Start With an Inventory, Not With Code
Every upgrade should begin with a written answer to four questions:
- Which contributed modules are installed, at what versions, and does each have a release compatible with the target?
- Which are unmaintained, and what would replace them?
- Which custom modules exist, what do they do, and who understands them?
- Which core APIs does the custom code use that the target version removes?
That inventory is the estimate. Everything after it is execution. Skipping it and starting with composer update produces a broken site and no information about how far from done you are.
Contributed Modules Decide the Timeline
For each contributed module the site depends on, there are four outcomes: a compatible release exists; one is in progress; the module is abandoned and something else must replace it; or its functionality is now in core and it can be dropped.
The third case is where time disappears. Replacing a module means migrating whatever configuration and content it owned, which can be substantial. Finding that out during the upgrade rather than before it is what turns a two-week job into a two-month one.
There is a fourth option people forget: a module may only need a patch. Many modules are compatible in practice and blocked only by a version constraint in their .info.yml. A tested patch, tracked in composer.json, is often the fastest honest path — and contributing it upstream means you stop maintaining it yourself.
Deprecations and How to Find Them
Deprecated code is discoverable through static analysis rather than guesswork. The Upgrade Status module reports readiness across the whole site, and PHPStan with the Drupal extension finds deprecated API use in custom code.
composer require --dev mglaman/phpstan-drupal phpstan/phpstan-deprecation-rules
vendor/bin/phpstan analyse web/modules/custom web/themes/customThe output is a list of exactly what has to change and where. Some of it can be automated — Drupal Rector applies mechanical fixes for many common deprecations — but it should be reviewed rather than trusted blindly. Automated refactoring is excellent at the repetitive cases and occasionally confident about something subtly wrong.
The Change That Fatals Rather Than Warns
Most deprecations degrade gently: the code works, a notice is logged, and you fix it when convenient. Plugin discovery is the exception worth planning for specifically.
Drupal has been moving plugin discovery from docblock annotations to PHP attributes. While a plugin manager supports both, a class carrying only an annotation keeps working. When annotation support is removed, that same class produces a fatal error rather than a warning.
The practical consequence is that this cannot be discovered gradually in a log. It surfaces the moment the code runs on the new version, and if you maintain several sites, it surfaces on all of them at once. Adding attributes to plugin classes while keeping the annotations is the safe order: newer core reads the attribute, older core reads the docblock, and both work during the transition.
Custom Themes Carry Upgrade Debt Too
Upgrade planning usually focuses on modules, and themes get checked last. They deserve equal attention, because template overrides copied from an older core version keep rendering that older version's markup indefinitely.
That is not always harmful, but it means accessibility fixes, structural improvements and new template variables introduced in core never reach your site. Over three major versions the gap becomes substantial, and it is invisible — the page renders, so nothing looks wrong.
Preprocess functions are the other theme-layer risk, since they call the same PHP APIs custom modules do and are deprecated on the same schedule.
Configuration and Database Updates
After the code is in place, Drupal still has work to do: database updates that alter schema and configuration updates that adjust settings to a new version's expectations.
drush updatedb
drush config:import
drush cache:rebuildThe order matters, and so does having a verified database backup before the first of them. Database updates are not reversible in any practical sense — the recovery path is restoring a snapshot, which means the snapshot has to exist and to have been tested. A backup nobody has restored is a belief, not a backup.
Testing an Upgrade Before It Is Live
An upgrade needs a rehearsal on a copy of production — the real database, the real content, the real configuration. A clean development site with a hundred test nodes will not surface the problems that matter, because those problems live in real content: the unusual paragraph nesting, the field that was deprecated three years ago and still has values, the view with a custom handler.
Worth exercising specifically: content editing for each content type, every form that submits somewhere, search, any integration that talks to an external service, and the pages that get the most traffic. Automated tests help if they exist, but the highest-value check is a person clicking through the paths that actually earn money.
Doing It Without a Long Freeze
The failure pattern is a long-lived upgrade branch: work starts, the site keeps changing, and the branch drifts until merging it is its own project.
The alternative is to do as much as possible in advance on the current version. Removing deprecated API use, updating modules to their latest compatible releases, and adopting forward-compatible patterns can nearly all be done on the version you are running today, shipped incrementally as normal work. By the time the major upgrade happens, it is a version bump against code that is already ready.
This is slower in total effort and dramatically less risky, because each step is small enough to reason about and to roll back.
If you would like an outside view of how far your own site is from its next major version, an upgrade assessment answers that without touching the site. Request a quote with your current Drupal version.
Common Upgrade Mistakes
- Starting with
composer updateinstead of an inventory, and learning the scope by breaking things. - Skipping the rehearsal on real data, so production is where the unusual content is discovered.
- No verified backup before database updates, which removes the only rollback that exists.
- Treating a Drupal 7 site as an upgrade. It is a rebuild, and pricing it as an upgrade guarantees the project goes wrong.
- Upgrading and redesigning at once, so when something breaks there is no way to tell which change caused it.
- Leaving contributed modules unchecked until the end, when an abandoned one can invalidate the whole plan.
What Good Upgrade Hygiene Looks Like
Sites that upgrade easily are not lucky; they run a small amount of routine maintenance. Minor updates get applied monthly rather than annually. Deprecation reports run as part of development, so the count trends toward zero instead of accumulating. Contributed modules are reviewed periodically for whether they are still maintained and still needed — the cheapest module to upgrade is the one you removed because nothing used it.
None of this is difficult. It is simply work that has no deadline, which is why it does not happen without a standing arrangement.
Drupal 7 and 8: This Is a Migration, Not an Upgrade
Everything above describes a modern major upgrade — Drupal 9 to 10, 10 to 11 — where the codebase moves forward and the database comes with it. A Drupal 7 site does not work that way, and anyone who tells you it is the same exercise has not priced it honestly. Drupal 7 reached end of life on 5 January 2025. It receives no security coverage from the Drupal Security Team, and the gap widens every month the site stays there.
The reason it is a migration rather than an upgrade is architectural. Drupal 8 rebuilt the system on Symfony, replaced the hook-and-array model with an object-oriented entity and plugin API, moved configuration out of the database into YAML, and replaced PHPTemplate with Twig. Almost nothing in a Drupal 7 custom module or theme survives contact with that. The content survives; the code does not.
What actually moves, and what gets rebuilt
Content migrates through Drupal core's Migrate API, which reads the Drupal 7 database directly and writes entities into the new site: nodes, users, taxonomy terms, files, and the field values attached to them. That part is mechanical and repeatable, which is what makes it estimable. The work that is not mechanical is everything around it — the content model, the theme, and any custom module. Those are rebuilt against current APIs, and the rebuild is where the hours are.
The content model is the decision that determines the cost, and it is worth making deliberately rather than by default. A Drupal 7 site has usually accumulated a decade of fields that no longer earn their place, content types that overlap, and taxonomy that nobody has pruned. Migrating all of it faithfully is the expensive option and preserves the mess. Deciding what to carry across first is cheaper and produces a better site — but it is a content decision, not a technical one, so it needs the people who own the content in the room.
How the estimate is produced
The same way as any other upgrade on this page, and for the same reason: a written inventory first. Every contributed module gets checked for a current release, a replacement, or core equivalence — a great many Drupal 7 contrib modules became unnecessary because core absorbed them. Every custom module gets read and sized. Every content type and field gets listed with its row count, because volume changes the migration approach. Only then is there a number worth quoting.
The migration itself is rehearsed repeatedly against a copy of the real database, not built once and hoped over. Each rehearsal is rerunnable, so content added to the live Drupal 7 site during the build is picked up at cutover rather than re-entered by hand. That is what lets an old site keep running and keep publishing while its replacement is assembled.
If you are running Drupal 7 or Drupal 8 today and want to know what moving off it actually involves for your site specifically, that inventory is the first piece of work, and it is the piece that tells you whether the rest is worth doing.
When to Bring in Help
A team comfortable with Composer and PHP can often handle a straightforward upgrade themselves, and should — it builds knowledge that pays back at the next one.
Help is worth it when the site carries custom modules nobody currently on the team wrote; when a dependency is abandoned and needs replacing; when the site is more than one major version behind; when it cannot afford downtime and needs a rehearsed cutover; or when a previous attempt has already failed and the codebase is now in an uncertain state.
That last one is common and worth saying plainly: a half-finished upgrade is harder to work with than an un-started one, because the first job is establishing what state things are actually in.
Estimating an Upgrade Honestly
Upgrade estimates go wrong in a consistent way: the version bump gets estimated and everything around it does not.
The code change is often the smallest part. What fills the time is the contributed module that needs replacing, the custom module nobody understands, the rehearsal on real content, the fixing of what the rehearsal finds, the second rehearsal, and the cutover itself — plus the testing that has to happen after, because an upgrade touches everything at once.
A defensible estimate separates these. The inventory is a fixed, small piece of work and should be priced as such, because it is what makes the rest estimable. Custom code remediation can be estimated once the deprecation report exists. Contributed module work depends entirely on what the inventory found, and if a dependency is abandoned, that is a distinct project with its own scope.
Anyone quoting a major upgrade without having looked at the module list is guessing, and the guess will be low — because the expensive cases are invisible from the outside. A site with forty contributed modules and no custom code can be straightforward. A site with eight modules and one large bespoke integration can be considerably harder.
The practical implication for planning: commission the inventory first as its own small piece of work, then decide. It converts an unknown into a number, and it is useful even if you then do the upgrade yourself or postpone it. What it prevents is committing to a date before anyone knows what is behind the door.
Next Steps
If you want to know where you stand before committing to anything, install Upgrade Status on a copy of your site and read its report. It will tell you which modules are ready, which are not, and roughly how much custom code needs attention. That report is most of an upgrade plan.
If you would rather have it done: an upgrade assessment produces a written inventory, a risk list and an estimate without touching your site; a defined upgrade takes you to the target version with a rehearsed cutover; and ongoing maintenance keeps minor updates and deprecations from accumulating again.
I have been in web development since 2005 and I write and maintain Drupal modules and themes of my own; they are listed on my drupal.org profile.
Request a quote with your current Drupal version and roughly how many contributed and custom modules you run, or get in touch to discuss timing. Related reading on the engineering approach is on the Drupal developer page.