profile_updates
"Profile Updates" lets site administrators review the optional configuration updates shipped by an install profile (or its modules) and decide — one by one, or in bulk — which ones to apply. Unlike a forced hook_post_update_N(), applying a profile update is always a deliberate action taken in the admin UI (or via Drush); nothing runs automatically on drush updb / drush deploy:hook.
The problem it solves
Distributions hit this the moment they have real clients on real sites: not every site wants every profile change. Sites carry local customizations, and forced update hooks can overwrite site-specific work — yet the site owner has no say in the matter. Profile Updates gives them one, while still letting the distribution ship a steady stream of improvements.
How it works
- A developer changes configuration and ships an update task: a small YAML file naming the changed config item(s), with a human-readable label and description.
- Tasks are auto-discovered as plugins from
EXTENSION/update_tasks/*.yml, re-scanned on every cache clear — no import step, no stale registry. - Administrators with the Administer profile updates permission open Configuration » Development » Profile updates and see what actually needs attention on this site.
- They Apply (through the Batch API, so it scales) or Skip updates. Every choice is recorded as an audit-trail entry that stays readable even if the originating task later changes or disappears.
Per-site, computed state
Each update's state is worked out live by diffing the shipped config against the site's active config and consulting the log:
- Pending — shipped config differs from active; actionable now.
- Blocked — actionable, but a required module or another update must come first.
- Up to date — already in sync; nothing to apply.
- Applied / Skipped — recorded decisions, browsable on their own tabs (un-skipping is simply removing a record from the Skipped tab).
Key features
- Nothing forced on deploy — applying is always an explicit, per-update decision.
- Version-aware — state is keyed to a content hash of the shipped config, so a re-released update re-appears as pending automatically instead of being silently missed.
- Diff on every row — see exactly what an update would change; the Applied tab even shows the historical before/after captured at apply time.
- Dependencies — a task can require modules to be enabled or other tasks to be applied first, and stays blocked until they are.
- Transactional apply of multi-config tasks, an ordering
weight, and a full audit log. - Drush support —
pu:list,pu:apply,pu:skip— for CI and automation.
For distribution maintainers
The optional Profile Updates: Export submodule (dev-only) generates update tasks straight from a diff of your active configuration, grouped per module or profile. It can alternatively generate forced hook_post_update_N() hooks for changes that genuinely must not be optional. The runtime module has no dependency on it — any module can ship tasks by hand.
Everything runs through a single Drush command once the submodule is enabled:
drush profile-updates-export— re-export active config back into the profile/moduleconfig/install\config/optionalfiles.--diff— dry run;--module=a,b— scope to extensions;--no-new— existing files only.--with-update-tasks— also generate optional update tasks (enableprofile_updates_export_update_tasks); add--group=slug --label="…"to bundle everything into one.--with-update-hooks— also generate forcedpost_updatehooks (enableprofile_updates_export_update_hooks).
Requirements
- Drupal 10.3+ or 11.
- Configuration Update Manager (
drupal/config_update), used to revert/import and diff each config item.