I was recently looking to modify the form for a content block entity that's placed as an inline block via Layout Builder.

This turns out to be quite difficult:

  • The outer most form is either \Drupal\layout_builder\Form\AddBlockForm or \Drupal\layout_builder\Form\UpdateBlockForm, so that's what needs to be form_altered()
  • Layout Builder's block form works similarly to core's \Drupal\block\BlockForm, in that it embeds configuration form for the block plugin inside of it. I guess this is called a "subform".
  • For inline blocks in layout builder, there's a block plugin class \Drupal\layout_builder\Plugin\Block\InlineBlock. The configuration form for this block plugin is created by building an entity form display for the content block entity that you're trying to create. It does this in a #process callback

Because the actual block content entity form is rendered in a #process callback, a developer that implements a form_alter for layout builder will have a bad time, since the form elements for the block content entity are not actually built yet. Here's what the form array looks like in the form_alter:

form alter array

Instead, I believe the developer must alter the form to add their own #process callback. And it cannot be added to the outer form array because it's still too early there. It needs to be added to the block_form, after the processBlockForm callback is done.

I wonder if there's a way to improve the developer experience here.

Issue fork drupal-3028391

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

bkosborne created an issue. See original summary.

bkosborne’s picture

Issue summary: View changes
ismail cherri’s picture

Hi,
I have the same issue here. I want to alter some values in the block form and I'm unable due to the reasons mentioned above.

johnwebdev’s picture

#3 It is possible, but as bkosborne mentioned you need to add a callback to #process to the layout_builder_update_block or/and layout_builder_add_block.

Here's a simple example:

/**
 * Implements hook_form_alter().
 */
function custom_example_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $form_ids = ['layout_builder_update_block', 'layout_builder_add_block'];

  if (!in_array($form_id, $form_ids)) {
    return;
  }

  $form['settings']['block_form']['#process'][] = '_custom_example_block_form_alter';
}

/**
 * Process callback for custom block form.
 */
function _custom_example_block_form_alter(array $element, FormStateInterface $form_state) {
  if (isset($element['body'])) {
    $element['body']['widget']['0']['#title_display'] = 'none';
  }

  return $element;
}

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

tim.plunkett’s picture

chris burge’s picture

Agreed it should be easier.

Another issue is that if a developer wants to alter a block form rendered by Layout Builder, the developer probably also wants to alter the same block form when it's rendered by Block Content. This means duplicate code or extra work for a workaround.

Take a look at Block Form Alter. It provides a hook_block_type_form_alter() function that modifies block forms rendered by both Block Content and Layout Builder. It also provides a hook_block_plugin_form_alter(), which allows a developer to target block forms by plugin.

dave reid’s picture

I just ran into this today. Agreed it's currently challenging to alter those forms.

chris burge’s picture

I moved my original sandbox project to Block Form Alter and updated my comment in #7. That said, I would love to deprecate the module in favor of a sane core solution.

philosurfer’s picture

@chris-burge you are a savior ;^)

dave reid’s picture

Status: Active » Needs review
StatusFileSize
new885 bytes

Here's a patch which adds hook_layout_builder_inline_block_alter() and hook_layout_builder_inline_block_TYPE_alter(). It needs documentation and tests still.

chris burge’s picture

Is there any appetite to tackle the larger issue described by #7? I think #11 is a step in the right direction, but it only gets us so far.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

maskedjellybean’s picture

I'm sorry for asking for help here but since there has already been some discussion of work arounds... Is there any chance anyone could tell me how to add a new field(s) to this form, save the value of the field(s) on submit and then access the field value within the hook_preprocess_block()? I'm including what I have so far below. My new fields are appearing in the form when configuring a block via Layout Builder. Within _oh_grid_layout_builder_block_submit() I see the correct chosen values being reflected within $form and $form_state.

However after submitting the form and configuring the block again, I see the fields are set to their default values, not the ones I've chosen.

Additionally, I cannot access the fields within hook_preprocess_block(). The new fields are missing from $vars['elements']['#configuration'] where I can find the values of the default fields.

