diff --git a/config/schema/search_api.server.schema.yml b/config/schema/search_api.server.schema.yml
index 86c223a..62dd39c 100644
--- a/config/schema/search_api.server.schema.yml
+++ b/config/schema/search_api.server.schema.yml
@@ -28,3 +28,6 @@ search_api.server.*:
     dependencies:
       type: config_dependencies
       label: 'Dependencies'
+
+plugin.plugin_configuration.search_api_backend.*:
+  type: mapping
diff --git a/search_api.install b/search_api.install
index 06fa607..3bef1f1 100644
--- a/search_api.install
+++ b/search_api.install
@@ -57,6 +57,16 @@ function search_api_schema() {
 }
 
 /**
+ * Implements hook_uninstall().
+ */
+function search_api_uninstall() {
+  \Drupal::state()->delete('search_api_use_tracking_batch');
+  foreach (\Drupal::configFactory()->listAll('search_api.index.') as $index_id) {
+    \Drupal::state()->delete("search_api.index.$index_id.has_reindexed");
+  }
+}
+
+/**
  * Implements hook_requirements().
  */
 function search_api_requirements($phase) {
diff --git a/search_api.routing.yml b/search_api.routing.yml
index 34c3448..bd5c2a9 100644
--- a/search_api.routing.yml
+++ b/search_api.routing.yml
@@ -30,6 +30,10 @@ entity.search_api_server.canonical:
     _title: "View"
   requirements:
     _entity_access: 'search_api_server.view'
+  options:
+    parameters:
+      search_api_server:
+        with_config_overrides: TRUE
 
 entity.search_api_server.edit_form:
   path: '/admin/config/search/search-api/server/{search_api_server}/edit'
@@ -44,6 +48,10 @@ entity.search_api_server.delete_form:
     _entity_form: 'search_api_server.delete'
   requirements:
     _entity_access: 'search_api_server.delete'
+  options:
+    parameters:
+      search_api_server:
+        with_config_overrides: TRUE
 
 entity.search_api_server.enable:
   path: '/admin/config/search/search-api/server/{search_api_server}/enable'
@@ -52,6 +60,10 @@ entity.search_api_server.enable:
   requirements:
     _entity_access: 'search_api_server.enable'
     _csrf_token: 'TRUE'
+  options:
+    parameters:
+      search_api_server:
+        with_config_overrides: TRUE
 
 entity.search_api_server.disable:
   path: '/admin/config/search/search-api/server/{search_api_server}/disable'
@@ -59,6 +71,10 @@ entity.search_api_server.disable:
     _entity_form: 'search_api_server.disable'
   requirements:
     _entity_access: 'search_api_server.disable'
+  options:
+    parameters:
+      search_api_server:
+        with_config_overrides: TRUE
 
 entity.search_api_server.clear:
   path: '/admin/config/search/search-api/server/{search_api_server}/clear'
@@ -66,6 +82,10 @@ entity.search_api_server.clear:
     _entity_form: 'search_api_server.clear'
   requirements:
     _entity_access: 'search_api_server.clear'
+  options:
+    parameters:
+      search_api_server:
+        with_config_overrides: TRUE
 
 entity.search_api_index.add_form:
   path: '/admin/config/search/search-api/add-index'
@@ -81,6 +101,10 @@ entity.search_api_index.canonical:
     _title_callback: '\Drupal\search_api\Controller\IndexController::pageTitle'
   requirements:
     _entity_access: 'search_api_index.view'
+  options:
+    parameters:
+      search_api_index:
+        with_config_overrides: TRUE
 
 entity.search_api_index.edit_form:
   path: '/admin/config/search/search-api/index/{search_api_index}/edit'
@@ -95,6 +119,10 @@ entity.search_api_index.delete_form:
     _entity_form: 'search_api_index.delete'
   requirements:
     _entity_access: 'search_api_index.delete'
+  options:
+    parameters:
+      search_api_index:
+        with_config_overrides: TRUE
 
 entity.search_api_index.enable:
   path: '/admin/config/search/search-api/index/{search_api_index}/enable'
@@ -103,6 +131,10 @@ entity.search_api_index.enable:
   requirements:
     _entity_access: 'search_api_index.enable'
     _csrf_token: 'TRUE'
+  options:
+    parameters:
+      search_api_index:
+        with_config_overrides: TRUE
 
 entity.search_api_index.disable:
   path: '/admin/config/search/search-api/index/{search_api_index}/disable'
@@ -110,6 +142,10 @@ entity.search_api_index.disable:
     _entity_form: 'search_api_index.disable'
   requirements:
     _entity_access: 'search_api_index.disable'
+  options:
+    parameters:
+      search_api_index:
+        with_config_overrides: TRUE
 
 entity.search_api_index.fields:
   path: '/admin/config/search/search-api/index/{search_api_index}/fields'
@@ -182,6 +218,10 @@ entity.search_api_index.reindex:
     _entity_form: 'search_api_index.reindex'
   requirements:
     _entity_access: 'search_api_index.reindex'
+  options:
+    parameters:
+      search_api_index:
+        with_config_overrides: TRUE
 
 entity.search_api_index.clear:
   path: '/admin/config/search/search-api/index/{search_api_index}/clear'
@@ -189,3 +229,7 @@ entity.search_api_index.clear:
     _entity_form: 'search_api_index.clear'
   requirements:
     _entity_access: 'search_api_index.clear'
+  options:
+    parameters:
+      search_api_index:
+        with_config_overrides: TRUE
diff --git a/src/Backend/BackendInterface.php b/src/Backend/BackendInterface.php
index 1ef2424..75d7259 100644
--- a/src/Backend/BackendInterface.php
+++ b/src/Backend/BackendInterface.php
@@ -51,6 +51,13 @@ interface BackendInterface extends ConfigurablePluginInterface, BackendSpecificI
    *
    * The server's $original property can be used to inspect the old
    * configuration values.
+   *
+   * Take care, though, that the server at this point might be override-free and
+   * thus contain property values (and apparent changes) which will not actually
+   * go into effect. If this might influence the code in this method, you have
+   * to manually check for overrides to ensure no incorrect action is taken.
+   *
+   * @see \Drupal\search_api\Utility\Utility::getConfigOverrides()
    */
   public function preUpdate();
 
diff --git a/src/Backend/BackendPluginBase.php b/src/Backend/BackendPluginBase.php
index 6c49fdb..02aaca7 100644
--- a/src/Backend/BackendPluginBase.php
+++ b/src/Backend/BackendPluginBase.php
@@ -112,6 +112,7 @@ abstract class BackendPluginBase extends ConfigurablePluginBase implements Backe
    */
   public function setServer(ServerInterface $server) {
     $this->server = $server;
+    return $this;
   }
 
   /**
diff --git a/src/Entity/Index.php b/src/Entity/Index.php
index bbe53e7..cd61b6e 100644
--- a/src/Entity/Index.php
+++ b/src/Entity/Index.php
@@ -37,7 +37,7 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
  *     plural = "@count search indexes",
  *   ),
  *   handlers = {
- *     "storage" = "Drupal\Core\Config\Entity\ConfigEntityStorage",
+ *     "storage" = "Drupal\search_api\Entity\SearchApiConfigEntityStorage",
  *     "list_builder" = "Drupal\search_api\IndexListBuilder",
  *     "form" = {
  *       "default" = "Drupal\search_api\Form\IndexForm",
@@ -259,13 +259,6 @@ class Index extends ConfigEntityBase implements IndexInterface {
   protected $processorInstances;
 
   /**
-   * Whether reindexing has been triggered for this index in this page request.
-   *
-   * @var bool
-   */
-  protected $hasReindexed = FALSE;
-
-  /**
    * The number of currently active "batch tracking" modes.
    *
    * @var int
@@ -568,9 +561,9 @@ class Index extends ConfigEntityBase implements IndexInterface {
   /**
    * {@inheritdoc}
    */
-  public function getProcessorsByStage($stage) {
-    // Get a list of all processors meeting the criteria (stage and, optionally,
-    // enabled) along with their effective weights (user-set or default).
+  public function getProcessorsByStage($stage, $overrides = []) {
+    // Get a list of all processors which support this stage, along with their
+    // weights.
     $processors = $this->getProcessors();
     $processor_weights = array();
     foreach ($processors as $name => $processor) {
@@ -579,6 +572,16 @@ class Index extends ConfigEntityBase implements IndexInterface {
       }
     }
 
+    // Apply any overrides that were passed by the caller.
+    foreach ($overrides as $name => $settings) {
+      /** @var \Drupal\search_api\Processor\ProcessorInterface $processor */
+      $processor = $this->createPlugin('processor', $name, $settings);
+      if ($processor->supportsStage($stage)) {
+        $processors[$name] = $processor;
+        $processor_weights[$name] = $processor->getWeight($stage);
+      }
+    }
+
     // Sort requested processors by weight.
     asort($processor_weights);
 
@@ -818,6 +821,14 @@ class Index extends ConfigEntityBase implements IndexInterface {
   /**
    * {@inheritdoc}
    */
+  public function discardFieldChanges() {
+    $this->fieldInstances = NULL;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getPropertyDefinitions($datasource_id) {
     if (isset($datasource_id)) {
       $datasource = $this->getDatasource($datasource_id);
@@ -978,7 +989,7 @@ class Index extends ConfigEntityBase implements IndexInterface {
       }
       // Since we've indexed items now, triggering reindexing would have some
       // effect again. Therefore, we reset the flag.
-      $this->hasReindexed = FALSE;
+      $this->setHasReindexed(FALSE);
       \Drupal::moduleHandler()->invokeAll('search_api_items_indexed', array($this, $processed_ids));
     }
 
@@ -1084,8 +1095,8 @@ class Index extends ConfigEntityBase implements IndexInterface {
    * {@inheritdoc}
    */
   public function reindex() {
-    if ($this->status() && !$this->hasReindexed) {
-      $this->hasReindexed = TRUE;
+    if ($this->status() && !$this->isReindexing()) {
+      $this->setHasReindexed();
       $this->getTrackerInstance()->trackAllItemsUpdated();
       \Drupal::moduleHandler()->invokeAll('search_api_index_reindex', array($this, FALSE));
     }
@@ -1098,9 +1109,9 @@ class Index extends ConfigEntityBase implements IndexInterface {
     if ($this->status()) {
       // Only invoke the hook if we actually did something.
       $invoke_hook = FALSE;
-      if (!$this->hasReindexed) {
+      if (!$this->isReindexing()) {
         $invoke_hook = TRUE;
-        $this->hasReindexed = TRUE;
+        $this->setHasReindexed();
         $this->getTrackerInstance()->trackAllItemsUpdated();
       }
       if (!$this->isReadOnly()) {
@@ -1117,7 +1128,23 @@ class Index extends ConfigEntityBase implements IndexInterface {
    * {@inheritdoc}
    */
   public function isReindexing() {
-    return $this->hasReindexed;
+    $id = $this->id();
+    return \Drupal::state()->get("search_api.index.$id.has_reindexed", FALSE);
+  }
+
+  /**
+   * Sets whether this index has all items marked for re-indexing.
+   *
+   * @param bool $has_reindexed
+   *   (optional) TRUE if the index has all items marked for re-indexing, FALSE
+   *   otherwise.
+   *
+   * @return $this
+   */
+  protected function setHasReindexed($has_reindexed = TRUE) {
+    $id = $this->id();
+    \Drupal::state()->set("search_api.index.$id.has_reindexed", $has_reindexed);
+    return $this;
   }
 
   /**
@@ -1155,9 +1182,26 @@ class Index extends ConfigEntityBase implements IndexInterface {
       return;
     }
 
-    // Prevent enabling of indexes when the server is disabled.
-    if ($this->status() && !$this->isServerEnabled()) {
-      $this->disable();
+    // Retrieve active config overrides for this index.
+    $overrides = Utility::getConfigOverrides($this);
+
+    // Prevent enabling of indexes when the server is disabled. Take into
+    // account that both the index's "status" and "server" properties might be
+    // overridden.
+    if ($this->status() && !isset($overrides['status'])) {
+      // NULL would be a valid override, so we can't use isset() here.
+      if (!array_key_exists('server', $overrides)) {
+        if (!$this->isServerEnabled()) {
+          $this->disable();
+        }
+      }
+      else {
+        $server_id = $overrides['server'];
+        $server = $server_id !== NULL ? Server::load($server_id) : NULL;
+        if (!$server || !$server->status()) {
+          $this->disable();
+        }
+      }
     }
 
     // Merge in default options.
@@ -1190,7 +1234,8 @@ class Index extends ConfigEntityBase implements IndexInterface {
     }
 
     // Call the preIndexSave() method of all applicable processors.
-    foreach ($this->getProcessorsByStage(ProcessorInterface::STAGE_PRE_INDEX_SAVE) as $processor) {
+    $processor_overrides = !empty($overrides['processor_settings']) ? $overrides['processor_settings'] : [];
+    foreach ($this->getProcessorsByStage(ProcessorInterface::STAGE_PRE_INDEX_SAVE, $processor_overrides) as $processor) {
       $processor->preIndexSave();
     }
 
@@ -1252,6 +1297,11 @@ class Index extends ConfigEntityBase implements IndexInterface {
   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
     parent::postSave($storage, $update);
 
+    // New indexes don't have any items indexed.
+    if (!$update) {
+      $this->setHasReindexed();
+    }
+
     try {
       // Fake an original for inserts to make code cleaner.
       /** @var \Drupal\search_api\IndexInterface $original */
@@ -1306,9 +1356,6 @@ class Index extends ConfigEntityBase implements IndexInterface {
     catch (SearchApiException $e) {
       $this->logException($e);
     }
-
-    // Reset the field instances so saved renames won't be reported anymore.
-    $this->fieldInstances = NULL;
   }
 
   /**
diff --git a/src/Entity/SearchApiConfigEntityStorage.php b/src/Entity/SearchApiConfigEntityStorage.php
new file mode 100644
index 0000000..b250aff
--- /dev/null
+++ b/src/Entity/SearchApiConfigEntityStorage.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Drupal\search_api\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityStorage;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\search_api\IndexInterface;
+
+/**
+ * Provides a special storage for Search API config entities.
+ *
+ * This is necessary since post-save hooks would otherwise operate on
+ * override-free entities, which is not desirable in our case.
+ */
+class SearchApiConfigEntityStorage extends ConfigEntityStorage {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doPostSave(EntityInterface $entity, $update) {
+    $this->resetCache(array($entity->id()));
+
+    // The entity is no longer new.
+    $entity->enforceIsNew(FALSE);
+
+    $overridden_entity = $this->load($entity->id());
+    if (isset($entity->original)) {
+      $overridden_entity->original = $entity->original;
+
+      // In the case of indexes, we also need to clone the fields to allow the
+      // correct detection of renamed field. Conversely, we need to set the new,
+      // rename-free fields on the passed index ($entity) so a subsequent save
+      // won't false detect field renames.
+      if ($entity instanceof IndexInterface) {
+        /** @var \Drupal\search_api\IndexInterface $overridden_entity */
+        $old_fields = $entity->original->getFields();
+        $new_fields = $entity->getFields();
+        $saved_fields = $overridden_entity->getFields();
+//        $cloned_fields = [];
+//        foreach ($saved_fields as $field_id => $field) {
+//          $field = clone $field;
+//          $field->setIndex($entity);
+//          $cloned_fields[$field_id] = $field;
+//        }
+//        $entity->setFields($cloned_fields);
+        foreach ($entity->getFieldRenames() as $old_id => $new_id) {
+          if (!empty($old_fields[$old_id]) && !empty($saved_fields[$new_id])) {
+            $field = clone $new_fields[$new_id];
+            $field->setIndex($overridden_entity);
+            $saved_fields[$new_id] = $field;
+          }
+        }
+        $overridden_entity->setFields($saved_fields);
+      }
+    }
+
+    // Allow code to run after saving.
+    $overridden_entity->postSave($this, $update);
+    $this->invokeHook($update ? 'update' : 'insert', $overridden_entity);
+
+    if ($entity instanceof IndexInterface) {
+      // Reset the field instances so saved renames won't be reported anymore.
+      $entity->discardFieldChanges();
+      $overridden_entity->discardFieldChanges();
+    }
+
+    // After saving, this is now the "original entity", and subsequent saves
+    // will be updates instead of inserts, and updates must always be able to
+    // correctly identify the original entity.
+    $entity->setOriginalId($entity->id());
+    $overridden_entity->setOriginalId($entity->id());
+
+    unset($entity->original);
+    unset($overridden_entity->original);
+  }
+
+}
diff --git a/src/Entity/Server.php b/src/Entity/Server.php
index 08a21c6..47812fd 100644
--- a/src/Entity/Server.php
+++ b/src/Entity/Server.php
@@ -9,6 +9,7 @@ use Drupal\search_api\LoggerTrait;
 use Drupal\search_api\Query\QueryInterface;
 use Drupal\search_api\SearchApiException;
 use Drupal\search_api\ServerInterface;
+use Drupal\search_api\Utility\Utility;
 
 /**
  * Defines the search server configuration entity.
@@ -24,7 +25,7 @@ use Drupal\search_api\ServerInterface;
  *     plural = "@count search servers",
  *   ),
  *   handlers = {
- *     "storage" = "Drupal\Core\Config\Entity\ConfigEntityStorage",
+ *     "storage" = "Drupal\search_api\Entity\SearchApiConfigEntityStorage",
  *     "form" = {
  *       "default" = "Drupal\search_api\Form\ServerForm",
  *       "edit" = "Drupal\search_api\Form\ServerForm",
@@ -464,11 +465,38 @@ class Server extends ConfigEntityBase implements ServerInterface {
     if (!isset($this->original)) {
       return;
     }
+    // Retrieve active config overrides for this server.
+    $overrides = Utility::getConfigOverrides($this);
+
+    // If there are overrides for the backend or its configuration, attempt to
+    // apply them for the preUpdate() call.
+    if (isset($overrides['backend']) || isset($overrides['backend_config'])) {
+      $backend_config = $this->getBackendConfig();
+      if (isset($overrides['backend_config'])) {
+        $backend_config = $overrides['backend_config'];
+      }
+      $backend_id = $this->getBackendId();
+      if (isset($overrides['backend'])) {
+        $backend_id = $overrides['backend'];
+      }
+      $backend_plugin_manager = \Drupal::service('plugin.manager.search_api.backend');
+      $backend_config['#server'] = $this;
+      if (!($backend = $backend_plugin_manager->createInstance($backend_id, $backend_config))) {
+        $label = $this->label();
+        throw new SearchApiException("The backend with ID '$backend_id' could not be retrieved for server '$label'.");
+      }
+    }
+    else {
+      $backend = $this->getBackend();
+    }
 
-    $this->getBackend()->preUpdate();
+    $backend->preUpdate();
 
     // If the server is being disabled, also disable all its indexes.
-    if (!$this->isSyncing() && !$this->status() && $this->original->status()) {
+    if (!$this->isSyncing()
+        && !isset($overrides['status'])
+        && !$this->status()
+        && $this->original->status()) {
       foreach ($this->getIndexes(array('status' => TRUE)) as $index) {
         /** @var \Drupal\search_api\IndexInterface $index */
         $index->setStatus(FALSE)->save();
diff --git a/src/IndexInterface.php b/src/IndexInterface.php
index c8f8813..4b59ade 100644
--- a/src/IndexInterface.php
+++ b/src/IndexInterface.php
@@ -311,12 +311,15 @@ interface IndexInterface extends ConfigEntityInterface {
    * @param string $stage
    *   The stage for which to return the processors. One of the
    *   \Drupal\search_api\Processor\ProcessorInterface::STAGE_* constants.
+   * @param array[] $overrides
+   *   (optional) Overrides to apply to the index's processors, keyed by
+   *   processor IDs with their respective overridden settings as values.
    *
    * @return \Drupal\search_api\Processor\ProcessorInterface[]
    *   An array of all enabled processors that support the given stage, ordered
    *   by the weight for that stage.
    */
-  public function getProcessorsByStage($stage);
+  public function getProcessorsByStage($stage, $overrides = []);
 
   /**
    * Determines whether the given processor ID is valid for this index.
@@ -545,6 +548,13 @@ interface IndexInterface extends ConfigEntityInterface {
   public function getFieldRenames();
 
   /**
+   * Resets the index's fields to the saved state.
+   *
+   * @return $this
+   */
+  public function discardFieldChanges();
+
+  /**
    * Retrieves the properties of one of this index's datasources.
    *
    * @param string|null $datasource_id
diff --git a/src/ParamConverter/SearchApiConverter.php b/src/ParamConverter/SearchApiConverter.php
index 616fad0..a68da13 100644
--- a/src/ParamConverter/SearchApiConverter.php
+++ b/src/ParamConverter/SearchApiConverter.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\search_api\ParamConverter;
 
+use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\ParamConverter\EntityConverter;
 use Drupal\Core\ParamConverter\ParamConverterInterface;
@@ -56,7 +57,11 @@ class SearchApiConverter extends EntityConverter implements ParamConverterInterf
    */
   public function convert($value, $definition, $name, array $defaults) {
     /** @var \Drupal\search_api\IndexInterface $entity */
-    if (!($entity = parent::convert($value, $definition, $name, $defaults))) {
+    $storage = $this->entityManager->getStorage('search_api_index');
+    if (!($storage instanceof ConfigEntityStorageInterface)) {
+      return NULL;
+    }
+    if (!($entity = $storage->loadOverrideFree($value))) {
       return NULL;
     }
 
diff --git a/src/Plugin/search_api/processor/RenderedItem.php b/src/Plugin/search_api/processor/RenderedItem.php
index 375d9aa..e60b24d 100644
--- a/src/Plugin/search_api/processor/RenderedItem.php
+++ b/src/Plugin/search_api/processor/RenderedItem.php
@@ -27,7 +27,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *   description = @Translation("Adds an additional field containing the rendered item as it would look when viewed."),
  *   stages = {
  *     "add_properties" = 0,
- *     "pre_index_save" = -10,
  *   },
  *   locked = true,
  *   hidden = true,
diff --git a/src/UnsavedIndexConfiguration.php b/src/UnsavedIndexConfiguration.php
index 7e7ec7b..e949251 100644
--- a/src/UnsavedIndexConfiguration.php
+++ b/src/UnsavedIndexConfiguration.php
@@ -368,8 +368,8 @@ class UnsavedIndexConfiguration implements IndexInterface, UnsavedConfigurationI
   /**
    * {@inheritdoc}
    */
-  public function getProcessorsByStage($stage) {
-    return $this->entity->getProcessorsByStage($stage);
+  public function getProcessorsByStage($stage, $overrides = []) {
+    return $this->entity->getProcessorsByStage($stage, $overrides);
   }
 
   /**
@@ -508,6 +508,14 @@ class UnsavedIndexConfiguration implements IndexInterface, UnsavedConfigurationI
   /**
    * {@inheritdoc}
    */
+  public function discardFieldChanges() {
+    $this->entity->discardFieldChanges();
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getPropertyDefinitions($datasource_id) {
     return $this->entity->getPropertyDefinitions($datasource_id);
   }
diff --git a/src/Utility/Utility.php b/src/Utility/Utility.php
index 9ed45bb..5cd47b8 100644
--- a/src/Utility/Utility.php
+++ b/src/Utility/Utility.php
@@ -2,6 +2,10 @@
 
 namespace Drupal\search_api\Utility;
 
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Config\ConfigFactoryOverrideInterface;
+use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
 use Drupal\Core\TypedData\ComplexDataInterface;
 use Drupal\Core\TypedData\DataDefinitionInterface;
@@ -10,6 +14,7 @@ use Drupal\search_api\Datasource\DatasourceInterface;
 use Drupal\search_api\IndexInterface;
 use Drupal\search_api\Item\FieldInterface;
 use Drupal\search_api\Plugin\search_api\data_type\value\TextToken;
+use Symfony\Component\DependencyInjection\TaggedContainerInterface;
 
 /**
  * Contains utility methods for the Search API.
@@ -492,4 +497,50 @@ class Utility {
     return array($property_path, NULL);
   }
 
+  /**
+   * Retrieves all overridden property values for the given config entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The config entity to check for overrides.
+   *
+   * @return array
+   *   An associative array mapping property names to their overridden values.
+   */
+  public static function getConfigOverrides(EntityInterface $entity) {
+    $entity_type = $entity->getEntityType();
+    if (!($entity_type instanceof ConfigEntityTypeInterface)) {
+      return [];
+    }
+
+    $config_key = $entity_type->getConfigPrefix() . '.' . $entity->id();
+    $overrides = [];
+
+    // Overrides from tagged services.
+    $container = \Drupal::getContainer();
+    if ($container instanceof TaggedContainerInterface) {
+      $tags = $container->findTaggedServiceIds('config.factory.override');
+      foreach (array_keys($tags) as $service_id) {
+        $override = $container->get($service_id);
+        if ($override instanceof ConfigFactoryOverrideInterface) {
+          $service_overrides = $override->loadOverrides([$config_key]);
+          if (!empty($service_overrides[$config_key])) {
+            // Existing overrides take precedence since these will have been
+            // added by events with a higher priority.
+            $arrays = [$service_overrides[$config_key], $overrides];
+            $overrides = NestedArray::mergeDeepArray($arrays, TRUE);
+          }
+        }
+      }
+    }
+
+    // Overrides from settings.php. (This takes precedence over overrides from
+    // services.)
+    if (isset($GLOBALS['config'][$config_key])) {
+      $arrays = [$overrides, $GLOBALS['config'][$config_key]];
+      $overrides = NestedArray::mergeDeepArray($arrays, TRUE);
+    }
+
+    return $overrides;
+  }
+
 }
diff --git a/tests/search_api_test/src/MethodOverrides.php b/tests/search_api_test/src/MethodOverrides.php
new file mode 100644
index 0000000..c2eac6e
--- /dev/null
+++ b/tests/search_api_test/src/MethodOverrides.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\search_api_test;
+
+use Drupal\search_api\Backend\BackendInterface;
+use Drupal\search_api\IndexInterface;
+
+/**
+ * Holds method overrides for test plugins.
+ */
+class MethodOverrides {
+
+  /**
+   * Provides a generic method override for the test backend.
+   *
+   * @param \Drupal\search_api\Backend\BackendInterface $backend
+   *   The backend plugin on which the method was called.
+   *
+   * @return true
+   *   Always returns TRUE, to cater to those methods that expect a return
+   *   value.
+   */
+  public static function overrideTestBackendMethod(BackendInterface $backend) {
+    if ($backend->getConfiguration() !== ['test' => 'foobar']) {
+      trigger_error('Critical server method called with incorrect backend configuration.', E_USER_ERROR);
+    }
+    return TRUE;
+  }
+
+  /**
+   * Provides an override for the test backend's indexItems() method.
+   *
+   * @param \Drupal\search_api\Backend\BackendInterface $backend
+   *   The backend plugin on which the method was called.
+   * @param \Drupal\search_api\IndexInterface $index
+   *   The search index for which items should be indexed.
+   * @param \Drupal\search_api\Item\ItemInterface[] $items
+   *   An array of items to be indexed, keyed by their item IDs.
+   *
+   * @return string[]
+   *   The array keys of $items.
+   */
+  public static function overrideTestBackendIndexItems(BackendInterface $backend, IndexInterface $index, array $items) {
+    if ($backend->getConfiguration() !== ['test' => 'foobar']) {
+      trigger_error('Server method indexItems() called with incorrect backend configuration.', E_USER_ERROR);
+    }
+    return array_keys($items);
+  }
+
+}
diff --git a/tests/search_api_test/src/Plugin/search_api/backend/TestBackend.php b/tests/search_api_test/src/Plugin/search_api/backend/TestBackend.php
index e0db91a..b0ec81f 100644
--- a/tests/search_api_test/src/Plugin/search_api/backend/TestBackend.php
+++ b/tests/search_api_test/src/Plugin/search_api/backend/TestBackend.php
@@ -30,7 +30,22 @@ class TestBackend extends BackendPluginBase implements PluginFormInterface {
   /**
    * {@inheritdoc}
    */
+  public function postInsert() {
+    if ($override = $this->getMethodOverride(__FUNCTION__)) {
+      call_user_func($override, $this);
+      return;
+    }
+    $this->checkError(__FUNCTION__);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function preUpdate() {
+    if ($override = $this->getMethodOverride(__FUNCTION__)) {
+      call_user_func($override, $this);
+      return;
+    }
     $this->checkError(__FUNCTION__);
   }
 
@@ -38,6 +53,9 @@ class TestBackend extends BackendPluginBase implements PluginFormInterface {
    * {@inheritdoc}
    */
   public function postUpdate() {
+    if ($override = $this->getMethodOverride(__FUNCTION__)) {
+      return call_user_func($override, $this);
+    }
     $this->checkError(__FUNCTION__);
     return $this->getReturnValue(__FUNCTION__, FALSE);
   }
@@ -96,6 +114,9 @@ class TestBackend extends BackendPluginBase implements PluginFormInterface {
    * {@inheritdoc}
    */
   public function indexItems(IndexInterface $index, array $items) {
+    if ($override = $this->getMethodOverride(__FUNCTION__)) {
+      return call_user_func($override, $this, $index, $items);
+    }
     $this->checkError(__FUNCTION__);
 
     $state = \Drupal::state();
@@ -117,6 +138,10 @@ class TestBackend extends BackendPluginBase implements PluginFormInterface {
    * {@inheritdoc}
    */
   public function addIndex(IndexInterface $index) {
+    if ($override = $this->getMethodOverride(__FUNCTION__)) {
+      call_user_func($override, $this, $index);
+      return;
+    }
     $this->checkError(__FUNCTION__);
   }
 
@@ -124,6 +149,10 @@ class TestBackend extends BackendPluginBase implements PluginFormInterface {
    * {@inheritdoc}
    */
   public function updateIndex(IndexInterface $index) {
+    if ($override = $this->getMethodOverride(__FUNCTION__)) {
+      call_user_func($override, $this, $index);
+      return;
+    }
     $this->checkError(__FUNCTION__);
     $index->reindex();
   }
@@ -132,6 +161,10 @@ class TestBackend extends BackendPluginBase implements PluginFormInterface {
    * {@inheritdoc}
    */
   public function removeIndex($index) {
+    if ($override = $this->getMethodOverride(__FUNCTION__)) {
+      call_user_func($override, $this, $index);
+      return;
+    }
     $this->checkError(__FUNCTION__);
   }
 
@@ -139,6 +172,10 @@ class TestBackend extends BackendPluginBase implements PluginFormInterface {
    * {@inheritdoc}
    */
   public function deleteItems(IndexInterface $index, array $item_ids) {
+    if ($override = $this->getMethodOverride(__FUNCTION__)) {
+      call_user_func($override, $this, $index, $item_ids);
+      return;
+    }
     $this->checkError(__FUNCTION__);
 
     $state = \Drupal::state();
@@ -155,6 +192,10 @@ class TestBackend extends BackendPluginBase implements PluginFormInterface {
    * {@inheritdoc}
    */
   public function deleteAllIndexItems(IndexInterface $index, $datasource_id = NULL) {
+    if ($override = $this->getMethodOverride(__FUNCTION__)) {
+      call_user_func($override, $this, $index, $datasource_id);
+      return;
+    }
     $this->checkError(__FUNCTION__);
 
     $key = 'search_api_test.backend.indexed.' . $index->id();
@@ -178,6 +219,10 @@ class TestBackend extends BackendPluginBase implements PluginFormInterface {
    * {@inheritdoc}
    */
   public function search(QueryInterface $query) {
+    if ($override = $this->getMethodOverride(__FUNCTION__)) {
+      call_user_func($override, $this, $query);
+      return;
+    }
     $this->checkError(__FUNCTION__);
 
     $results = $query->getResults();
@@ -217,6 +262,9 @@ class TestBackend extends BackendPluginBase implements PluginFormInterface {
    * {@inheritdoc}
    */
   public function isAvailable() {
+    if ($override = $this->getMethodOverride(__FUNCTION__)) {
+      return call_user_func($override, $this);
+    }
     return $this->getReturnValue(__FUNCTION__, TRUE);
   }
 
@@ -224,6 +272,9 @@ class TestBackend extends BackendPluginBase implements PluginFormInterface {
    * {@inheritdoc}
    */
   public function getDiscouragedProcessors() {
+    if ($override = $this->getMethodOverride(__FUNCTION__)) {
+      return (array) call_user_func($override, $this);
+    }
     return $this->getReturnValue(__FUNCTION__, array());
   }
 
diff --git a/tests/search_api_test/src/PluginTestTrait.php b/tests/search_api_test/src/PluginTestTrait.php
index 4f1e11d..d9f5907 100644
--- a/tests/search_api_test/src/PluginTestTrait.php
+++ b/tests/search_api_test/src/PluginTestTrait.php
@@ -41,6 +41,21 @@ trait PluginTestTrait {
   }
 
   /**
+   * Overrides a method for a certain plugin.
+   *
+   * @param string $plugin_type
+   *   The "short" plugin type.
+   * @param string $method
+   *   The name of the method to override.
+   * @param callable|null $override
+   *   The new code of the method, or NULL to use the default.
+   */
+  protected function setMethodOverride($plugin_type, $method, callable $override = NULL) {
+    $key = "search_api_test.$plugin_type.method.$method";
+    \Drupal::state()->set($key, $override);
+  }
+
+  /**
    * Retrieves the methods called on a given plugin.
    *
    * @param string $plugin_type
diff --git a/tests/search_api_test/src/TestPluginTrait.php b/tests/search_api_test/src/TestPluginTrait.php
index 19d8386..e68a190 100644
--- a/tests/search_api_test/src/TestPluginTrait.php
+++ b/tests/search_api_test/src/TestPluginTrait.php
@@ -74,6 +74,21 @@ trait TestPluginTrait {
   }
 
   /**
+   * Retrieves a possible override set for the given method.
+   *
+   * @param string $method
+   *   The name of the method.
+   *
+   * @return callable|null
+   *   The method override to use, or NULL if none was set.
+   */
+  protected function getMethodOverride($method) {
+    $type = $this->getPluginType();
+    $key = "search_api_test.$type.method.$method";
+    return \Drupal::state()->get($key);
+  }
+
+  /**
    * Returns the plugin type of this object.
    *
    * Equivalent to the last component of the namespace.
diff --git a/tests/src/Functional/ConfigOverrideIntegrationTest.php b/tests/src/Functional/ConfigOverrideIntegrationTest.php
new file mode 100644
index 0000000..6e8a356
--- /dev/null
+++ b/tests/src/Functional/ConfigOverrideIntegrationTest.php
@@ -0,0 +1,205 @@
+<?php
+
+namespace Drupal\Tests\search_api\Functional;
+
+use Drupal\search_api\Entity\Server;
+use Drupal\search_api_test\MethodOverrides;
+use Drupal\search_api_test\PluginTestTrait;
+
+/**
+ * Contains integration tests for config entities with overrides.
+ *
+ * @group search_api
+ */
+class ConfigOverrideIntegrationTest extends SearchApiBrowserTestBase {
+
+  use PluginTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Set up overrides.
+    $settings['config']['search_api.server.test_server']['name'] = (object) array(
+      'value' => 'Overridden server',
+      'required' => TRUE,
+    );
+    $settings['config']['search_api.server.test_server']['status'] = (object) array(
+      'value' => TRUE,
+      'required' => TRUE,
+    );
+    $settings['config']['search_api.server.test_server']['backend_config']['test'] = (object) array(
+      'value' => 'foobar',
+      'required' => TRUE,
+    );
+    $settings['config']['search_api.index.test_index']['name'] = (object) array(
+      'value' => 'Overridden index',
+      'required' => TRUE,
+    );
+    $this->writeSettings($settings);
+
+    $permissions = [
+      'administer search_api',
+      'access administration pages',
+      'administer nodes',
+      'bypass node access',
+      'administer content types',
+    ];
+    $this->adminUser = $this->drupalCreateUser($permissions);
+    $this->drupalLogin($this->adminUser);
+  }
+
+  /**
+   * Tests that the UI works correctly with config entity overrides present.
+   */
+  public function testConfigOverrideIntegration() {
+    $base_path = 'admin/config/search/search-api';
+    $new_user = $this->drupalCreateUser();
+
+    // Set up a trap through method overrides to ensure that critical methods
+    // are only ever called with the correct, overridden backend configuration.
+    $override = [MethodOverrides::class, 'overrideTestBackendMethod'];
+    $methods = [
+      'postInsert',
+      'preUpdate',
+      'postUpdate',
+      'addIndex',
+      'updateIndex',
+      'removeIndex',
+      'deleteItems',
+      'deleteAllIndexItems',
+      'search',
+      'isAvailable',
+      'getDiscouragedProcessors',
+    ];
+    foreach ($methods as $method) {
+      $this->setMethodOverride('backend', $method, $override);
+    }
+    // indexItems() needs a special override since it needs to return item IDs.
+    $override = [MethodOverrides::class, 'overrideTestBackendIndexItems'];
+    $this->setMethodOverride('backend', 'indexItems', $override);
+
+    // Add the server.
+    $this->drupalGet("$base_path/add-server");
+    $edit = [
+      'id' => 'test_server',
+      'name' => 'Test server',
+      'backend' => 'search_api_test',
+    ];
+    $this->submitForm($edit, 'Save');
+    $this->assertSession()
+      ->pageTextContains('The server was successfully saved.');
+    $this->assertSession()->addressEquals($base_path . '/server/test_server');
+
+    // Add the index.
+    $this->drupalGet("$base_path/add-index");
+    $edit = [
+      'id' => 'test_index',
+      'name' => 'Test index',
+      'server' => 'test_server',
+      'datasources[entity:user]' => TRUE,
+      'options[index_directly]' => FALSE,
+    ];
+    $this->submitForm($edit, 'Save');
+    $this->assertSession()
+      ->pageTextContains('Please configure the used datasources.');
+    $this->submitForm([], 'Save');
+    $this->checkForMetaRefresh();
+    $this->assertSession()
+      ->pageTextContains('The index was successfully saved.');
+    $this->assertSession()->addressEquals($base_path . '/index/test_index');
+
+    // Normal server page displays overridden values.
+    $this->drupalGet($base_path . '/server/test_server');
+    $this->assertSession()->pageTextContains('Overridden server');
+    $this->assertSession()->pageTextNotContains('Test server');
+    $this->assertSession()->pageTextContains('Overridden index');
+    $this->assertSession()->pageTextNotContains('Test index');
+    $this->assertSession()->pageTextContains('enabled');
+    $this->assertSession()->pageTextNotContains('disabled');
+
+    // Disabling the server isn't possible.
+    $this->clickLink('disable');
+    $this->submitForm([], 'Disable');
+    $storage = \Drupal::entityTypeManager()->getStorage('search_api_server');
+    $storage->resetCache();
+    $server = $storage->load('test_server');
+    $this->assertTrue($server->status());
+    $this->assertSession()->pageTextNotContains('disabled');
+    $this->assertSession()->pageTextContains('enabled');
+
+    // The "Edit" form shows the override-free server.
+    $this->drupalGet($base_path . '/server/test_server/edit');
+    $this->assertSession()->pageTextContains('Test server');
+    $this->assertSession()->pageTextNotContains('Overridden server');
+    $this->assertSession()->pageTextNotContains('foobar');
+    $edit = [
+      'name' => 'New server name',
+      'backend_config[test]' => 'nonsense',
+    ];
+    $this->submitForm($edit, 'Save');
+
+    // "View" tab still shows overrides, "Edit" tab still doesn't.
+    $this->assertSession()->addressEquals($base_path . '/server/test_server');
+    $this->assertSession()->pageTextContains('Overridden server');
+    $this->assertSession()->pageTextNotContains('New server name');
+    $this->drupalGet($base_path . '/server/test_server/edit');
+    $this->assertSession()->pageTextContains('New server name');
+    $this->assertSession()->pageTextNotContains('Overridden server');
+    $this->assertSession()->pageTextContains('nonsense');
+    $this->assertSession()->pageTextNotContains('foobar');
+
+    // Server "clear" form and command use overridden server.
+    $this->drupalGet($base_path . '/server/test_server/clear');
+    $this->assertSession()->pageTextContains('Overridden server');
+    $this->assertSession()->pageTextNotContains('New server name');
+    $this->submitForm([], 'Confirm');
+
+    // Index "View" tab uses overridden index and server.
+    $this->drupalGet($base_path . '/index/test_index');
+    $this->assertSession()->pageTextContains('Overridden index');
+    $this->assertSession()->pageTextNotContains('Test index');
+    $this->assertSession()->pageTextContains('Overridden server');
+    $this->assertSession()->pageTextNotContains('New server name');
+    $this->assertSession()->pageTextContains('enabled');
+    $this->assertSession()->pageTextNotContains('disabled');
+
+    // Index items, see if that triggers an error.
+    $this->submitForm([], 'Index now');
+    $this->checkForMetaRefresh();
+
+    // Same for deleting an item.
+    $new_user->delete();
+
+    // The "Edit", "Fields" and "Processors" tabs should use the override-free
+    // index version.
+    foreach (['edit', 'fields', 'fields/add', 'processors'] as $tab) {
+      $this->drupalGet("$base_path/index/test_index/$tab");
+      $this->assertSession()->pageTextContains('Test index');
+      $this->assertSession()->pageTextNotContains('Overridden index');
+    }
+
+    // The "Reindex" and "Clear" forms and commands both use the overridden
+    // index.
+    foreach (['reindex', 'clear'] as $tab) {
+      $this->drupalGet("$base_path/index/test_index/$tab");
+      $this->assertSession()->pageTextContains('Overridden index');
+      $this->assertSession()->pageTextNotContains('Test index');
+    }
+    // Clear the index, see if that triggers an error.
+    $this->submitForm([], 'Confirm');
+
+    // The server "Delete" form also uses overrides.
+    $this->drupalGet($base_path . '/server/test_server/delete');
+    $this->assertSession()->pageTextContains('Overridden server');
+    $this->assertSession()->pageTextNotContains('New server name');
+    $this->assertSession()->pageTextContains('Overridden index');
+    $this->assertSession()->pageTextNotContains('Test index');
+
+    // Delete the server, see if that triggers any errors.
+    $this->submitForm([], 'Delete');
+  }
+
+}
diff --git a/tests/src/Kernel/BackendTestBase.php b/tests/src/Kernel/BackendTestBase.php
index 73f3175..5157dc7 100644
--- a/tests/src/Kernel/BackendTestBase.php
+++ b/tests/src/Kernel/BackendTestBase.php
@@ -1018,6 +1018,8 @@ abstract class BackendTestBase extends KernelTestBase {
 
     $index->getField('body')->setType('text');
     $index->save();
+    $count = $this->indexItems($this->indexId);
+    $this->assertEquals(count($this->entities), $count, 'All items needed to be re-indexed after switching type from string to text.');
   }
 
   /**
diff --git a/tests/src/Kernel/ConfigOverrideKernelTest.php b/tests/src/Kernel/ConfigOverrideKernelTest.php
new file mode 100644
index 0000000..a111e26
--- /dev/null
+++ b/tests/src/Kernel/ConfigOverrideKernelTest.php
@@ -0,0 +1,220 @@
+<?php
+
+namespace Drupal\Tests\search_api\Kernel;
+
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\search_api\Backend\BackendInterface;
+use Drupal\search_api\Entity\Index;
+use Drupal\search_api\Entity\Server;
+use Drupal\search_api\Processor\ProcessorInterface;
+use Drupal\search_api_test\PluginTestTrait;
+
+/**
+ * Contains tests for config entities with overrides.
+ *
+ * @group search_api
+ */
+class ConfigOverrideKernelTest extends KernelTestBase {
+
+  use PluginTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array(
+    'search_api',
+    'search_api_test',
+    'system',
+    'user',
+  );
+
+  /**
+   * The test server used for this test.
+   *
+   * @var \Drupal\search_api\ServerInterface
+   */
+  protected $server;
+
+  /**
+   * The test index used for this test.
+   *
+   * @var \Drupal\search_api\IndexInterface
+   */
+  protected $index;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Set up necessary schemas.
+    $this->installSchema('search_api', ['search_api_item']);
+    $this->installSchema('system', ['router']);
+    $this->installSchema('user', ['users_data']);
+    $this->installEntitySchema('user');
+    $this->installEntitySchema('search_api_task');
+
+    // Do not use a batch for tracking the initial items after creating an
+    // index when running the tests via the GUI. Otherwise, it seems Drupal's
+    // Batch API gets confused and the test fails.
+    if (php_sapi_name() != 'cli') {
+      \Drupal::state()->set('search_api_use_tracking_batch', FALSE);
+    }
+
+    // Set tracking page size so tracking will work properly.
+    \Drupal::configFactory()
+      ->getEditable('search_api.settings')
+      ->set('tracking_page_size', 100)
+      ->save();
+
+    // Set up overrides.
+    $GLOBALS['config']['search_api.server.test_server'] = [
+      'name' => 'Overridden server',
+      'backend' => 'search_api_test',
+      'backend_config' => [
+        'test' => 'foobar',
+      ],
+    ];
+    $GLOBALS['config']['search_api.index.test_index'] = [
+      'name' => 'Overridden index',
+      'server' => 'test_server',
+      'processor_settings' => [
+        'search_api_test' => [],
+      ],
+    ];
+
+    // Create a test server and index.
+    $this->server = Server::create([
+      'id' => 'test_server',
+      'name' => 'Test server',
+      'backend' => 'does not exist',
+    ]);
+    $this->index = Index::create([
+      'id' => 'test_index',
+      'name' => 'Test index',
+      'server' => 'unknown_server',
+      'datasource_settings' => [
+        'entity:user' => [],
+      ],
+      'tracker_settings' => [
+        'default' => [],
+      ],
+    ]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function tearDown() {
+    parent::tearDown();
+
+    unset($GLOBALS['config']['search_api.server.test_server']);
+    unset($GLOBALS['config']['search_api.index.test_index']);
+  }
+
+  /**
+   * Checks whether saving an index with overrides works correctly.
+   */
+  public function testIndexSave() {
+    $this->server->save();
+
+    // Even though no processors are set on the index, saving it should trigger
+    // the test processor's preIndexSave() method (since we added that processor
+    // in the override).
+    $this->assertEmpty($this->index->getProcessorsByStage(ProcessorInterface::STAGE_PRE_INDEX_SAVE));
+    $this->index->save();
+    $this->assertEquals(['preIndexSave'], $this->getCalledMethods('processor'));
+    $this->assertEmpty($this->index->getProcessorsByStage(ProcessorInterface::STAGE_PRE_INDEX_SAVE));
+
+    // Verify the override is correctly present when loading the index.
+    /** @var \Drupal\search_api\IndexInterface $index */
+    $index = Index::load($this->index->id());
+    $this->assertEquals('Overridden index', $index->label());
+    $server = $index->getServerInstance();
+    $this->assertNotEmpty($server);
+    $this->assertEquals('Overridden server', $server->label());
+    $this->assertTrue($index->status());
+
+    // Verify that overrides are not present when loading the index
+    // override-free.
+    /** @var \Drupal\search_api\Entity\SearchApiConfigEntityStorage $index_storage */
+    $index_storage = \Drupal::entityTypeManager()
+      ->getStorage('search_api_index');
+    $index = $index_storage->loadOverrideFree($index->id());
+    $this->assertEquals('Test index', $index->label());
+
+    // Try to change the index's name (starting from the override-free index)
+    // and verify a copy with overrides is used for post-save operations.
+    $args = [];
+    $this->setMethodOverride('backend', 'updateIndex', function () use (&$args) {
+      $args = func_get_args();
+    });
+    $index->set('name', 'New index name')->save();
+    $this->assertCount(2, $args);
+    $index = $args[1];
+    $this->assertEquals('Overridden index', $index->label());
+
+    // Verify the override is correctly present when loading the index.
+    $index = Index::load($this->index->id());
+    $this->assertEquals('Overridden index', $index->label());
+    $this->assertEquals('Overridden server', $index->getServerInstance()
+      ->label());
+
+    // Verify the new name is included when loading the index override-free.
+    $index = $index_storage->loadOverrideFree($index->id());
+    $this->assertEquals('New index name', $index->label());
+  }
+
+  /**
+   * Checks whether saving a server with overrides works correctly.
+   */
+  public function testServerSave() {
+    // Verify that in postInsert() the backend overrides are already applied.
+    $passed_config = [];
+    $passed_name = NULL;
+    $override = function (BackendInterface $backend) use (&$passed_config, &$passed_name) {
+      $passed_config = $backend->getConfiguration();
+      $passed_name = $backend->getServer()->label();
+    };
+    $this->setMethodOverride('backend', 'postInsert', $override);
+    $this->server->save();
+    $this->assertEquals(['test' => 'foobar'], $passed_config);
+    $this->assertEquals('Overridden server', $passed_name);
+    $this->assertEquals('Test server', $this->server->label());
+    $this->assertTrue($this->server->status());
+
+    // Save the index.
+    $this->index->save();
+
+    // Verify that on load, the overrides are correctly applied.
+    $server = Server::load($this->server->id());
+    $this->assertEquals('Overridden server', $server->label());
+    $this->assertTrue($server->status());
+    $this->assertEquals('does not exist', $this->server->getBackendId());
+
+    // Verify that in preUpdate() the backend overrides are already applied.
+    $this->setMethodOverride('backend', 'preUpdate', $override);
+    $this->server->save();
+    $this->assertEquals(['test' => 'foobar'], $passed_config);
+
+    // Verify that overriding "status" prevents the server's indexes from being
+    // disabled when attempting to disable the server.
+    $GLOBALS['config']['search_api.server.test_server']['status'] = TRUE;
+    $this->server->disable()->save();
+    \Drupal::configFactory()->clearStaticCache();
+    $index = Index::load($this->index->id());
+    $this->assertTrue($index->status());
+    $server = Server::load($this->server->id());
+    $this->assertTrue($server->status());
+
+    // Verify that overrides are not present when loading the server
+    // override-free.
+    /** @var \Drupal\search_api\Entity\SearchApiConfigEntityStorage $server_storage */
+    $server_storage = \Drupal::entityTypeManager()
+      ->getStorage('search_api_server');
+    $server = $server_storage->loadOverrideFree($server->id());
+    $this->assertEquals('Test server', $server->label());
+  }
+
+}
diff --git a/tests/src/Kernel/IndexChangesTest.php b/tests/src/Kernel/IndexChangesTest.php
index 1aa64b9..44a0385 100644
--- a/tests/src/Kernel/IndexChangesTest.php
+++ b/tests/src/Kernel/IndexChangesTest.php
@@ -266,11 +266,7 @@ class IndexChangesTest extends KernelTestBase {
     $this->taskManager->executeAllTasks();
 
     $methods = $this->getCalledMethods('tracker');
-    // Note: The initial "trackAllItemsUpdated" call comes from the test
-    // backend, which marks the index for re-indexing every time it gets
-    // updated.
     $expected = array(
-      'trackAllItemsUpdated',
       'trackItemsInserted',
       'trackItemsInserted',
     );
diff --git a/tests/src/Unit/Plugin/Processor/AggregatedFieldsTest.php b/tests/src/Unit/Plugin/Processor/AggregatedFieldsTest.php
index e8570ad..0f383bd 100644
--- a/tests/src/Unit/Plugin/Processor/AggregatedFieldsTest.php
+++ b/tests/src/Unit/Plugin/Processor/AggregatedFieldsTest.php
@@ -365,6 +365,7 @@ class AggregatedFieldsTest extends UnitTestCase {
       ->willReturnMap(array(
         array(
           ProcessorInterface::STAGE_ADD_PROPERTIES,
+          array(),
           array(
             'aggregated_field' => $this->processor,
             'processor1' => $processor_mock,
diff --git a/tests/src/Unit/Plugin/Processor/HighlightTest.php b/tests/src/Unit/Plugin/Processor/HighlightTest.php
index b017e74..fb4b97f 100644
--- a/tests/src/Unit/Plugin/Processor/HighlightTest.php
+++ b/tests/src/Unit/Plugin/Processor/HighlightTest.php
@@ -862,6 +862,7 @@ class HighlightTest extends UnitTestCase {
       ->willReturnMap(array(
         array(
           ProcessorInterface::STAGE_ADD_PROPERTIES,
+          array(),
           array(
             'aggregated_field' => $this->processor,
             'processor1' => $processor_mock,
diff --git a/tests/src/Unit/Plugin/Processor/TestFieldsProcessorPlugin.php b/tests/src/Unit/Plugin/Processor/TestFieldsProcessorPlugin.php
index e9dbd0b..91f3a93 100644
--- a/tests/src/Unit/Plugin/Processor/TestFieldsProcessorPlugin.php
+++ b/tests/src/Unit/Plugin/Processor/TestFieldsProcessorPlugin.php
@@ -59,7 +59,7 @@ class TestFieldsProcessorPlugin extends FieldsProcessorPluginBase {
    * @param callable|null $override
    *   The new code of the method, or NULL to use the default.
    */
-  public function setMethodOverride($method, $override = NULL) {
+  public function setMethodOverride($method, callable $override = NULL) {
     $this->methodOverrides[$method] = $override;
   }
 
