Before 1.0.0, a beta renames or removes an API outright, with no deprecation layer. This record lists every such change of 1.0.0-beta10.
Run the database updates: they re-save the entity view displays and create the missing instances. A site deploying by config exports after drush updb.
The homepage page layout and the generic components are removed
No homepage kind of page layout
PageLayoutInterface::isHomepage() is removed, with the entity.page_layout.add_homepage_form route and its Create the homepage action. A page layout on <front> is a conditional layout, ordered by weight like the others. Only the default page layouts still come last.<front> in a page layout's Pages condition matches the front page URL only, not the path it points to, /node/1 for example. Block visibility keeps the core behavior.
Generic components
The components display_builder:section, display_builder:inline, display_builder:break and display_builder:link are removed. They were deprecated since 1.0.0-beta7. Replace them in your displays, presets and recipes before updating.
Entity view displays keep their core class
Display Builder no longer replaces the entity view display classes
display_builder_entity_view works through hooks. The entity class of entity_view_display stays the one of core, of Layout Builder or of the contrib module that sets it, and so does its edit form.
These are removed: DisplayBuilderEntityDisplayInterface, EntityViewDisplay, LayoutBuilderEntityViewDisplay, EntityViewDisplayTrait, EntityViewDisplayForm, LayoutBuilderEntityViewDisplayForm and EntityViewDisplayFormTrait.
Before:
$display->isDisplayBuilderEnabled();
$display->isDisplayBuilderOverridable();
$display->getDisplayBuilderOverrideField();
After:
$manager = \Drupal::service('plugin.manager.display_buildable');
$manager->createInstance('entity_view', ['display' => $display])->isEnabled();
$override = $manager->createInstance('entity_view_override', ['display' => $display]);
$override->isOverridable();
$override->getOverrideField();
DisplayBuilderHelpers::isDisplayBuilderEntityType() moves to EntityView::isDisplayBuilderEntityType().
Displays no longer depend on their profiles
Only an entity class can keep a display alive when a dependency goes, so a display no longer declares one on its profiles or on the modules and themes providing its components. A post update re-saves the displays to drop the dependencies stored before.
Deleting a profile leaves its ID on the display, which falls back to its fields, as before.
collectInstances() is removed, instance lists read storage
collectInstances() is removed
DisplayBuildableInterface::collectInstances() is removed, with no replacement on the plugin. The admin instances list, the presets list and the Views page read instance storage, so a buildable gets its instances listed with no code of its own.
Listing no longer writes anything. Nothing calls the method any more, so delete it from your plugin.
Before:
$buildable = $manager->createInstance('view_display', []);
$instances = $buildable->collectInstances();
After:
$storage = \Drupal::entityTypeManager()->getStorage('display_builder_instance');
$ids = $storage->getQuery()
->accessCheck(FALSE)
->condition('buildable.plugin_id', 'view_display')
->execute();
$instances = $storage->loadMultiple($ids);
Create the instance when Display Builder is enabled
Page layouts, entity view displays and view displays create their instance on save, config imports and recipes included. A contrib buildable does the same by calling initInstanceIfMissing() when it is enabled on a display. Otherwise its display is listed only once opened in a builder.
An existing instance does not follow a later API save of the sources. Set the profile and the sources in one save.
A listed instance has a profile
The list calls getProfile() on every stored instance of an installed buildable, and leaves out the ones answering NULL. An instance can outlive its display, so getProfile() must return NULL when the display is gone.
Entity view overrides
An override is listed once opened in a builder. An override that came with content, from a recipe or a migration, has no instance until then.
Kernel tests
Saving a display or a view with a profile now writes an instance. A kernel test doing so needs $this->installEntitySchema('display_builder_instance').
Update
Run drush updb: post updates create the missing instances of entity view displays and view displays.
DisplayBuildableInterface has no static methods, its URLs go through toUrl()
The static methods are removed
DisplayBuildableInterface::checkInstanceId(), getUrlFromInstanceId() and getDisplayUrlFromInstanceId() are removed. An instance ID is opaque: do not parse it.
Read the buildable plugin off the instance, and its IDs from the plugin configuration. DisplayBuildableInterface now extends ConfigurableInterface.
Before:
$parts = ViewDisplay::checkInstanceId($instance->id());
$url = ViewDisplay::getUrlFromInstanceId($instance->id());
$back = ViewDisplay::getDisplayUrlFromInstanceId($instance->id());
After:
$buildable = $instance->getBuildablePlugin();
$view_id = $buildable->getConfiguration()['view_id'];
$url = $buildable->getBuilderUrl();
$back = $buildable->toUrl();
getCollectionUrl() and getAddUrl() become toUrl()
One method, named like an entity link template, returns every URL but the builder itself:
toUrl()ortoUrl('canonical'): where the Back button leads. Defaults to edit-form.toUrl('edit-form'): where the display's own settings are edited, outside the builder.toUrl('collection'): replacesgetCollectionUrl().toUrl('add-form'): replacesgetAddUrl().
Each one returns NULL when there is none. Another relationship throws \InvalidArgumentException.DisplayBuildablePluginBase gives a working default, so a plugin overrides only the relationships it has, and passes the rest to parent::toUrl($rel). collection and add-form must work on a plugin built with no configuration.
In your buildable plugin
Delete your three static methods. Move your getCollectionUrl(), getAddUrl() and getDisplayUrlFromInstanceId() code into toUrl(). getPrefix() is no longer static: call $this->getPrefix().
Link changes
The Edit operation of a view display opens that display's tab in Views UI, not the whole view. Entity view display URLs no longer carry an entity query parameter.
The instance list shows no Edit operation when toUrl('edit-form') returns NULL.
setNewPresent() saves once, the hashes leave InstanceInterface
setNewPresent() saves once, without flags
HistoryInterface::setNewPresent(array $sources, string|\Stringable $log_message = ''): static loses its $check_hash and $index flags. It normalizes the tree and saves the instance as a new revision, unless the tree is unchanged.
No save after a change
Every mutator saves the instance: setNewPresent(), attachToRoot(), attachToSlot(), moveToRoot(), moveToSlot(), setSource(), setThirdPartySettings() and remove(). Drop the save() that follows them: it saved the instance a second time.
Before:
$instance->setNewPresent($sources, 'Import');
$instance->save();
After:
$instance->setNewPresent($sources, 'Import');
Hashes
InstanceInterface::getUniqId(),getContentHash()andhasUnpublishedChanges()are removed. UseisPublishedPresent(), which now ignores node IDs. Sources published from elsewhere, a recipe for example, lack the node IDs of the draft.- A buildable fills
DisplayReference::$publishedHashwith$this->contentHash($sources), fromDisplayBuildablePluginBase. - The instance storage alone writes the
hashfield, on save.
Smaller changes
ensureCurrentTranslation()returnsstatic. It saves the instance when it adds a translation, and may return another object than the one called.- The public
Instance::$currentUseris removed: the instance asks for the current user when it saves a revision. A test switching users callssetCurrentUser()ofUserCreationTrait.
Builder events take the module prefix
The 11 constants of DisplayBuilderEvents keep their names. Their values change from the island method name to a name prefixed with the module, as core event names are: onUpdate becomes display_builder.update, onPublish becomes display_builder.publish, and so on.
A subscriber using the constants has nothing to change. One using the string values listens to nothing until it uses the constants.
Before:
public static function getSubscribedEvents(): array {
return ['onPublish' => 'onPublish'];
}
After:
public static function getSubscribedEvents(): array {
return [DisplayBuilderEvents::ON_PUBLISH => 'onPublish'];
}
These are builder UI events: the builder fires them after an action. A change made through InstanceInterface outside the builder fires none of them.
API routes can require an island
A route acting on a display builder instance can require an island with the _display_builder_island requirement, valued with its plugin ID. Access is allowed only when the island is enabled in the instance's profile and its isAvailable() returns TRUE for the current user.
The route needs a display_builder_instance parameter. An instance without a profile is refused.
mymodule.api_export:
path: '/api/mymodule/{display_builder_instance}/export'
defaults:
_controller: '\Drupal\mymodule\Controller\ExportController::export'
requirements:
_entity_access: 'display_builder_instance.update'
_display_builder_island: 'mymodule_export'
options:
parameters:
display_builder_instance:
type: 'entity:display_builder_instance'
Display Builder guards its own routes this way: publish, restore and revert require state, save as preset requires menu_preset. Code calling these routes directly gets a 403 when the island is off in the profile.
Smaller removals and changes
ApiControllerBase::SSE_EVENTSandsaveSseData()are removed. The instance records the change for open builders when it saves, whoever saves it, so an API controller extending the base has nothing to record.HtmxEvents::onInstanceUpdateButtonClick()is removed, with the Update button of the contextual form. The form saves on change.- The
display_builder_views.views.deleteroute andConfirmViewsBuilderDeleteFormare removed, with the Delete operation of the Views builder list. EntityViewOverride::getDisplayInfos()returns the view mode IDs as values, not their labels. Name them throughEntityDisplayRepositoryInterface::getViewModeOptions().
Island forms go through PluginWithFormsInterface, with one build info
IslandWithFormInterface extends PluginWithFormsInterface
The static getFormClass() and hasFormClass() of IslandWithFormInterface are removed. The interface now extends core's PluginWithFormsInterface, whose methods of the same names are instance methods taking an operation.IslandWithFormTrait implements them: contextual is the form in the Settings sidebar, and configure the form in the profile, when the island implements PluginFormInterface. An island using the trait has nothing to change.
The contextual form is IslandContextualForm, which runs the island's buildForm(), validateForm() and submitForm(). To give a form class of your own, override getFormClass() and return a PluginFormInterface class. One extending PluginFormBase gets the island as its plugin.
Before:
if ($island_class::hasFormClass()) {
$form_class = $island_class::getFormClass();
}
After:
if ($island->hasFormClass('contextual')) {
$form_class = $island->getFormClass('contextual');
}
One build info shape
The form of an island got its build info in three shapes, one on render and two from the API. They are now one, in the first argument: island_id, instance_id, node_id, node and contexts.instance_id is the display builder instance ID, and node the data of the node, third party settings included. The contexts move from the second argument into contexts. The old builder_id and instance keys are gone, and instance_id no longer holds the node ID.
The island building the form is configured with its own data, its third party settings when it has some, as on render. Read $this->data instead of the old instance key, which held the island data on render and the whole node from the API.
Before:
public function buildForm(array &$form, FormStateInterface $form_state): void {
$args = $form_state->getBuildInfo()['args'];
$data = $args[0]['instance'] ?? [];
$contexts = $args[1] ?? [];
}
After:
public function buildForm(array &$form, FormStateInterface $form_state): void {
$data = $this->data;
$contexts = $form_state->getBuildInfo()['args'][0]['contexts'] ?? [];
}
Removed
IslandWithFormInterface::setBuilderId()andsetNodeId(), with their implementations inIslandPluginBase: the build info carries the instance ID and the node ID.IslandFormBase. Its replacement,IslandForm, is internal.
Islands name the instance $instance, and declare their shortcut on the object
The instance is called $instance
An island called the display builder instance a builder. The protected properties of IslandPluginBase are renamed:
$builderbecomes$instance.$builderIdbecomes$instanceId.
The arguments of build(), buildContent() and getHtmlId() are renamed the same way. An island reading or writing the properties has to use the new names. An island overriding a method keeps working with its old argument names.
Before:
public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
$this->builder = $builder;
$this->builderId = (string) $builder->id();
}
After:
public function build(InstanceInterface $instance, array $data = [], array $options = []): array {
$this->instance = $instance;
$this->instanceId = (string) $instance->id();
}
getKeyboardShortcuts() replaces the static keyboardShortcuts()
IslandInterface::keyboardShortcuts() was static, so an island could not answer from its configuration. It is removed, replaced by getKeyboardShortcuts(), an instance method returning array{key?: string, help?: string|\Stringable}, empty for none. IslandPluginBase returns none.
Before:
public static function keyboardShortcuts(): array {
return ['key' => 'b', 'help' => \t('Show the builder')];
}
After:
public function getKeyboardShortcuts(): array {
return ['key' => 'b', 'help' => $this->t('Show the builder')];
}
type is required on #[Island]
An island without a type did not work. type is now required, and comes right after label. Every island passing named arguments is unaffected.
Removed
IslandPluginBase::reloadWithNodeData()is removed: nothing called it, and its target could not match.IslandWithFormInterface::afterBuild()is removed. The method stays onIslandInterface, so implementations are unaffected.