diff --git a/config/schema/search_api.processor.schema.yml b/config/schema/search_api.processor.schema.yml
index 661b85c..5338e4b 100644
--- a/config/schema/search_api.processor.schema.yml
+++ b/config/schema/search_api.processor.schema.yml
@@ -28,6 +28,17 @@ plugin.plugin_configuration.search_api_processor.*:
 
 # Definitions for individual processors
 
+plugin.plugin_configuration.search_api_processor.hierarchy:
+  type: search_api.default_processor_configuration
+  label: 'Hierarchy processor configuration'
+  mapping:
+    fields:
+      type: sequence
+      label: 'Fields for which to add the hierarchy'
+      sequence:
+        type: string
+        label: 'Field ID'
+
 plugin.plugin_configuration.search_api_processor.highlight:
   type: search_api.default_processor_configuration
   label: 'Highlight processor configuration'
diff --git a/search_api.services.yml b/search_api.services.yml
index 279b0b0..9894aa0 100644
--- a/search_api.services.yml
+++ b/search_api.services.yml
@@ -56,7 +56,7 @@ services:
 
   search_api.fields_helper:
     class: \Drupal\search_api\Utility\FieldsHelper
-    arguments: ['@entity_field.manager', '@entity_type.bundle.info', '@search_api.data_type_helper']
+    arguments: ['@entity_type.manager', '@entity_field.manager', '@entity_type.bundle.info', '@search_api.data_type_helper']
 
   search_api.index_task_manager:
     class: Drupal\search_api\Task\IndexTaskManager
diff --git a/src/Form/IndexAddFieldsForm.php b/src/Form/IndexAddFieldsForm.php
index ac6e8e7..724409f 100644
--- a/src/Form/IndexAddFieldsForm.php
+++ b/src/Form/IndexAddFieldsForm.php
@@ -7,14 +7,16 @@
 use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
+use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
+use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
 use Drupal\Core\Url;
 use Drupal\search_api\Datasource\DatasourceInterface;
 use Drupal\search_api\Processor\ConfigurablePropertyInterface;
 use Drupal\search_api\Processor\ProcessorPropertyInterface;
