diff --git a/src/Plugin/facets/processor/MergeNodeTypes.php b/src/Plugin/facets/processor/MergeNodeTypes.php
new file mode 100644
index 0000000..1a16a09
--- /dev/null
+++ b/src/Plugin/facets/processor/MergeNodeTypes.php
@@ -0,0 +1,357 @@
+entityTypeManager = $entity_type_manager;
+ $this->urlProcessorManager = $url_processor_manager;
+ }
+
+ /**
+ * Creates an instance of the plugin.
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('entity_type.manager'),
+ $container->get('plugin.manager.facets.url_processor')
+ );
+ }
+
+ /**
+ * Extract all available node types, then map them as valid options.
+ *
+ * @return array
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ */
+ protected function getNodeTypes(): array {
+ /** @var array $nodeTypes */
+ $nodeTypes = array_map(function ($nodeType) {
+ /** @var \Drupal\node\Entity\NodeType $nodeType */
+ return $nodeType->label();
+ }, $this->entityTypeManager->getStorage('node_type')->loadMultiple());
+
+ return $nodeTypes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet) {
+ /** @var array $config */
+ $config = $this->getConfiguration()['facet_groups'];
+
+ // Gather the number of groups present in the form.
+ $groups = $form_state->get('groups');
+
+ // Ensure that there is at least one group.
+ if (is_null($groups)) {
+ $groups = count($config);
+ $form_state->set('groups', $groups);
+ }
+
+ // Prepare form widget.
+ $build['#tree'] = TRUE;
+ $build['container_open']['#markup'] = '
';
+
+ // Iterate over available groups.
+ for ($i = 0; $i < $groups; $i++) {
+
+ // Build details wrapper for each group.
+ $build['facet_groups'][$i] = [
+ '#type' => 'details',
+ '#title' => $this->t('Facet group'),
+ '#open' => FALSE,
+ ];
+
+ // Include field to overwrite facet name.
+ $build['facet_groups'][$i]['facet_name'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('New Facet name'),
+ '#default_value' => $config[$i]['facet_name'] ?? NULL,
+ ];
+
+ // Expose all available content types.
+ $build['facet_groups'][$i]['content_types'] = [
+ '#type' => 'checkboxes',
+ '#title' => $this->t('Content types to be grouped.'),
+ '#options' => $this->getNodeTypes(),
+ '#default_value' => $config[$i]['content_types'] ?? [],
+ ];
+ }
+
+ // Close container element.
+ $build['container_close']['#markup'] = '
';
+
+ // Setup $.ajax buttons.
+ $build['actions'] = [
+ '#type' => 'actions',
+ ];
+ $build['actions']['add_group'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Add another group'),
+ '#submit' => [
+ [$this, 'addOne'],
+ ],
+ '#ajax' => [
+ 'callback' => [$this, 'addMoreCallback'],
+ 'wrapper' => 'facet-group-fieldset-wrapper',
+ ],
+ ];
+
+ // If there is more than one group, add the remove button.
+ if ($groups > 1) {
+ $build['actions']['remove_group'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Remove last group'),
+ '#submit' => [
+ [$this, 'removeOne'],
+ ],
+ '#ajax' => [
+ 'callback' => [$this, 'addMoreCallback'],
+ 'wrapper' => 'facet-group-fieldset-wrapper',
+ ],
+ ];
+ }
+
+ return $build;
+ }
+
+ /**
+ * Submit handler for the "Add another group" button.
+ *
+ * Increments the max counter and causes a rebuild.
+ *
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ */
+ public function addOne(array &$form, FormStateInterface $form_state) {
+ $groups = $form_state->get('groups');
+ $add_button = $groups + 1;
+ $form_state->set('groups', $add_button);
+
+ // Since our buildForm() method relies on the value of 'num_names' to
+ // generate 'name' form elements, we have to tell the form to rebuild. If we
+ // don't do this, the form builder will not call buildForm().
+ $form_state->setRebuild();
+ }
+
+ /**
+ * Submit handler for the "Remove last group" button.
+ *
+ * Decrements the max counter and causes a form rebuild.
+ *
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ */
+ public function removeOne(array &$form, FormStateInterface $form_state) {
+ $groups = $form_state->get('groups');
+ if ($groups > 1) {
+ $remove_button = $groups - 1;
+ $form_state->set('groups', $remove_button);
+ }
+
+ // Since our buildForm() method relies on the value of 'num_names' to
+ // generate 'name' form elements, we have to tell the form to rebuild. If we
+ // don't do this, the form builder will not call buildForm().
+ $form_state->setRebuild();
+ }
+
+ /**
+ * Callback for both ajax-enabled buttons.
+ *
+ * Selects and returns the fieldset with the names in it.
+ *
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ *
+ * @return array
+ */
+ public function addMoreCallback(array &$form, FormStateInterface $form_state) {
+ /** @var array $facet_groups */
+ $facet_groups = NestedArray::getValue($form, [
+ 'facet_settings',
+ 'facets_merge_node_types',
+ 'settings',
+ 'facet_groups',
+ ]);
+
+ // Recreate container wrapper.
+ $facet_groups['#prefix'] = '';
+ $facet_groups['#suffix'] = '
';
+
+ return $facet_groups;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet) {
+ $form_state->unsetValue('actions');
+ parent::submitConfigurationForm($form, $form_state, $facet);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ return [
+ 'facet_groups' => [
+ [
+ 'facet_name' => '',
+ 'content_types' => [],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function build(FacetInterface $facet, array $results) {
+ /** @var array $facet_groups */
+ $facet_groups = $this->getConfiguration()['facet_groups'];
+
+ /** @var \Drupal\facets\Result\Result[] $facets */
+ $facets = array_reduce($results, function ($carry, $item) {
+ /** @var \Drupal\facets\Result\Result $item */
+ $carry[$item->getRawValue()] = $item;
+ return $carry;
+ }, []);
+
+ $url_processor = $this->urlProcessorManager->createInstance($facet->getFacetSourceConfig()
+ ->getUrlProcessorName(), ['facet' => $facet]);
+ $filter_key = $url_processor->getFilterKey();
+ $field_name = $facet->getUrlAlias();
+
+ array_walk($facet_groups, function ($config) use ($results, &$facets, $filter_key, $field_name) {
+ /** @var array $types */
+ $types = array_filter($config['content_types']);
+ if (empty($types)) {
+ return;
+ }
+
+ /** @var array $filtered */
+ $filtered = array_filter($types, function ($type) use ($facets) {
+ return array_key_exists($type, $facets);
+ });
+ if (empty($filtered)) {
+ return;
+ }
+
+ /** @var string $key */
+ $key = array_shift($filtered);
+ /** @var \Drupal\facets\Result\Result $first */
+ $first = &$facets[$key];
+
+ // Overwrite label if new facet name was defined.
+ if (!empty($config['facet_name'])) {
+ $facet_name = $this->t($config['facet_name']);
+ $first->setDisplayValue($facet_name);
+ }
+
+ // Init flag variables.
+ $updated = FALSE;
+
+ /** @var \Drupal\Core\Url $url */
+ $url = $first->getUrl();
+ /** @var array $query */
+ $query = $url->getOption('query');
+
+ // Walk through all remaining filtered types.
+ foreach ($filtered as $item) {
+ // Setup dynamic filter.
+ $filter = "{$field_name}:{$item}";
+
+ // Look-up for query string.
+ if (empty($query[$filter_key]) || !in_array($filter, $query[$filter_key])) {
+ // Inject filter to current query.
+ $updated = TRUE;
+ $query[$filter_key][] = $filter;
+ }
+ // Verify that current facet is active.
+ elseif ($first->isActive()) {
+ // Remove duplication filter values.
+ $updated = TRUE;
+ $query[$filter_key] = array_filter($query[$filter_key], function ($param) use ($filter) {
+ return $param != $filter;
+ });
+
+ // Remove whole query string when there are not filters.
+ if (empty($query[$filter_key])) {
+ unset($query[$filter_key]);
+ }
+ }
+
+ // Overwrite URL options then define it back to facet.
+ if ($updated) {
+ $url->setOption('query', $query);
+ $first->setUrl($url);
+ }
+
+ // Update facet count value when lab facet was found.
+ $first->setCount($first->getCount() + $facets[$item]->getCount());
+
+ // Remove facet instance.
+ unset($facets[$item]);
+ }
+ });
+
+ return array_values($facets);
+ }
+
+}