Upgrading from Drupal 10 to Drupal 11: what actually changes
Drupal 11 is a far smaller step than the Drupal 7 to 9 migration was. Here is what the upgrade touches, what breaks, and the order to do it in.
Custom modules are where Drupal projects accumulate their long-term cost. A few conventions keep them readable and upgradeable.
The first question is whether a contributed module already solves this. The second is whether the requirement is genuine or a workaround for a content-model problem. A surprising share of custom modules exist to patch over decisions that would be cheaper to fix upstream.
When custom code is the right answer, keep the module narrow and named for what it does rather than for the project or the client.
Drupal provides services for almost everything a module needs: entity queries, the database abstraction layer, the cache backend, the queue system, configuration and state storage. Reaching around them — raw SQL against core tables, direct file writes, global state — is what makes a module fail at the next major version.
Inject dependencies through the service container rather than calling \Drupal::service() throughout. It is marginally more code and makes the module testable.
These three are distinct and are regularly confused. Configuration is deployable and belongs in version control. State is runtime data that does not deploy, such as a last-run timestamp. Settings are per-environment values such as credentials, which belong in settings.php or environment variables, never in exported configuration.
Getting this wrong is how API keys end up committed to a repository.
Write at least a kernel test for the module's core behaviour. It does not have to be exhaustive; it has to catch the case where a future upgrade silently changes something.
Run PHPStan with the Drupal extension in your pipeline. It reports deprecated API usage early, which converts the next major upgrade from an investigation into a task list.
The most common structural mistake is the catch-all module — mysite_custom — that accumulates every piece of bespoke behaviour on the site. After two years it contains form alters, a block, three services, a migration and some theme preprocessing, and nobody can safely remove any of it.
Split by capability instead. A module that provides an integration with one external system, a module that provides one content feature, a module that provides shared utilities. Each has a clear reason to exist and can be removed when that reason expires.
Name modules for what they do rather than for the project or client. zbd_events tells the next developer nothing; event_registration tells them everything they need to decide whether to open the file.
Usually yes, under web/modules/custom , versioned with the site. Splitting a module into its own repository is worth it only when it is genuinely reused across several projects, because the overhead of separate versioning and release management is real.
Enough to catch silent breakage at the next upgrade. A kernel test covering the module's central behaviour is usually the highest-value first test. Exhaustive unit coverage of glue code rarely repays the effort.
Yes, when the patch comes from the module's issue queue and is applied through Composer so it is recorded and re-applied on every build. Editing files in place inside the vendor directory is not acceptable, because the change disappears at the next update.
Keep reading
Drupal 11 is a far smaller step than the Drupal 7 to 9 migration was. Here is what the upgrade touches, what breaks, and the order to do it in.
Most difficult Drupal builds are content-modelling failures, not code failures. How to model entities, fields and taxonomies so a site stays maintainable.
Drupal has a strong security team and a predictable advisory process. The risk on most sites is not the platform, it is that nobody is watching the advisories.