+use Drupal\search_api\Utility\FieldsHelperInterface;
 use Drupal\search_api\Utility\Utility;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -26,6 +28,13 @@ class IndexAddFieldsForm extends EntityForm {
   use UnsavedConfigurationFormTrait;
 
   /**
+   * The fields helper.
+   *
+   * @var \Drupal\search_api\Utility\FieldsHelperInterface
+   */
+  protected $fieldsHelper;
+
+  /**
    * The index for which the fields are configured.
    *
    * @var \Drupal\search_api\IndexInterface
@@ -68,6 +77,8 @@ public function getBaseFormId() {
    *
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   The entity manager.
+   * @param \Drupal\search_api\Utility\FieldsHelperInterface $fields_helper
+   *   The fields helper.
    * @param \Drupal\Core\Render\RendererInterface $renderer
    *   The renderer to use.
    * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
@@ -75,8 +86,9 @@ public function getBaseFormId() {
    * @param array $parameters
    *   The parameters for this page request.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, DateFormatterInterface $date_formatter, array $parameters) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, FieldsHelperInterface $fields_helper, RendererInterface $renderer, DateFormatterInterface $date_formatter, array $parameters) {
     $this->entityTypeManager = $entity_type_manager;
+    $this->fieldsHelper = $fields_helper;
     $this->renderer = $renderer;
     $this->dateFormatter = $date_formatter;
     $this->parameters = $parameters;
@@ -87,12 +99,13 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Ren
    */
   public static function create(ContainerInterface $container) {
     $entity_type_manager = $container->get('entity_type.manager');
+    $fields_helper = $container->get('search_api.fields_helper');
     $renderer = $container->get('renderer');
     $date_formatter = $container->get('date.formatter');
     $request_stack = $container->get('request_stack');
     $parameters = $request_stack->getCurrentRequest()->query->all();
 
-    return new static($entity_type_manager, $renderer, $date_formatter, $parameters);
+    return new static($entity_type_manager, $fields_helper, $renderer, $date_formatter, $parameters);
   }
 
 
@@ -264,15 +277,14 @@ protected function getPropertiesList(array $properties, $active_property_path, U
       $this_path .= $key;
 
       $label = $property->getLabel();
-      $property = Utility::getInnerProperty($property);
-      $inner_property = $property;
+      $property = $this->fieldsHelper->getInnerProperty($property);
 
       $can_be_indexed = TRUE;
       $nested_properties = array();
       $parent_child_type = NULL;
       if ($property instanceof ComplexDataDefinitionInterface) {
         $can_be_indexed = FALSE;
-        $nested_properties = Utility::getNestedProperties($property);
+        $nested_properties = $this->fieldsHelper->getNestedProperties($property);
         $main_property = $property->getMainPropertyName();
         if ($main_property && isset($nested_properties[$main_property])) {
           $parent_child_type = $property->getDataType() . '.';
@@ -282,18 +294,17 @@ protected function getPropertiesList(array $properties, $active_property_path, U
           $can_be_indexed = TRUE;
         }
 
-        // Don't add the additional 'entity' property for entity reference
+        // Don't add the additional "entity" property for entity reference
         // fields which don't target a content entity type.
-        $allowed_properties = array(
-          'field_item:entity_reference',
-          'field_item:image',
-          'field_item:file',
-        );
-        if ($inner_property instanceof FieldItemDataDefinition && in_array($inner_property->getDataType(), $allowed_properties)) {
-          $entity_type = $this->getEntityTypeManager()
-            ->getDefinition($inner_property->getSetting('target_type'));
-          if (!$entity_type->isSubclassOf('Drupal\Core\Entity\ContentEntityInterface')) {
-            unset($nested_properties['entity']);
+        if (isset($nested_properties['entity'])) {
+          $entity_property = $nested_properties['entity'];
+          if ($entity_property instanceof DataReferenceDefinitionInterface) {
+            $target = $entity_property->getTargetDefinition();
+            if ($target instanceof EntityDataDefinitionInterface) {
+              if (!$this->fieldsHelper->isContentEntityType($target->getEntityTypeId())) {
+                unset($nested_properties['entity']);
+              }
+            }
           }
         }
       }
@@ -414,7 +425,7 @@ public function addField(array $form, FormStateInterface $form_state) {
     $property = $button['#property'];
 
     list($datasource_id, $property_path) = Utility::splitCombinedId($button['#name']);
-    $field = Utility::createFieldFromProperty($this->entity, $property, $datasource_id, $property_path, NULL, $button['#data_type']);
+    $field = $this->fieldsHelper->createFieldFromProperty($this->entity, $property, $datasource_id, $property_path, NULL, $button['#data_type']);
     $field->setLabel($button['#prefixed_label']);
     $this->entity->addField($field);
 
diff --git a/src/Plugin/search_api/processor/AddHierarchy.php b/src/Plugin/search_api/processor/AddHierarchy.php
new file mode 100644
index 0000000..c6a5707
--- /dev/null
+++ b/src/Plugin/search_api/processor/AddHierarchy.php
@@ -0,0 +1,315 @@
+<?php
+
+namespace Drupal\search_api\Plugin\search_api\processor;
+
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
+use Drupal\search_api\IndexInterface;
+use Drupal\search_api\Item\FieldInterface;
+use Drupal\search_api\Plugin\PluginFormTrait;
+use Drupal\search_api\Processor\ProcessorPluginBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @SearchApiProcessor(
+ *   id = "hierarchy",
+ *   label = @Translation("Index hierarchy"),
+ *   description = @Translation("Allows the indexing of values along with all their ancestors for hierarchical fields (like taxonomy term references)"),
+ *   stages = {
+ *     "preprocess_index" = -45
+ *   }
+ * )
+ */
+class AddHierarchy extends ProcessorPluginBase implements PluginFormInterface {
+
+  use PluginFormTrait;
+
+  /**
+   * Static cache for getHierarchyFields() return values, keyed by index ID.
+   *
+   * @var string[][][]
+   *
+   * @see \Drupal\search_api\Plugin\search_api\processor\AddHierarchy::getHierarchyFields()
+   */
+  protected static $indexHierarchyFields = array();
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface|null
+   */
+  protected $entityTypeManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    /** @var static $processor */
+    $processor = parent::create($container, $configuration, $plugin_id, $plugin_definition);
+
+    $processor->setEntityTypeManager($container->get('entity_type.manager'));
+
+    return $processor;
+  }
+
+  /**
+   * Retrieves the entity type manager service.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager service.
+   */
+  public function getEntityTypeManager() {
+    return $this->entityTypeManager ?: \Drupal::entityTypeManager();
+  }
+
+  /**
+   * Sets the entity type manager service.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager service.
+   *
+   * @return $this
+   */
+  public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function supportsIndex(IndexInterface $index) {
+    $processor = new static(array('#index' => $index), 'hierarchy', array());
+    return (bool) $processor->getHierarchyFields();
+  }
+
+  /**
+   * Finds all (potentially) hierarchical fields for this processor's index.
+   *
+   * Fields are returned if:
+   * - they point to an entity type; and
+   * - that entity type contains a property referencing the same type of entity
+   *   (so that a hierarchy could be built from that nested property).
+   *
+   * @return string[][]
+   *   An array containing all fields of the index for which hierarchical data
+   *   might be retrievable. The keys are those field's IDs, the values are
+   *   associative arrays containing the nested properties of those fields from
+   *   which a hierarchy might be constructed, with the property paths as the
+   *   keys and labels as the values.
+   */
+  protected function getHierarchyFields() {
+    if (!isset(static::$indexHierarchyFields[$this->index->id()])) {
+      $field_options = array();
+
+      foreach ($this->index->getFields() as $field_id => $field) {
+        $definition = $field->getDataDefinition();
+        if ($definition instanceof ComplexDataDefinitionInterface) {
+          $properties = $this->getFieldsHelper()
+            ->getNestedProperties($definition);
+          // The property might be an entity data definition itself.
+          $properties[''] = $definition;
+          foreach ($properties as $property) {
+            $property_label = $property->getLabel();
+            $property = $this->getFieldsHelper()->getInnerProperty($property);
+            if ($property instanceof EntityDataDefinitionInterface) {
+              $options = self::findHierarchicalProperties($property, $property_label);
+              if ($options) {
+                $field_options += array($field_id => array());
+                $field_options[$field_id] += $options;
+              }
+            }
+          }
+        }
+      }
+
+      static::$indexHierarchyFields[$this->index->id()] = $field_options;
+    }
+
+    return static::$indexHierarchyFields[$this->index->id()];
+  }
+
+  /**
+   * Finds all hierarchical properties nested on an entity-typed property.
+   *
+   * @param \Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface $property
+   *   The property to be searched for hierarchical nested properties.
+   * @param string $property_label
+   *   The property's label.
+   *
+   * @return string[]
+   *   An options list of hierarchical properties, keyed by the parent
+   *   property's entity type ID and the nested properties identifier,
+   *   concatenated with a dash (-).
+   */
+  protected function findHierarchicalProperties(EntityDataDefinitionInterface $property, $property_label) {
+    $entity_type_id = $property->getEntityTypeId();
+    $options = array();
+
+    // Check properties for potential hierarchy. Check two levels down, since
+    // Core's entity references all have an additional "entity" sub-property for
+    // accessing the actual entity reference, which we'd otherwise miss.
+    foreach ($this->getFieldsHelper()->getNestedProperties($property) as $name_2 => $property_2) {
+      $property_2_label = $property_2->getLabel();
+      $property_2 = $this->getFieldsHelper()->getInnerProperty($property_2);
+      if ($property_2 instanceof EntityDataDefinitionInterface) {
+        if ($property_2->getEntityTypeId() == $entity_type_id) {
+          $options["$entity_type_id-$name_2"] = Html::escape($property_label . ' » ' . $property_2_label);
+        }
+      }
+      elseif ($property_2 instanceof ComplexDataDefinitionInterface) {
+        foreach ($property_2->getPropertyDefinitions() as $property_3) {
+          $property_3 = $this->getFieldsHelper()->getInnerProperty($property_3);
+          if ($property_3 instanceof EntityDataDefinitionInterface) {
+            if ($property_3->getEntityTypeId() == $entity_type_id) {
+              $options["$entity_type_id-$name_2"] = Html::escape($property_label . ' » ' . $property_2_label);
+              break;
+            }
+          }
+        }
+      }
+    }
+    return $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return array(
+      'fields' => array(),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $formState) {
+    $form['#description'] = $this->t('Select the fields to which hierarchical data should be added.');
+
+    foreach ($this->getHierarchyFields() as $field_id => $options) {
+      $enabled = !empty($this->configuration['fields'][$field_id]);
+      $form['fields'][$field_id]['status'] = array(
+        '#type' => 'checkbox',
+        '#title' => $this->index->getField($field_id)->getLabel(),
+        '#default_value' => $enabled,
+      );
+      reset($options);
+      $form['fields'][$field_id]['property'] = array(
+        '#type' => 'radios',
+        '#title' => $this->t('Hierarchy property to use'),
+        '#description' => $this->t("This field has several nested properties which look like they might contain hierarchy data for the field. Please pick the one that should be used."),
+        '#options' => $options,
+        '#default_value' => $enabled ? $this->configuration['fields'][$field_id] : key($options),
+        '#access' => count($options) > 1,
+        '#states' => array(
+          'visible' => array(
+            // @todo This shouldn't be dependent on the form array structure.
+            //   Use the '#process' trick instead.
+            ":input[name=\"processors[hierarchy][settings][fields][$field_id][status]\"]" => array(
+              'checked' => TRUE,
+            ),
+          ),
+        ),
+      );
+    }
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $formState) {
+    $fields = array();
+    foreach ($formState->getValue('fields', array()) as $field_id => $values) {
+      if (!empty($values['status'])) {
+        if (empty($values['property'])) {
+          $formState->setError($form['fields'][$field_id]['property'], $this->t('You need to select a nested property to use for the hierarchy data.'));
+        }
+        else {
+          $fields[$field_id] = $values['property'];
+        }
+      }
+    }
+    $formState->setValue('fields', $fields);
+    if (!$fields) {
+      $formState->setError($form['fields'], $this->t('You need to select at least one field for which to add hierarchy data.'));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preprocessIndexItems(array $items) {
+    /** @var \Drupal\search_api\Item\ItemInterface $item */
+    foreach ($items as $item) {
+      foreach ($this->configuration['fields'] as $field_id => $property_specifier) {
+        $field = $item->getField($field_id);
+        if (!$field) {
+          continue;
+        }
+        list ($entity_type_id, $property) = explode('-', $property_specifier);
+        foreach ($field->getValues() as $entity_id) {
+          $this->addHierarchyValues($entity_type_id, $entity_id, $property, $field);
+        }
+      }
+    }
+  }
+
+  /**
+   * Adds all ancestors' IDs of the given entity to the given field.
+   *
+   * @param string $entityTypeId
+   *   The entity type ID.
+   * @param mixed $entityId
+   *   The ID of the entity for which ancestors should be found.
+   * @param string $property
+   *   The name of the property on the entity type which contains the references
+   *   to the parent entities.
+   * @param \Drupal\search_api\Item\FieldInterface $field
+   *   The field to which values should be added.
+   */
+  protected function addHierarchyValues($entityTypeId, $entityId, $property, FieldInterface $field) {
+    if ("$entityTypeId-$property" == 'taxonomy_term-parent') {
+      /** @var \Drupal\taxonomy\TermStorageInterface $entity_storage */
+      $entity_storage = $this->getEntityTypeManager()
+        ->getStorage('taxonomy_term');
+      $parents = array();
+      foreach ($entity_storage->loadParents($entityId) as $term) {
+        $parents[] = $term->id();
+      }
+    }
+    else {
+      $entity = $this->getEntityTypeManager()
+        ->getStorage($entityTypeId)
+        ->load($entityId);
+      $parents = array();
+      if ($entity instanceof ContentEntityInterface) {
+        try {
+          foreach ($entity->get($property) as $data) {
+            $values = static::getFieldsHelper()->extractFieldValues($data);
+            $parents = array_merge($parents, $values);
+          }
+        }
+        catch (\InvalidArgumentException $e) {
+          // Might happen, for example, if the property only exists on a certain
+          // bundle, and this entity has the wrong one.
+        }
+      }
+    }
+
+    foreach ($parents as $parent) {
+      if (!in_array($parent, $field->getValues())) {
+        $field->addValue($parent);
+        $this->addHierarchyValues($entityTypeId, $parent, $property, $field);
+      }
+    }
+  }
+
+}
diff --git a/src/Tests/ProcessorIntegrationTest.php b/src/Tests/ProcessorIntegrationTest.php
index cbd5192..2b73521 100644
--- a/src/Tests/ProcessorIntegrationTest.php
+++ b/src/Tests/ProcessorIntegrationTest.php
@@ -3,9 +3,13 @@
 namespace Drupal\search_api\Tests;
 
 use Drupal\Component\Utility\Html;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Entity\Server;
+use Drupal\search_api\Item\Field;
 use Drupal\search_api_test\PluginTestTrait;
+use Drupal\taxonomy\Tests\TaxonomyTestTrait;
 
 /**
  * Tests the admin UI for processors.
@@ -17,7 +21,17 @@
  */
 class ProcessorIntegrationTest extends WebTestBase {
 
+  use EntityReferenceTestTrait;
   use PluginTestTrait;
+  use TaxonomyTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array(
+    'filter',
+    'taxonomy',
+  );
 
   /**
    * {@inheritdoc}
@@ -27,7 +41,7 @@ public function setUp() {
     $this->drupalLogin($this->adminUser);
 
     $this->indexId = 'test_index';
-    Index::create(array(
+    $index = Index::create(array(
       'name' => 'Test index',
       'id' => $this->indexId,
       'status' => 1,
@@ -37,7 +51,45 @@ public function setUp() {
           'settings' => array(),
         ),
       ),
-    ))->save();
+    ));
+
+    // Add a node and a taxonomy term reference, both of which could be used to
+    // create a hierarchy.
+    $this->createEntityReferenceField(
+      'node',
+      'page',
+      'term_field',
+      NULL,
+      'taxonomy_term',
+      'default',
+      array(),
+      FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
+    );
+    $this->createEntityReferenceField(
+      'node',
+      'page',
+      'parent_reference',
+      NULL,
+      'node',
+      'default',
+      array('target_bundles' => array('page' => 'page'))
+    );
+
+    // Index the taxonomy and entity reference fields.
+    $term_field = new Field($index, 'term_field');
+    $term_field->setType('integer');
+    $term_field->setPropertyPath('term_field');
+    $term_field->setDatasourceId('entity:node');
+    $term_field->setLabel('Terms');
+    $index->addField($term_field);
+
+    $parent_reference = new Field($index, 'parent_reference');
+    $parent_reference->setType('integer');
+    $parent_reference->setPropertyPath('parent_reference');
+    $parent_reference->setDatasourceId('entity:node');
+    $parent_reference->setLabel('Terms');
+    $index->addField($parent_reference);
+    $index->save();
   }
 
   /**
@@ -127,6 +179,13 @@ public function testProcessorIntegration() {
     sort($actual_processors);
     $this->assertEqual($enabled, $actual_processors);
 
+    $this->checkAddHierarchyIntegration();
+    $enabled[] = 'hierarchy';
+    sort($enabled);
+    $actual_processors = array_keys($this->loadIndex()->getProcessors());
+    sort($actual_processors);
+    $this->assertEqual($enabled, $actual_processors);
+
     // The 'add_url' processor is not available to be removed because it's
     // locked.
     $this->checkUrlFieldIntegration();
@@ -400,6 +459,25 @@ public function checkTransliterationIntegration() {
   }
 
   /**
+   * Tests the hierarchy processor.
+   */
+  protected function checkAddHierarchyIntegration() {
+    $configuration = array(
+      'fields' => array(
+        'term_field' => 'taxonomy_term-parent',
+        'parent_reference' => 'node-parent_reference',
+      ),
+    );
+    $edit = array(
+      'fields' => array(
+        'term_field' => array('status' => 1),
+        'parent_reference' => array('status' => 1),
+      ),
+    );
+    $this->editSettingsForm($configuration, 'hierarchy', $edit, TRUE, FALSE);
+  }
+
+  /**
    * Tests the integration of the "URL field" processor.
    */
   public function checkUrlFieldIntegration() {
@@ -441,8 +519,11 @@ protected function enableProcessor($processor_id) {
    * @param bool $enable
    *   (optional) If TRUE, explicitly enable the processor. If FALSE, it should
    *   already be enabled.
+   * @param bool $unset_fields
+   *   (optional) If TRUE, the "fields" property will be removed from the
+   *   actual configuration prior to comparing with the given configuration.
    */
-  protected function editSettingsForm(array $configuration, $processor_id, array $form_values = NULL, $enable = TRUE) {
+  protected function editSettingsForm(array $configuration, $processor_id, array $form_values = NULL, $enable = TRUE, $unset_fields = TRUE) {
     if (!isset($form_values)) {
       $form_values = $configuration;
     }
@@ -459,7 +540,10 @@ protected function editSettingsForm(array $configuration, $processor_id, array $
     $this->assertTrue($processor, "Successfully enabled the '$processor_id' processor.'");
     if ($processor) {
       $actual_configuration = $processor->getConfiguration();
-      unset($actual_configuration['fields'], $actual_configuration['weights']);
+      unset($actual_configuration['weights']);
+      if ($unset_fields) {
+        unset($actual_configuration['fields']);
+      }
       $configuration += $processor->defaultConfiguration();
       $this->assertEqual($configuration, $actual_configuration, "Processor configuration for processor '$processor_id' was set correctly.");
     }
diff --git a/src/Utility/FieldsHelper.php b/src/Utility/FieldsHelper.php
index 9b972af..74c6c5d 100644
--- a/src/Utility/FieldsHelper.php
+++ b/src/Utility/FieldsHelper.php
@@ -2,10 +2,12 @@
 
 namespace Drupal\search_api\Utility;
 
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
 use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
 use Drupal\Core\TypedData\ComplexDataInterface;
@@ -33,6 +35,13 @@
 class FieldsHelper implements FieldsHelperInterface {
 
   /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
    * The entity field manager.
    *
    * @var \Drupal\Core\Entity\EntityFieldManagerInterface
@@ -74,6 +83,8 @@ class FieldsHelper implements FieldsHelperInterface {
   /**
    * Constructs a FieldsHelper object.
    *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   The entity type manager.
    * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
    *   The entity field manager.
    * @param EntityTypeBundleInfoInterface $entityBundleInfo
@@ -81,7 +92,8 @@ class FieldsHelper implements FieldsHelperInterface {
    * @param \Drupal\search_api\Utility\DataTypeHelperInterface $dataTypeHelper
    *   The data type helper service.
    */
-  public function __construct(EntityFieldManagerInterface $entityFieldManager, EntityTypeBundleInfoInterface $entityBundleInfo, DataTypeHelperInterface $dataTypeHelper) {
+  public function __construct(EntityTypeManagerInterface $entityTypeManager, EntityFieldManagerInterface $entityFieldManager, EntityTypeBundleInfoInterface $entityBundleInfo, DataTypeHelperInterface $dataTypeHelper) {
+    $this->entityTypeManager = $entityTypeManager;
     $this->entityFieldManager = $entityFieldManager;
     $this->entityBundleInfo = $entityBundleInfo;
     $this->dataTypeHelper = $dataTypeHelper;
@@ -314,12 +326,15 @@ public function filterForPropertyPath(array $fields, $datasource_id, $property_p
   public function getNestedProperties(ComplexDataDefinitionInterface $property) {
     $nestedProperties = $property->getPropertyDefinitions();
     if ($property instanceof EntityDataDefinitionInterface) {
-      $bundles = $this->entityBundleInfo
-        ->getBundleInfo($property->getEntityTypeId());
-      foreach ($bundles as $bundle => $bundleLabel) {
-        $bundleProperties = $this->entityFieldManager
-          ->getFieldDefinitions($property->getEntityTypeId(), $bundle);
-        $nestedProperties += $bundleProperties;
+      $entity_type_id = $property->getEntityTypeId();
+      $is_content_type = $this->isContentEntityType($entity_type_id);
+      if ($is_content_type) {
+        $bundles = $this->entityBundleInfo->getBundleInfo($entity_type_id);
+        foreach ($bundles as $bundle => $bundleLabel) {
+          $bundleProperties = $this->entityFieldManager
+            ->getFieldDefinitions($entity_type_id, $bundle);
+          $nestedProperties += $bundleProperties;
+        }
       }
     }
     return $nestedProperties;
@@ -362,6 +377,19 @@ public function getInnerProperty(DataDefinitionInterface $property) {
   /**
    * {@inheritdoc}
    */
+  public function isContentEntityType($entity_type_id) {
+    try {
+      $definition = $this->entityTypeManager->getDefinition($entity_type_id);
+      return $definition->isSubclassOf(ContentEntityInterface::class);
+    }
+    catch (PluginNotFoundException $e) {
+      return FALSE;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function isFieldIdReserved($fieldId) {
     return substr($fieldId, 0, 11) == 'search_api_';
   }
diff --git a/src/Utility/FieldsHelperInterface.php b/src/Utility/FieldsHelperInterface.php
index 9c31cd2..562d69a 100644
--- a/src/Utility/FieldsHelperInterface.php
+++ b/src/Utility/FieldsHelperInterface.php
@@ -134,6 +134,18 @@ public function retrieveNestedProperty(array $properties, $propertyPath);
   public function getInnerProperty(DataDefinitionInterface $property);
 
   /**
+   * Checks whether the given entity type is a content entity type.
+   *
+   * @param string $entity_type_id
+   *   The ID of the entity type.
+   *
+   * @return bool
+   *   TRUE if the $entity_type_id is a valid content entity type, FALSE
+   *   otherwise.
+   */
+  public function isContentEntityType($entity_type_id);
+
+  /**
    * Determines whether a field ID is reserved for special use.
    *
    * We define all field IDs starting with "search_api_" as reserved, to be safe
diff --git a/tests/src/Kernel/Processor/AddHierarchyTest.php b/tests/src/Kernel/Processor/AddHierarchyTest.php
new file mode 100644
index 0000000..3ffa73e
--- /dev/null
+++ b/tests/src/Kernel/Processor/AddHierarchyTest.php
@@ -0,0 +1,302 @@
+<?php
+
+namespace Drupal\Tests\search_api\Kernel\Processor;
+
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
+use Drupal\node\Entity\NodeType;
+use Drupal\search_api\Item\Field;
+use Drupal\search_api\Query\Query;
+use Drupal\simpletest\NodeCreationTrait;
+use Drupal\taxonomy\Tests\TaxonomyTestTrait;
+use Drupal\Tests\search_api\Kernel\ResultsTrait;
+
+/**
+ * Tests the "Hierarchy" processor.
+ *
+ * @see \Drupal\search_api\Plugin\search_api\processor\AddHierarchy
+ *
+ * @group search_api
+ *
+ * @coversDefaultClass \Drupal\search_api\Plugin\search_api\processor\AddHierarchy
+ */
+class AddHierarchyTest extends ProcessorTestBase {
+
+  use NodeCreationTrait;
+  use EntityReferenceTestTrait;
+  use ResultsTrait;
+  use TaxonomyTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array(
+    'filter',
+    'taxonomy',
+  );
+
+  /**
+   * A hierarchy to test.
+   */
+  protected static $hierarchy = array(
+    'fruit' => array(
+      'apple',
+      'pear'
+    ),
+    'vegetable' => array(
+      'radish',
+      'turnip',
+    ),
+  );
+
+  /**
+   * The nodes created for testing.
+   *
+   * @var \Drupal\node\NodeInterface[]
+   */
+  protected $nodes = array();
+
+  /**
+   * Hierarchical taxonomy terms.
+   *
+   * This is keyed by "type.item", for example: "fruit.pear".
+   *
+   * @var \Drupal\taxonomy\TermInterface[]
+   */
+  protected $terms = array();
+
+  /**
+   * Vocabulary to test with when using taxonomy for the hierarchy.
+   *
+   * @var \Drupal\taxonomy\VocabularyInterface
+   */
+  protected $vocabulary;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp($processor = NULL) {
+    parent::setUp('hierarchy');
+
+    $this->installConfig(array('filter'));
+    $this->installEntitySchema('taxonomy_term');
+    $this->createTaxonomyHierarchy();
+
+    // Create a node type for testing.
+    $type = NodeType::create(array(
+      'type' => 'page',
+      'name' => 'page',
+    ));
+    $type->save();
+
+    // Add the taxonomy field to page type.
+    $this->createEntityReferenceField(
+      'node',
+      'page',
+      'term_field',
+      NULL,
+      'taxonomy_term',
+      'default',
+      array(),
+      FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
+    );
+
+    // Add a generic entity reference field.
+    $this->createEntityReferenceField(
+      'node',
+      'page',
+      'parent_reference',
+      NULL,
+      'node',
+      'default',
+      array()
+    );
+
+    // Index the taxonomy field.
+    $term_field = new Field($this->index, 'term_field');
+    $term_field->setType('integer');
+    $term_field->setPropertyPath('term_field');
+    $term_field->setDatasourceId('entity:node');
+    $term_field->setLabel('Terms');
+    $this->index->addField($term_field);
+    $this->index->save();
+
+    // Index the entity reference field.
+    $reference_field = new Field($this->index, 'parent_reference');
+    $reference_field->setType('integer');
+    $reference_field->setPropertyPath('parent_reference');
+    $reference_field->setDatasourceId('entity:node');
+    $reference_field->setLabel('Parent page');
+    $this->index->addField($reference_field);
+    $this->index->save();
+
+    // Setup a node index.
+    $this->index->setDatasources(array(
+      'entity:node' => $this->index->createPlugin('datasource', 'entity:node'),
+    ));
+    $this->index->save();
+    $this->container
+      ->get('search_api.index_task_manager')
+      ->addItemsAll($this->index);
+    $index_storage = $this->container
+      ->get('entity_type.manager')
+      ->getStorage('search_api_index');
+    $index_storage->resetCache(array($this->index->id()));
+    $this->index = $index_storage->load($this->index->id());
+  }
+
+  /**
+   * Helper function to create the hierarchy with taxonomy terms.
+   */
+  protected function createTaxonomyHierarchy() {
+    $this->vocabulary = $this->createVocabulary();
+
+    foreach (static::$hierarchy as $type => $items) {
+      // Add the 'type' item, and nest items underneath.
+      $this->terms[$type] = $type_term = $this->createTerm($this->vocabulary, array(
+        'name' => $type,
+      ));
+      foreach ($items as $item) {
+        $this->terms["$type.$item"] = $this->createTerm($this->vocabulary, array(
+          'name' => $item,
+          'parent' => $type_term,
+        ));
+      }
+    }
+  }
+
+  /**
+   * Tests taxonomy-based hierarchy indexing.
+   *
+   * @covers ::preprocessIndexItems
+   */
+  public function testPreprocessIndexItemsTaxonomy() {
+    // Add hierarchical terms to 3 nodes.
+    foreach (array('vegetable.turnip', 'vegetable', 'fruit.pear') as $i => $term) {
+      $this->nodes[$i] = $this->createNode(array(
+        'type' => 'page',
+        'term_field' => array(
+          'target_id' => $this->terms[$term]->id(),
+        ),
+      ));
+    }
+    $this->index->reindex();
+    $this->index->indexItems();
+
+    // By default, hierarchy is not indexed, so a search for 'vegetable' should
+    // only return node 2.
+    $query = new Query($this->index);
+    $query->addCondition('term_field', $this->terms['vegetable']->id());
+    $result = $query->execute();
+    $expected = array('node' => array(1));
+    $this->assertResults($result, $expected);
+
+    // Enable hierarchical indexing.
+    $processor = $this->index->getProcessor('hierarchy');
+    $processor->setConfiguration(array(
+      'fields' => array(
+        'term_field' => 'taxonomy_term-parent',
+      ),
+    ));
+    $this->index->save();
+    $this->index->indexItems();
+
+    // Query for "vegetable" should return 2 items:
+    // Node 1 is "vegetable.turnip" and node 2 is just "vegetable".
+    $query = new Query($this->index);
+    $query->addCondition('term_field', $this->terms['vegetable']->id());
+    $result = $query->execute();
+    $expected = array('node' => array(0, 1));
+    $this->assertResults($result, $expected);
+
+    // A search for just turnips should return node 1 only.
+    $query = new Query($this->index);
+    $query->addCondition('term_field', $this->terms['vegetable.turnip']->id());
+    $result = $query->execute();
+    $expected = array('node' => array(0));
+    $this->assertResults($result, $expected);
+
+    // Also add a term with multiple parents.
+    $this->terms['avocado'] = $this->createTerm($this->vocabulary, array(
+      'name' => 'Avocado',
+      'parent' => array($this->terms['fruit']->id(), $this->terms['vegetable']->id()),
+    ));
+    $this->nodes[3] = $this->createNode(array(
+      'type' => 'page',
+      'term_field' => array(
+        'target_id' => $this->terms['avocado']->id(),
+      ),
+    ));
+    $this->index->reindex();
+    $this->index->indexItems();
+
+    // Searching for 'fruit' or 'vegetable' should return this new node.
+    $query = new Query($this->index);
+    $query->addCondition('term_field', $this->terms['fruit']->id());
+    $result = $query->execute();
+    $expected = array('node' => array(2, 3));
+    $this->assertResults($result, $expected);
+
+    $query = new Query($this->index);
+    $query->addCondition('term_field', $this->terms['vegetable']->id());
+    $result = $query->execute();
+    $expected = array('node' => array(0, 1, 3));
+    $this->assertResults($result, $expected);
+  }
+
+  /**
+   * Tests non-taxonomy-based hierarchy.
+   *
+   * @covers ::preprocessIndexItems
+   * @covers ::addHierarchyValues
+   */
+  public function testPreprocessIndexItems() {
+    // Setup the nodes to follow the hierarchy.
+    foreach (static::$hierarchy as $type => $items) {
+      $this->nodes[] = $type_node = $this->createNode(array(
+        'title' => $type,
+      ));
+      foreach ($items as $item) {
+        $this->nodes[] = $this->createNode(array(
+          'title' => $item,
+          'parent_reference' => array('target_id' => $type_node->id()),
+        ));
+      }
+    }
+    // Add a third tier of hierarchy for specific types of radishes.
+    foreach (array('Cherry Belle', 'Snow Belle', 'Daikon') as $item) {
+      $this->nodes[] = $this->createNode(array(
+        'title' => $item,
+        'parent_reference' => array('target_id' => $this->nodes[5]->id()),
+      ));
+    }
+    $this->index->reindex();
+    $this->index->indexItems();
+
+    // Initially hierarchy is excluded, so "vegetable" should only return nodes
+    // 5 and 6.
+    $query = new Query($this->index);
+    $query->addCondition('parent_reference', $this->nodes[3]->id());
+    $result = $query->execute();
+    $expected = array('node' => array(4, 5));
+    $this->assertResults($result, $expected);
+
+    // Enable hierarchical indexing.
+    $processor = $this->index->getProcessor('hierarchy');
+    $processor->setConfiguration(array(
+      'fields' => array(
+        'parent_reference' => 'node-parent_reference',
+      ),
+    ));
+    $this->index->save();
+    $this->index->indexItems();
+
+    // A search for "vegetable" should now include the hierarchy.
+    $query = new Query($this->index);
+    $query->addCondition('parent_reference', $this->nodes[3]->id());
+    $result = $query->execute();
+    $expected = array('node' => array(4, 5, 6, 7, 8));
+    $this->assertResults($result, $expected);
+  }
+
+}
diff --git a/tests/src/Unit/Plugin/Processor/TestItemsTrait.php b/tests/src/Unit/Plugin/Processor/TestItemsTrait.php
index 46a9df3..edc52eb 100644
--- a/tests/src/Unit/Plugin/Processor/TestItemsTrait.php
+++ b/tests/src/Unit/Plugin/Processor/TestItemsTrait.php
@@ -125,13 +125,16 @@ protected function setUpMockContainer() {
       ->getMock();
     $dataTypeHelper = new DataTypeHelper($moduleHandler, $dataTypeManager);
 
+    $entityTypeManager = $this->getMockBuilder('Drupal\Core\Entity\EntityTypeManagerInterface')
+      ->disableOriginalConstructor()
+      ->getMock();
     $entityFieldManager = $this->getMockBuilder('Drupal\Core\Entity\EntityFieldManagerInterface')
       ->disableOriginalConstructor()
       ->getMock();
     $entityBundleInfo = $this->getMockBuilder('Drupal\Core\Entity\EntityTypeBundleInfoInterface')
       ->disableOriginalConstructor()
       ->getMock();
-    $fieldsHelper = new FieldsHelper($entityFieldManager, $entityBundleInfo, $dataTypeHelper);
+    $fieldsHelper = new FieldsHelper($entityTypeManager, $entityFieldManager, $entityBundleInfo, $dataTypeHelper);
 
     $queryHelper = $this->getMock('Drupal\search_api\Utility\QueryHelperInterface');
     $queryHelper->method('createQuery')