function oh_grid_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  switch ($form_id) {
    case 'layout_builder_add_block':
    case 'layout_builder_update_block':

      $form['settings']['breakpoints'] = array(
        '#type' => 'details',
        '#title' => t('Breakpoints'),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#description' => t('Display or hide this block at specific breakpoints.')
      );

      $form['settings']['breakpoints']['display_xsmall'] = [
        '#type' => 'checkbox',
        '#title' => t('Display at XSmall breakpoint'),
        '#default_value' => TRUE,
        '#empty_value' => FALSE,
      ];

      $form['settings']['breakpoints']['display_small'] = [
        '#type' => 'checkbox',
        '#title' => t('Display at Small breakpoint'),
        '#default_value' => TRUE,
        '#empty_value' => FALSE,
      ];

      $form['settings']['breakpoints']['display_medium'] = [
        '#type' => 'checkbox',
        '#title' => t('Display at Medium breakpoint'),
        '#default_value' => TRUE,
        '#empty_value' => FALSE,
      ];

      $form['settings']['breakpoints']['display_large'] = [
        '#type' => 'checkbox',
        '#title' => t('Display at Large breakpoint'),
        '#default_value' => TRUE,
        '#empty_value' => FALSE,
      ];

      $form['settings']['breakpoints']['display_xlarge'] = [
        '#type' => 'checkbox',
        '#title' => t('Display at XLarge breakpoint'),
        '#default_value' => TRUE,
        '#empty_value' => FALSE,
      ];

      $form['#submit'][] = '_oh_grid_layout_builder_block_submit';

      return $form;

      break;
  }
}

function _oh_grid_layout_builder_block_submit($form, FormStateInterface $form_state) {
  // @TODO: What goes here?
}

Thank you for any help.

maskedjellybean’s picture

After digging into this further I think what I'm trying to do isn't possible yet. Even contrib modules such as Block Class have not found a way to add fields to the block configuration form in Layout Builder based on https://www.drupal.org/project/block_class/issues/2998114 and https://www.drupal.org/project/drupal/issues/3015152 . They're using hook_form_alter() as well. I notice they're using $form['third_party_settings'] instead of trying to add fields to $form['settings'] which is the piece of the puzzle I was missing. Regardless this does not yet work in Layout Builder at least not without the patch to core I linked.

The fields for the block configuration form are defined in BlockBase::buildConfigurationForm(). In order to modify the configuration form for all blocks without using hook_form_alter() the only way I can think would be to override this class entirely, which is maybe not possible and probably not a great idea.

tim.plunkett’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

NW for tests

randalv’s picture

Hello there,

I've run into this issue as well. I've applied the solution provided in #4, but this turns the issue into a whole different one sadly.
While running the code below, if we try to add/remove media (or basically interact with the media object at all), it bugs and gives us the following ajax error.
I'm not quite sure where we're going wrong, any help is appreciated!

An AJAX HTTP error occurred.
HTTP Result Code: 500
Debugging information follows.
Path: /en-INT/layout_builder/add/block/overrides/node.36/0/content/inline_block%3Atext_media_one_col?_wrapper_format=drupal_dialog&ajax_form=1
StatusText: 500 Service unavailable (with message)
ResponseText: The website encountered an unexpected error. Please try again later.LogicException: The database connection is not serializable. This probably means you are serializing an object that has an indirect reference to the database connection. Adjust your code so that is not necessary. Alternatively, look at DependencySerializationTrait as a temporary solution. in Drupal\Core\Database\Connection->__sleep() (line 1546 of core/lib/Drupal/Core/Database/Connection.php). serialize(Array) (Line: 14)
Drupal\Component\Serialization\PhpSerialize::encode(Array) (Line: 80)
Drupal\Core\KeyValueStore\DatabaseStorageExpirable->setWithExpire('form-s2VDC1x8QKwu-dhYUUdMylM8JXxt4RfnRz6apHR3nlI', Array, 21600) (Line: 193)
Drupal\Core\Form\FormCache->setCache('form-s2VDC1x8QKwu-dhYUUdMylM8JXxt4RfnRz6apHR3nlI', Array, Object) (Line: 447)
Drupal\Core\Form\FormBuilder->setCache('form-s2VDC1x8QKwu-dhYUUdMylM8JXxt4RfnRz6apHR3nlI', Array, Object) (Line: 425)
Drupal\Core\Form\FormBuilder->rebuildForm('layout_builder_add_block', Object, Array) (Line: 627)
Drupal\Core\Form\FormBuilder->processForm('layout_builder_add_block', Array, Object) (Line: 320)
Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 91)
Drupal\Core\Controller\FormController->getContentResult(Object, Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 573)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 151)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 68)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 694)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

