How can I filter child terms in an exposed filter by parent term from contextual filter?
I've done this recently in a project of mine using MYMODULE_form_views_exposed_form_alter. The code assumes you've a view with the term ID on first position as contextual filter, e.g. domain.com/myview/123

&lt;?php
function MYMODULE_form_views_exposed_form_alter(&amp;$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  $view = $form_state-&gt;getStorage('view');
  $display_id = $view['view']-&gt;current_display;

  if ($form_id == 'views_exposed_form' &amp;&amp; $view['view']-&gt;id() == 'MY_VIEW' &amp;&amp; $display_id == 'MY_VIEWDISPLAY') {
    if (!empty($view['view']-&gt;args) &amp;&amp; $view['view']-&gt;args[0]) {
      //get the term ID from the contextual filter
      $contextTermId = $view['view']-&gt;args[0];
      $entriesToRemove = [];

      $currentFilterValue = $form_state-&gt;getUserInput()['FIELD_FILTER_TAXONOMY'];
      foreach ($form['FIELD_FILTER_TAXONOMY']['#options'] as $idx =&gt; $entry) {
        if ($idx === 'All') {
          //remove the old root '- Any -' option
          $entriesToRemove[] = $idx;
        }
        //the next line is a bit shaky might need rework:
        //sometimes filter entries are a stdClass, 
        //but I've also seen entries as array in other exposed forms (grouped filters?)
        elseif ($entry instanceof stdClass &amp;&amp; is_array($entry-&gt;option)) {
          $termId = key($entry-&gt;option);
          $termName = current($entry-&gt;option);
          //the context filter term is our new root, so we change its label to '- Any -'
          if ($termId == $contextTermId) {
            $form['FIELD_FILTER_TAXONOMY']['#options'][$idx]-&gt;option = [$termId =&gt; t('- Any -')];
            if (empty($currentFilterValue) || $currentFilterValue == 'All') {
              //we've removed the default empty/All entry, reset it to our newly created, faked '- Any -' category
              $form_state-&gt;setUserInput(['FIELD_FILTER_TAXONOMY' =&gt; $contextTermId]);
            }
          }
          else {
            //this is the core part: for each filter option load the term
            //and check if it is a child of the contextual filter term
            $parents = \Drupal::entityTypeManager()
              -&gt;getStorage('taxonomy_term')
              -&gt;loadParents($termId);
            if (!in_array($contextTermId, array_keys($parents))) {
              $entriesToRemove[] = $idx;
            }
            else {
              //optional: we are in a sub-filter now,so we no longer need the leading '-' to indicate hierarchy
              $form['FIELD_FILTER_TAXONOMY']['#options'][$idx]-&gt;option = [$termId =&gt; trim(preg_replace('/^-/', '', $termName))];
            }
          }
        }
      }
      foreach ($entriesToRemove as $idx) {
        unset($form['FIELD_FILTER_TAXONOMY']['#options'][$idx]);
      }
      //optional: if there is only 1 entry left, hide the filter
      if (count($form['FIELD_FILTER_TAXONOMY']['#options']) == 1) {
        $form['FIELD_FILTER_TAXONOMY'] = [
          '#type' =&gt; 'hidden',
          'value' =&gt; $contextTermId
        ];
      }
    }
  }
}


A word of caution: Don't to this on huge taxonomies, my code loads every taxonomy term to decide if it needs to be removed.