Why there is no upgrade button
Drupal 7 was the last release built on Drupal's own procedural architecture. Drupal 8 replaced it with Symfony components, an object-oriented plugin system, Twig templates and configuration management. Nothing carries across untouched.
That break has one useful consequence and one expensive one. The useful one is that once you are on Drupal 8 or later, major upgrades are ordinary maintenance: Drupal 9 to 10 to 11 is a composer update, a deprecation sweep and a test run. The expensive one is that getting from 7 to 11 is not an upgrade at all. You build a new site, then move the content into it with the Migrate API.
So the honest framing for a client is this: you are buying a new Drupal site, and roughly a fifth to a third of the budget is the cost of bringing the old content with you. Once you say that out loud, the estimate stops being a guess and becomes an inventory.
The deadline that actually matters. Drupal 7 reached end of life on 5 January 2025, so it has had no community security support for over a year and a half. Commercial extended support can be purchased, and for some organisations that is the right stopgap, but it is a rental, not a fix. Meanwhile Drupal 12 is scheduled for the week of 7 December 2026, and no new releases of Drupal 10 will be made after that date. If you are planning a migration now, the target is 11, not 10.
The five things that drive the number
1. Contrib modules with no Drupal 11 equivalent
This is the single largest source of variance between two sites that look identical from the front end.
How to count it: list the enabled modules on the live site, not the ones in the codebase. For each one there are four outcomes, and only the fourth costs real money:
- It exists for Drupal 11. Install it, configure it, move on. Most of the well-known ones are here.
- Core absorbed it. Media, WYSIWYG, layout, entity reference, views, date handling and workflow are all core concerns now. Half a dozen contrib modules on a typical Drupal 7 site disappear into core features, which is a saving, not a cost.
- It was replaced by something different. Field Collection becomes Paragraphs. Panels and Context become Layout Builder. These are not upgrades, they are migrations with a content model change in the middle, and they need a decision from someone who knows the site.
- Nothing exists. A niche module, an abandoned one, or something a previous agency wrote and never published. Now you are quoting a build, and it needs a specification before it needs a price.
Count the fourth category precisely. Twenty modules where nineteen are ports and one is a rebuild is a very different job from twenty where four are rebuilds. The temptation is to average it. Do not.
2. Custom code volume, and its shape
Lines of code are a weak proxy. What matters is how much of the custom code is business logic and how much is glue that core or a modern contrib module now does for free.
How to count it: read the custom modules and sort each one into rewrite, replace or delete. In my experience a meaningful share of a Drupal 7 custom module is glue: a block that a view now does, a form alter that a field setting now handles, a cron job that a queue worker should have been. Deleting those is fast. Porting them because nobody read them is slow and expensive.
Two specifics that are routinely underestimated:
- The theme layer is a rewrite, not a port. PHPTemplate to Twig, plus the preprocess layer, plus the asset libraries system. If the design is also being refreshed, this is where the two projects should be merged rather than run in sequence.
- Automated tooling handles syntax, not architecture. Deprecation scanners and automated rectors will take care of a lot of mechanical change on a Drupal 8 or 9 codebase. Going from Drupal 7 they help far less, because a
hook_implementation does not become a plugin class by find and replace.
3. Content model complexity
The Migrate API is genuinely good, and the standard migrations for nodes, users, taxonomy terms and files do a great deal of the work. The cost sits in everything that is not standard.
How to count it: export the field list. Every content type, every field, every field type. Then flag:
- Fields whose Drupal 7 type has no direct Drupal 11 equivalent, which means a process plugin and a decision about data loss.
- Field Collections, nested Field Collections especially, which become Paragraphs with all the revision semantics that implies.
- Multilingual content, which roughly doubles the migration build because Drupal 7 and Drupal 11 handle translation in fundamentally different ways.
- Files and media. Drupal 7 stored files; Drupal 11 wants media entities. The mapping is rarely one to one, and the file count is usually an order of magnitude larger than anyone expects.
- Path aliases and redirects, which are not glamorous but are the difference between keeping your search rankings and explaining to a client why traffic halved.
4. Whether Commerce is involved
If the site takes money, treat it as a separate project with its own budget line.
Ubercart or Commerce 1 to Commerce 3 is not a version bump. The entity model is different, the order workflow is different, and there is no supported path that carries historical orders, line items and payment transactions across cleanly. The usual answer is to migrate products, customers and a defined window of order history, and archive the rest as read-only data outside the new site.
Payment gateways get rebuilt in every case, because the Drupal 7 payment modules do not exist for Commerce 3. That is also the moment to re-take the PCI decision rather than reproduce whatever the old site did, since a hosted fields or redirect integration can keep the merchant at SAQ-A instead of SAQ-A-EP. I maintain two published CyberSource gateways on drupal.org, and that scope question comes up on every one of these jobs.
5. The integration and editorial surface
The last driver is everything that is neither code nor content: views to rebuild, editorial workflow, user roles and permissions, search, and any integration with a CRM, ERP, SSO provider or payment processor.
How to count it: count the views on the live site and count the integrations. Views are quick individually and slow in aggregate, and nobody remembers there are seventy of them until the fortieth one. Integrations are the opposite: few in number, and each one capable of consuming a week on its own if the remote end is old, undocumented or owned by a third party who has to be scheduled.
An estimating model you can actually apply
Here is the shape I use. It is a model, not a price list: the point is that each line is something you counted rather than something you felt.
| Phase | Typical share | What moves it |
|---|---|---|
| Discovery and module map | 5 to 10% | Fixed and small. Skipping it is what makes every other line wrong. |
| Site build: content types, fields, views, permissions | 20 to 30% | Number of content types and views; whether the content model is being redesigned at the same time. |
| Custom and contrib gap work | 10 to 40% | The count of modules in category four and the volume of genuine business logic. The widest band on the table. |
| Theme | 15 to 30% | Whether the design is being kept, refreshed or replaced. |
| Migration build and re-runs | 15 to 25% | Field type mismatches, multilingual, media, and how many times you re-run before launch. |
| QA, content checking and launch | 10 to 15% | Content volume and how much of it a human has to look at. |
Two rules that keep this honest. First, the migration is built to be re-run: you will run it a dozen times or more, and the final run happens on launch day against live data, so anything manual in that path is a defect. Second, discovery is quoted separately and delivered as a document. A fixed price for a migration written before anyone read the custom modules is not an estimate, it is a bet.
Rebuild or migrate: the decision most agencies face
Every one of these projects reaches a point where someone asks whether to run the Migrate API at all, or simply build the new site and copy the content across by hand. It is a legitimate question and the answer is usually decided by three numbers.
- Content volume. Below a few hundred nodes, a careful person with a checklist is often cheaper and always more predictable than a migration build. Above a few thousand, manual copying stops being viable.
- How much of the content is worth keeping. This is the question nobody asks, and it is the cheapest saving available. Pull the analytics for the last twelve months and count the pages with meaningful traffic. On an old site, a large share of the content typically has none. Migrating it costs real money in mapping, QA and re-runs, and it makes the new site worse.
- Whether the content model is sound. If the Drupal 7 field structure is a sensible model of the business, migrate it. If it is fifteen years of accumulated compromise, a migration faithfully reproduces the compromise on a platform you are meant to be modernising, and you will pay to redo it later.
The usual answer is a hybrid: migrate the high-value, high-volume content types with the Migrate API, redesign the models that need it, and hand-carry the small handful of landing pages that are really design work anyway.
Where the estimate overruns
Five places, in roughly the order they bite.
- Files and media. The file count is bigger than the estimate, a meaningful share of them are orphaned or missing from disk, and mapping them onto media entities involves decisions about alt text and media types that nobody made in 2011.
- URLs and redirects. Path aliases, old redirect rules, and the search rankings attached to them. This is cheap to do at the start and expensive to do after launch when traffic has already dropped.
- The module nobody mentioned. Something with data in its own tables, no Drupal 11 equivalent, and one person in the client organisation who depends on it daily. Always found in discovery if you look; always found in week six if you do not.
- Content clean-up promised for later. Duplicate taxonomy, half-finished drafts and inconsistent field usage. The migration exposes all of it, and the argument about who fixes it happens in the middle of the project instead of before it.
- Third-party scheduling. The payment provider, the SSO team, the CRM vendor. Your team can be ready and still lose two weeks.
None of these are technical surprises. They are all things that a week of discovery would have surfaced, which is exactly why the discovery phase should be a paid deliverable rather than a free part of the sales process.
What I would tell a client asking for one number
That the number depends on four counts they can get in an afternoon: how many enabled modules, how many custom modules, how many content types and fields, and whether the site takes payments. Any developer who gives a price without asking for those four things is pricing a different site.
And that the cost of waiting is not zero. A Drupal 7 site today is running unsupported code with a paid stopgap at best, on PHP versions that hosting providers are steadily retiring, with a shrinking pool of developers who remember how it works. Drupal 11 needs PHP 8.3 or newer, which for most Drupal 7 sites also means the server estate moves at the same time.
If you want this done
I take Drupal 7 to 11 migration work as fixed-price pieces for agencies: module and custom-code porting, migration builds, and PHP 8.x upgrades priced per site, delivered under your name. That is set out on the white-label Drupal for agencies page, and the end-of-life background for site owners is on the Drupal 7 support page.
If what you actually need first is the count rather than the build, a fixed-price site audit produces the module map, the custom code inventory and the content model review as a written document you can take to any developer, including one who is not me.