Our situation:
- Layoutbuilder block with a media reference
- Hide a certain field on the form
- Using hook_event_dispatcher and catching the event via class based EventListeners.

Our code:

  /**
   * Hide fields from Media-Text-One-Col block (only on cases)
   *
   * @param array $element
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *
   * @return array
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function alterMediaTextOneColFields(array $element, FormStateInterface $form_state) {
    if (!isset($element['field_media_position'])) {
      return $element;
    }
    $nodeId = $this->request->attributes->get('_raw_variables');
    $nodeId = $nodeId->get('section_storage');
    if (!$nodeId) {
      return $element;
    }
    $nodeId = str_replace('node.', '', $nodeId);
    $node = $this->entityTypeManager->getStorage('node')->load($nodeId);
    if ($node->bundle() !== 'case_study') {
      return $element;
    }

    $element['field_media_position']['#access'] = FALSE;
    return $element;
  }

  public function alterBlockForm(FormIdAlterEvent $event) {
    $form = &$event->getForm();
    $block_id = $form['settings']['block_form']['#block']->bundle();
    if ($block_id === 'text_media_one_col') {
      $form['settings']['block_form']['#process'][] = [$this, 'alterMediaTextOneColFields'];
    }
  }
maskedjellybean’s picture

If you are trying to add a field and not alter an existing field:

I was able to figure this out but there's way too much code involved to paste here. I would just say to look at the two links I posted here: https://www.drupal.org/project/drupal/issues/3028391#comment-13445067

One is a patch to core. Apply that. Enable the block_class module and then try to replicate what they did to make their CSS Classes field appear on the configuration form in Layout Builder. When you're done you can disable and uninstall block_class, it's just for reference. It's way more difficult than it should be but it is possible. I was able to create a field that allows me to hide and show blocks at certain breakpoints.

randalv’s picture

Hi Maskedjellybean,

Thanks for the reply! Sadly this was not our use case.
We needed to remove a certain field from displaying on the form, but only when this block was used through layoutbuilder.
So it had to be done via a form alter.

Whilst this seemed easy enough at first, our media library completely broke and spew out the error in my comment above.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

bradallenfisher’s picture

is there a way to simply expose the form display when adding the inline-block to the layout builder? I can see it similar to choosing a view mode for how the block should be rendered.

komalk’s picture

I have run a test case for 9.2.x in #11

himerus’s picture

Just running into this issue today, and having issues with what should be incredibly simple Form API tasks:

  • Adding a visibility state to certain fields based on a select or checkbox.
  • Wrapping some fields in tabs, details, fieldset, etc.

I'll have a workaround today I believe based on the above noted solutions, but when using a block type, 'canvas' in my current case, form modifications should apply the same if it is being added as an inline block_content block, or added as a reusable block at block/add/BUNDLE.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

phjou’s picture

I just had to do the same tweak. Using #process is working pretty well, but I wanted to add a #validate and it seems not possible to do that inside a #process. So I am not really sure how to solve that issue.

claudiu.cristea’s picture

The problem, I think, is that there's no way to alter the plugin form before it's injected as sub-form. That would guarantee that any form embedding the sub-form would receive the same altered sub-form. For instance, if you alter the block settings in this way hook_form_block_form_alter(), only BlockForm will benefit for alterations. A block placed with Layout Builder will have to duplicate the code in its own alter callback, and so on.

There must be a way to alter the plugin config form itself. And this is not a Layout Builder, or Block modules issue. It's not even a block plugin issue. I think this should be fixed at PluginFormInterface level.

sundarcreosen’s picture

I have a requirement for coming up with a Video block by topic functionality.

As a content editor,

I need to place a block on any page, listing videos that are tagged with a certain topic.
And, I need to be able to choose a different topic each time I place a block.
And I need the ability to place multiple of these blocks on the same page.

When a content editor is logged on and on a landing page node, he should be able to click "Layout" and click "Add block" in the desired section. And select the Video Gallery by topic. And select a topic and click "Add block". And click Save Layout. Now, the listing should be available in page/videos url.

Here the topic indicates a taxonomy term. The primary requirement is ability to select a topic at the time of block placement. And, only links are links to the video nodes.

I tried the above functionality with Layout builder module with hook form alter custom code. But it shows different form id each time the block is added. So, I tried with "Block form alter" contributed module and in this approach form id is default. I tried with following hooks: hook_block_plugin_form_alter(), hook_block_type_form_alter().

Got an idea to override the form with the hook provided by the contributed module. But when I try to add the form id which i got after enabling the module, it doesn’t allow to select the block. And, I am not able to add the blocks further. Anybody tried similar feature or functionality. Any help is much appreciated.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

alt.dev made their first commit to this issue’s fork.

alt.dev’s picture

Patch #11 works great to me so I created the MR with this patch and also added hooks declarations to the layout_builder.api.php file.

anybody’s picture

@maskedjellybean as of your comments #7, #11, #12, #16 and #27 I'd suggest the following:

  1. Discuss the solution from #11 considering #7, #12 and #27. First we need to know what / how we want the solution to look like!
  2. Afterwards, we can write tests for this (#27)!
  3. Postpone (PP-1) this on #3015152: Support third-party settings for components within a section. Please help to review and finish #3015152: Support third-party settings for components within a section finally. Otherwise it will still be hard to save the third party values even if the form can be modified!

What do the others think?

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

grevil’s picture

Just ran into this at #3117170: Make fences available for all block types in layout builder and agree with @Anybody at #33 that this needs a plan, presumably by a core expert / maintainer to tell us how / what to do.
This is an important decision and the missing functionality is a blocker for third-party, making such alterations a lot more complex than it's typical for Drupal!

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

guardiola86’s picture

I tried using Block Class module and didn't work for me. My issue was that I couldn't use the same validate and submit functions that were working in the block edit page, but not when using layout builder. I ended up using something like this:

/**
 * Implements hook_entity_insert().
 */
