Cannot get values in 3rd level SubFormState I have a block that embeds another plugins into itself and this includes configuration form. I can properly handle the form structure but when it comes to validation I am not able to access the values in the embedded plugin form validation handler. I was looking at the SubFormState and I tried to adjust the #parents of the provided form element but it didn't help. I know SubFormState is far from perfect but this should not be an issue.

This is my form:

// ...
$carousel_ajax = [
      'wrapper' =&gt; 'carousel-settings-ajax-wrapper',
      'callback' =&gt; [get_called_class(), 'selectCarouselAjaxFormRebuild']
    ];

    // No need for "#tree" option here since this form is already embedded.
    $form['carousel'] = [
      '#type' =&gt; 'fieldset',
      '#title' =&gt; $this-&gt;t('Carousel'),
      '#prefix' =&gt; '&lt;div id="' . $carousel_ajax['wrapper'] . '"&gt;',
      '#suffix' =&gt; '&lt;/div&gt;'
    ];

    $form['carousel']['plugin'] = [
      '#type' =&gt; 'select',
      '#title' =&gt; $this-&gt;t('Use carousel'),
      '#options' =&gt; ['' =&gt; '- ' . $this-&gt;t('None') . ' -'] + $this-&gt;carouselManager-&gt;getList(),
      '#default_value' =&gt; $configuration['carousel']['plugin'],
      '#ajax' =&gt; $carousel_ajax + ['trigger_as' =&gt; ['name' =&gt; 'select_carousel_submit']],
    ];

    $form['carousel']['select_carousel_submit'] = [
      '#type' =&gt; 'submit',
      '#name' =&gt; 'select_carousel_submit',
      '#value' =&gt; $this-&gt;t('Select carousel'),
      '#submit' =&gt; [[$this, 'selectCarouselFormSubmit']],
      '#limit_validation_errors' =&gt; [['settings', 'carousel']],
      '#ajax' =&gt; $carousel_ajax,
      '#attributes' =&gt; [
        'class' =&gt; ['js-hide']
      ]
    ];

    $form['carousel']['settings'] = [];
    if (!empty($configuration['carousel']['plugin']) &amp;&amp; $this-&gt;carouselManager-&gt;hasDefinition($configuration['carousel']['plugin'])) {
      $carousel_plugin = $this-&gt;carouselManager-&gt;createInstance($configuration['carousel']['plugin'], $configuration['carousel']['settings']);
      $form['carousel']['settings'] = $carousel_plugin-&gt;buildConfigurationForm(
        $form['carousel']['settings'],
        SubformState::createForSubform($form['carousel']['settings'], $form, $form_state)
      );
    } else {
      $form['carousel']['settings'] = [
        '#type' =&gt; 'value',
        '#value' =&gt; []
      ];
    }

    return $form;


This is the block form validation handler(I am experimenting with various structures and whatnot):

public function blockValidate($form, FormStateInterface $form_state) {
    if ($form_state-&gt;hasValue(['carousel', 'plugin'])) {
      $carousel_plugin = $this-&gt;carouselManager-&gt;createInstance(
        $form_state-&gt;getValue(['carousel', 'plugin']),
        $form_state-&gt;getValue(['carousel', 'settings'])
      );
      //array_shift($form['settings']['carousel']['settings']['#parents']);
      $carousel_plugin-&gt;validateConfigurationForm(
        $form['settings']['carousel']['settings'],
        SubformState::createForSubform($form['settings']['carousel']['settings'], $form, $form_state)
      );
    }
  }




3rd level because the block itself is 2nd level being embedded in the \Drupal\block\BlockForm::form
After quite some experimenting I came to a working solution.

  /**
   * {@inheritdoc}
   */
  public function blockValidate($form, FormStateInterface $form_state) {
    if ($form_state-&gt;hasValue(['carousel', 'plugin'])) {
      $carousel_plugin = $this-&gt;carouselManager-&gt;createInstance(
        $form_state-&gt;getValue(['carousel', 'plugin']),
        $form_state-&gt;getValue(['carousel', 'settings'])
      );

      // We have to do some manual adjustments here since the SubFormState
      // is far from perfect.
      $element = $form['settings']['carousel']['settings'];
      array_shift($element['#parents']);

      $carousel_plugin-&gt;validateConfigurationForm(
        $element,
        SubformState::createForSubform($element, $form, $form_state)
      );
    }


So the embedded validation handler has to receive altered form element and the subformstate has to receive the original, intact, form.