function embeddocument_block_entity_insert(EntityInterface $entity) {
  _embeddocument_block_entity_insert_or_update($entity);
}

/**
 * Implements hook_entity_update().
 */
function embeddocument_block_entity_update(EntityInterface $entity) {
  _embeddocument_block_entity_insert_or_update($entity);
}

/**
 * Custom callback used by insert and update.
 * 
 * @param $entity
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function _embeddocument_block_entity_insert_or_update($entity) {
  if (\Drupal::moduleHandler()->moduleExists('block_content')) {
    $entity_langcode = $entity->get('langcode')->value;
    if ($entity->bundle() == 'document') {
      $entity_array = $entity->toArray();
      if (isset($entity_array['field_documents'])) {
        $mid = $entity_array['field_documents'][0]['target_id'];
        if (!empty($mid) && !empty($entity_langcode)) {
          $media = Media::load($mid);
          // If translation doesn't exist, create it.
          if (!$media->hasTranslation($entity_langcode)) {
            $media->addTranslation($entity_langcode, $media->toArray());
          }
          $media->save();
        }
      }
    }
  }
}
apotek’s picture

Incredibly, Dave's now six years old patch from comment #11 still applies cleanly to 11.2.4 and continues to fix the issue.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

igorgoncalves’s picture

+1 to say that #11 still works well for Drupal 11.2.10

i will left below an example of what works for me, just in case someone else falls here and maybe take this as a shortcut instead of read this long thread ;)

after apply the #11 patch

  1. on my custom module:
  2. my_module/src/Hook/Hooks.php

  3. Goals and Case Overview
  4. In my case i had a block type hero, with a "hero_items" paragraph (with ulimited number of values), and another paragraph inside called "manual_hero".
    our goal it was to handle manual_hero fields as: make the field_image required, and hide field_icon and field_body only for certain cases when user was editing by LB

  5. Add our new hook
  6. #[Hook('layout_builder_inline_block_hero_alter')]
      public function layoutBuilderInlineBlockHeroAlter(array &$build): void {
        foreach ($build['field_hero_items']['widget'] as &$widget) {
          if (is_array($widget) && isset($widget['subform']['field_manual_hero']['widget'][0]['subform']['field_image']['widget'])) {
            $widget['subform']['field_manual_hero']['widget'][0]['subform']['field_image']['widget']['#required'] = TRUE;
            unset(
              $widget['subform']['field_manual_hero']['widget'][0]['subform']['field_icon'],
              $widget['subform']['field_manual_hero']['widget'][0]['subform']['field_body']
            );
          }
        }
      }