diff --git a/core/lib/Drupal/Core/Entity/EntityListController.php b/core/lib/Drupal/Core/Entity/EntityListController.php
index 64c1c43..7bf78c2 100644
--- a/core/lib/Drupal/Core/Entity/EntityListController.php
+++ b/core/lib/Drupal/Core/Entity/EntityListController.php
@@ -55,6 +55,13 @@ class EntityListController implements EntityListControllerInterface, EntityContr
   protected $translationManager;
 
   /**
+   * The URL generator.
+   *
+   * @var \Drupal\Core\Routing\UrlGeneratorInterface
+   */
+  protected $urlGenerator;
+
+  /**
    * {@inheritdoc}
    */
   public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
@@ -261,4 +268,30 @@ protected function getTitle() {
     return;
   }
 
+  /**
+   * Generates a URL or path for a specific route based on the given parameters.
+   *
+   * @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for
+   *   details on the arguments, usage, and possible exceptions.
+   *
+   * @return string
+   *   The generated URL for the given route.
+   */
+  public function url($route_name, $route_parameters = array(), $options = array()) {
+    return $this->urlGenerator()->generateFromRoute($route_name, $route_parameters, $options);
+  }
+
+  /**
+   * Gets the URL generator.
+   *
+   * @return \Drupal\Core\Routing\UrlGeneratorInterface
+   *   The URL generator.
+   */
+  protected function urlGenerator() {
+    if (!$this->urlGenerator) {
+      $this->urlGenerator = \Drupal::urlGenerator();
+    }
+    return $this->urlGenerator;
+  }
+
 }
diff --git a/core/modules/node/config/search.search.node_search.yml b/core/modules/node/config/search.search.node_search.yml
new file mode 100644
index 0000000..7e9ac7e
--- /dev/null
+++ b/core/modules/node/config/search.search.node_search.yml
@@ -0,0 +1,9 @@
+id: node_search
+label: 'Node search'
+uuid: 25687eeb-4bb5-469c-ad05-5eb24cd7012c
+status: '1'
+langcode: en
+path: node
+title: Content
+plugin: node_search
+configuration: {  }
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php
index 087944e..4d4cf8a 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\node\Plugin\Search;
 
-use Drupal\Core\Annotation\Translation;
 use Drupal\Core\Config\Config;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\Query\SelectExtender;
@@ -16,14 +15,11 @@
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
 use Drupal\Core\Language\Language;
-use Drupal\Core\Plugin\PluginFormInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Access\AccessibleInterface;
 use Drupal\Core\Database\Query\Condition;
-use Drupal\search\Annotation\SearchPlugin;
-use Drupal\search\Plugin\SearchPluginBase;
+use Drupal\search\Plugin\ConfigurableSearchPluginBase;
 use Drupal\search\Plugin\SearchIndexingInterface;
-
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -35,7 +31,7 @@
  *   path = "node"
  * )
  */
-class NodeSearch extends SearchPluginBase implements AccessibleInterface, SearchIndexingInterface, PluginFormInterface {
+class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInterface, SearchIndexingInterface {
 
   /**
    * A database connection object.
@@ -80,6 +76,13 @@ class NodeSearch extends SearchPluginBase implements AccessibleInterface, Search
   protected $account;
 
   /**
+   * @todo.
+   *
+   * @var array
+   */
+  protected $rankings;
+
+  /**
    * The list of options and info for advanced search filters.
    *
    * Each entry in the array has the option as the key and and for its value, an
@@ -269,11 +272,10 @@ public function execute() {
    *   A query object that has been extended with the Search DB Extender.
    */
   protected function addNodeRankings(SelectExtender $query) {
-    if ($ranking = $this->moduleHandler->invokeAll('ranking')) {
+    if ($ranking = $this->getRankings()) {
       $tables = &$query->getTables();
       foreach ($ranking as $rank => $values) {
-        // @todo - move rank out of drupal variables.
-        if ($node_rank = variable_get('node_rank_' . $rank, 0)) {
+        if ($node_rank = $this->configuration[$rank]) {
           // If the table defined in the ranking isn't already joined, then add it.
           if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
             $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
@@ -516,27 +518,53 @@ public function searchFormSubmit(array &$form, array &$form_state) {
   }
 
   /**
+   * @todo.
+   *
+   * @return array
+   */
+  protected function getRankings() {
+    if (!$this->rankings) {
+      $this->rankings = array();
+      foreach ($this->moduleHandler->invokeAll('ranking') as $var => $value) {
+        $this->rankings["node_rank_$var"] = $value;
+      }
+    }
+    return $this->rankings;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    $configuration = array();
+    foreach ($this->getRankings() as $var => $value) {
+      $configuration[$var] = 0;
+    }
+    return $configuration;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, array &$form_state) {
     // Output form for defining rank factor weights.
     $form['content_ranking'] = array(
       '#type' => 'details',
-      '#title' => t('Content ranking'),
+      '#title' => $this->t('Content ranking'),
+      '#theme' => 'node_search_admin',
     );
-    $form['content_ranking']['#theme'] = 'node_search_admin';
     $form['content_ranking']['info'] = array(
-      '#value' => '<em>' . t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>'
+      '#value' => '<em>' . $this->t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>'
     );
 
     // Note: reversed to reflect that higher number = higher ranking.
     $options = drupal_map_assoc(range(0, 10));
-    foreach ($this->moduleHandler->invokeAll('ranking') as $var => $values) {
-      $form['content_ranking']['factors']['node_rank_' . $var] = array(
+    foreach ($this->getRankings() as $var => $values) {
+      $form['content_ranking']['factors'][$var] = array(
         '#title' => $values['title'],
         '#type' => 'select',
         '#options' => $options,
-        '#default_value' => variable_get('node_rank_' . $var, 0),
+        '#default_value' => $this->configuration[$var],
       );
     }
     return $form;
@@ -545,18 +573,13 @@ public function buildConfigurationForm(array $form, array &$form_state) {
   /**
    * {@inheritdoc}
    */
-  public function validateConfigurationForm(array &$form, array &$form_state) {
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function submitConfigurationForm(array &$form, array &$form_state) {
-    foreach ($this->moduleHandler->invokeAll('ranking') as $var => $values) {
-      if (isset($form_state['values']['node_rank_' . $var])) {
-        // @todo Fix when https://drupal.org/node/1831632 is in.
-        variable_set('node_rank_' . $var, $form_state['values']['node_rank_' . $var]);
+    foreach ($this->getRankings() as $var => $values) {
+      // @todo Restore theme_node_ranking or whatever.
+      if (isset($form_state['values'][$var])) {
+        $this->configuration[$var] = $form_state['values'][$var];
       }
     }
   }
+
 }
diff --git a/core/modules/search/config/search.settings.yml b/core/modules/search/config/search.settings.yml
index 78e0767..4c73a0b 100644
--- a/core/modules/search/config/search.settings.yml
+++ b/core/modules/search/config/search.settings.yml
@@ -1,8 +1,5 @@
-active_plugins:
-  node_search: node_search
-  user_search: user_search
 and_or_limit: 7
-default_plugin: node_search
+default_type: node_search
 index:
   cron_limit: 100
   overlap_cjk: true
diff --git a/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php b/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php
index b36d88e..cfd59f2 100644
--- a/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php
+++ b/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php
@@ -8,8 +8,8 @@
 namespace Drupal\search\Access;
 
 use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\search\SearchPluginManager;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
 
@@ -19,20 +19,20 @@
 class SearchAccessCheck implements StaticAccessCheckInterface {
 
   /**
-   * The search plugin manager.
+   * The search storage.
    *
-   * @var \Drupal\search\SearchPluginManager
+   * @var \Drupal\search\SearchStorageInterface
    */
-  protected $searchManager;
+  protected $searchStorage;
 
   /**
-   * Contructs a new search access check.
+   * Constructs a new search route subscriber.
    *
-   * @param SearchPluginManager $search_plugin_manager
-   *   The search plugin manager.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
    */
-  public function __construct(SearchPluginManager $search_plugin_manager) {
-    $this->searchManager = $search_plugin_manager;
+  public function __construct(EntityManagerInterface $entity_manager) {
+    $this->searchStorage = $entity_manager->getStorageController('search');
   }
 
   /**
@@ -46,7 +46,7 @@ public function appliesTo() {
    * {@inheritdoc}
    */
   public function access(Route $route, Request $request, AccountInterface $account) {
-    return $this->searchManager->getActiveDefinitions() ? static::ALLOW : static::DENY;
+    return $this->searchStorage->isSearchActive() ? static::ALLOW : static::DENY;
   }
 
 }
diff --git a/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php b/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php
index 9ecda1d..b297c1b 100644
--- a/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php
+++ b/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php
@@ -27,8 +27,8 @@ public function appliesTo() {
    * {@inheritdoc}
    */
   public function access(Route $route, Request $request, AccountInterface $account) {
-    $plugin_id = $route->getRequirement('_search_plugin_view_access');
-    return $this->searchManager->pluginAccess($plugin_id, $account) ? static::ALLOW : static::DENY;
+    $entity_id = $route->getRequirement('_search_plugin_view_access');
+    return $this->searchStorage->load($entity_id)->access('view', $account) ? static::ALLOW : static::DENY;
   }
 
 }
diff --git a/core/modules/search/lib/Drupal/search/Controller/SearchController.php b/core/modules/search/lib/Drupal/search/Controller/SearchController.php
index 98dae2b..1fb80f4 100644
--- a/core/modules/search/lib/Drupal/search/Controller/SearchController.php
+++ b/core/modules/search/lib/Drupal/search/Controller/SearchController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Form\FormBuilderInterface;
+use Drupal\search\SearchInterface;
 use Drupal\search\SearchPluginManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
@@ -61,7 +62,7 @@ public static function create(ContainerInterface $container) {
    *
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   The request object.
-   * @param string $plugin_id
+   * @param string $entity_id
    *   The ID of a search plugin.
    * @param string $keys
    *   Search keywords.
@@ -69,7 +70,7 @@ public static function create(ContainerInterface $container) {
    * @return array|\Symfony\Component\HttpFoundation\RedirectResponse
    *   The search form and search results or redirect response.
    */
-  public function view(Request $request, $plugin_id = NULL, $keys = NULL) {
+  public function view(Request $request, $entity_id = NULL, $keys = NULL) {
     $info = FALSE;
     $keys = trim($keys);
     // Also try to pull search keywords from the request to support old GET
@@ -79,28 +80,16 @@ public function view(Request $request, $plugin_id = NULL, $keys = NULL) {
     }
     $build['#title'] = $this->t('Search');
 
-    if (!empty($plugin_id)) {
-      $active_plugin_info = $this->searchManager->getActiveDefinitions();
-      if (isset($active_plugin_info[$plugin_id])) {
-        $info = $active_plugin_info[$plugin_id];
-      }
-    }
-
-    if (empty($plugin_id) || empty($info)) {
+    if (empty($entity_id)) {
       // No path or invalid path: find the default plugin. Note that if there
       // are no enabled search plugins, this function should never be called,
       // since hook_menu() would not have defined any search paths.
-      $info = search_get_default_plugin_info();
-      // Redirect from bare /search or an invalid path to the default search
-      // path.
-      $path = 'search/' . $info['path'];
-      if ($keys) {
-        $path .= '/' . $keys;
-      }
+      $entity_id = search_get_default_type();
 
-      return $this->redirect('search.view_' . $info['id']);
+      return $this->redirect('search.view_' . $entity_id);
     }
-    $plugin = $this->searchManager->createInstance($plugin_id);
+    /** @var $plugin \Drupal\search\Plugin\SearchInterface */
+    $plugin = entity_load('search', $entity_id)->getPlugin();
     $plugin->setSearch($keys, $request->query->all(), $request->attributes->all());
     // Default results output is an empty string.
     $results = array('#markup' => '');
@@ -126,4 +115,54 @@ public function view(Request $request, $plugin_id = NULL, $keys = NULL) {
     return $build;
   }
 
+  /**
+   * Route title callback.
+   *
+   * @param \Drupal\search\SearchInterface $search
+   *   The search entity.
+   *
+   * @return string
+   *   The title for the search edit form.
+   */
+  public function editTitle(SearchInterface $search) {
+    return $this->t('Edit @label search', array('@label' => $search->label()));
+  }
+
+  /**
+   * Provides a list of eligible search plugins for configuring.
+   *
+   * @return array
+   *   A list of search plugins that can be configured.
+   */
+  public function addSearchType() {
+    $search_types = array();
+    foreach ($this->searchManager->getDefinitions() as $plugin_id => $search_info) {
+      $search_types[$plugin_id] = array(
+        'title' => $search_info['title'],
+        'href' => 'admin/config/search/settings/add/' . $plugin_id,
+        'localized_options' => array(),
+      );
+    }
+    return array(
+      '#theme' => 'admin_block_content',
+      '#content' => $search_types,
+    );
+  }
+
+  /**
+   * Performs an operation on the search entity.
+   *
+   * @param \Drupal\search\SearchInterface $search
+   *   The search entity.
+   * @param string $op
+   *   The operation to perform, usually 'enable' or 'disable'.
+   *
+   * @return \Symfony\Component\HttpFoundation\RedirectResponse
+   *   A redirect back to the search settings page.
+   */
+  public function performOperation(SearchInterface $search, $op) {
+    $search->$op()->save();
+    return $this->redirect('search.settings');
+  }
+
 }
diff --git a/core/modules/search/lib/Drupal/search/Entity/Search.php b/core/modules/search/lib/Drupal/search/Entity/Search.php
new file mode 100644
index 0000000..9601c60
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/Entity/Search.php
@@ -0,0 +1,229 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search\Entity\Search.
+ */
+
+namespace Drupal\search\Entity;
+
+use Drupal\Component\Plugin\DefaultSinglePluginBag;
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\search\SearchInterface;
+
+/**
+ * @todo.
+ *
+ * @EntityType(
+ *   id = "search",
+ *   label = @Translation("Search"),
+ *   controllers = {
+ *     "access" = "Drupal\search\SearchAccessController",
+ *     "storage" = "Drupal\search\SearchStorage",
+ *     "list" = "Drupal\search\SearchListController",
+ *     "form" = {
+ *       "add" = "Drupal\search\Form\SearchAddForm",
+ *       "edit" = "Drupal\search\Form\SearchEditForm",
+ *       "delete" = "Drupal\search\Form\SearchDeleteForm"
+ *     }
+ *   },
+ *   admin_permission = "administer search",
+ *   config_prefix = "search.search",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid",
+ *     "status" = "status"
+ *   }
+ * )
+ */
+class Search extends ConfigEntityBase implements SearchInterface {
+
+  /**
+   * The name (plugin ID) of the search entity.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The label of the search entity.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The UUID of the search entity.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The configuration of the search entity.
+   *
+   * @var array
+   */
+  protected $configuration = array();
+
+  /**
+   * The search plugin ID.
+   *
+   * @var string
+   */
+  protected $plugin;
+
+  /**
+   * @var string
+   */
+  protected $path;
+
+  /**
+   * @var string
+   */
+  protected $title;
+
+  /**
+   * The plugin bag that stores search plugins.
+   *
+   * @var \Drupal\Component\Plugin\DefaultPluginBag
+   */
+  protected $pluginBag;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $values, $entity_type) {
+    parent::__construct($values, $entity_type);
+
+    $this->pluginBag = new DefaultSinglePluginBag(\Drupal::service('plugin.manager.search'), array($this->plugin), $this->configuration);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPlugin() {
+    return $this->pluginBag->get($this->plugin);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPlugin($plugin_id) {
+    $this->plugin = $plugin_id;
+    $this->pluginBag->addInstanceID($plugin_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPluginDefinition() {
+    return $this->getPlugin()->getPluginDefinition();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isConfigurable() {
+    return $this->getPlugin() instanceof ConfigurablePluginInterface;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isDefaultSearch() {
+    return \Drupal::config('search.settings')->get('default_type') == $this->id();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTitle() {
+    return $this->title;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPath() {
+    return $this->path;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProvider() {
+    $definition = $this->getPluginDefinition();
+    return $definition['provider'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function uri() {
+    // @todo replace with a route name and parameters.
+    return array(
+      'path' => 'admin/config/search/settings/manage/' . $this->id(),
+      'options' => array(
+        'entity_type' => $this->entityType,
+        'entity' => $this,
+      ),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getExportProperties() {
+    $properties = parent::getExportProperties();
+    $names = array(
+      'path',
+      'title',
+      'plugin',
+      'configuration',
+    );
+    foreach ($names as $name) {
+      $properties[$name] = $this->get($name);
+    }
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preSave(EntityStorageControllerInterface $storage_controller) {
+    parent::preSave($storage_controller);
+
+    $plugin = $this->getPlugin();
+    // If this plugin has any configuration, ensure that it is set.
+    if ($plugin instanceof ConfigurablePluginInterface) {
+      $this->set('configuration', $plugin->getConfiguration());
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    parent::postSave($storage_controller, $update);
+
+    \Drupal::state()->set('menu_rebuild_needed', TRUE);
+    \Drupal::cache('cache')->deleteTags(array('local_task' => 1));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function sort($a, $b) {
+    $a_status = (int) $a->status();
+    $b_status = (int) $b->status();
+    if ($a_status != $b_status) {
+      return ($a_status > $b_status) ? -1 : 1;
+    }
+    return strnatcasecmp($a->label(), $b->label());
+  }
+
+}
diff --git a/core/modules/search/lib/Drupal/search/Form/SearchAddForm.php b/core/modules/search/lib/Drupal/search/Form/SearchAddForm.php
new file mode 100644
index 0000000..f4960a8
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/Form/SearchAddForm.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search\Form\SearchAddForm.
+ */
+
+namespace Drupal\search\Form;
+
+/**
+ * @todo.
+ */
+class SearchAddForm extends SearchFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state, $search_plugin_id = NULL) {
+    $this->entity->setPlugin($search_plugin_id);
+    $definition = $this->entity->getPluginDefinition();
+    $this->entity->set('path', $definition['path']);
+    $this->entity->set('label', $definition['title']);
+    $this->entity->set('title', $definition['title']);
+    return parent::buildForm($form, $form_state);
+  }
+
+}
diff --git a/core/modules/search/lib/Drupal/search/Form/SearchBlockForm.php b/core/modules/search/lib/Drupal/search/Form/SearchBlockForm.php
index c943c93..7bb2332 100644
--- a/core/modules/search/lib/Drupal/search/Form/SearchBlockForm.php
+++ b/core/modules/search/lib/Drupal/search/Form/SearchBlockForm.php
@@ -62,17 +62,17 @@ public function submitForm(array &$form, array &$form_state) {
     }
 
     $form_id = $form['form_id']['#value'];
-    $info = search_get_default_plugin_info();
-    if ($info) {
+    if ($entity = search_get_default_type()) {
       $form_state['redirect_route'] = array(
-        'route_name' => 'search.view_' . $info['id'],
+        'route_name' => 'search.view_' . $entity,
         'route_parameters' => array(
           'keys' => trim($form_state['values'][$form_id]),
         ),
       );
     }
     else {
-      $this->setFormError('', $form_state, $this->t('Search is currently disabled.'), 'error');
+      $this->setFormError('', $form_state, $this->t('Search is currently disabled.'));
     }
   }
+
 }
diff --git a/core/modules/search/lib/Drupal/search/Form/SearchDeleteForm.php b/core/modules/search/lib/Drupal/search/Form/SearchDeleteForm.php
new file mode 100644
index 0000000..e137f9c
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/Form/SearchDeleteForm.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\search\Form\SearchDeleteForm.
+ */
+
+namespace Drupal\search\Form;
+
+use Drupal\Core\Entity\EntityConfirmFormBase;
+
+/**
+ * Provides a deletion confirm form for search.
+ */
+class SearchDeleteForm extends EntityConfirmFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to delete the %label search?', array('%label' => $this->entity->label()));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelRoute() {
+    return array(
+      'route_name' => 'search.settings',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Delete');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submit(array $form, array &$form_state) {
+    $this->entity->delete();
+    $form_state['redirect'] = 'admin/config/search/settings';
+    drupal_set_message($this->t('The %label search has been deleted.', array('%label' => $this->entity->label())));
+  }
+
+}
diff --git a/core/modules/search/lib/Drupal/search/Form/SearchEditForm.php b/core/modules/search/lib/Drupal/search/Form/SearchEditForm.php
new file mode 100644
index 0000000..a897251
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/Form/SearchEditForm.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search\Form\SearchEditForm.
+ */
+
+namespace Drupal\search\Form;
+
+/**
+ * @todo.
+ */
+class SearchEditForm extends SearchFormBase {
+
+}
diff --git a/core/modules/search/lib/Drupal/search/Form/SearchFormBase.php b/core/modules/search/lib/Drupal/search/Form/SearchFormBase.php
new file mode 100644
index 0000000..5641cc4
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/Form/SearchFormBase.php
@@ -0,0 +1,189 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search\Form\SearchFormBase.
+ */
+
+namespace Drupal\search\Form;
+
+use Drupal\Core\Entity\EntityFormController;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\Core\Plugin\PluginFormInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @todo.
+ */
+abstract class SearchFormBase extends EntityFormController {
+
+  /**
+   * The entity being used by this form.
+   *
+   * @var \Drupal\search\SearchInterface
+   */
+  protected $entity;
+
+  /**
+   * The search plugin being configured.
+   *
+   * @var \Drupal\search\Plugin\SearchInterface
+   */
+  protected $plugin;
+
+  /**
+   * The entity query factory.
+   *
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  protected $entityQuery;
+
+  /**
+   * Constructs a new search form.
+   *
+   * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query
+   *   The entity query.
+   */
+  public function __construct(QueryFactory $entity_query) {
+    $this->entityQuery = $entity_query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.query')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBaseFormID() {
+    return 'search_entity_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state) {
+    $this->plugin = $this->entity->getPlugin();
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, array &$form_state) {
+    $form['label'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#description' => $this->t('The administrative label for this search'),
+      '#default_value' => $this->entity->label(),
+      '#maxlength' => '255',
+    );
+
+    $form['id'] = array(
+      '#type' => 'machine_name',
+      '#default_value' => $this->entity->id(),
+      '#disabled' => !$this->entity->isNew(),
+      '#maxlength' => 64,
+      '#machine_name' => array(
+        'exists' => array($this, 'exists'),
+      ),
+    );
+    $form['title'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Menu title'),
+      '#description' => $this->t('The title used by the local tab on the search page'),
+      '#default_value' => $this->entity->getTitle(),
+      '#maxlength' => '255',
+    );
+    $form['path'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Path'),
+      '#field_prefix' => 'search/',
+      '#default_value' => $this->entity->getPath(),
+      '#maxlength' => '255',
+    );
+    $form['plugin'] = array(
+      '#type' => 'value',
+      '#value' => $this->entity->get('plugin'),
+    );
+
+    if ($this->plugin instanceof PluginFormInterface) {
+      $form += $this->plugin->buildConfigurationForm($form, $form_state);
+    }
+
+    return parent::form($form, $form_state);
+  }
+
+  /**
+   * Determines if the search entity already exists.
+   *
+   * @param string $id
+   *   The search configuration ID.
+   *
+   * @return bool
+   *   TRUE if the search configuration exists, FALSE otherwise.
+   */
+  public function exists($id) {
+    $entity = $this->entityQuery->get('search')
+      ->condition('id', $id)
+      ->execute();
+    return (bool) $entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function actions(array $form, array &$form_state) {
+    $actions = parent::actions($form, $form_state);
+    unset($actions['delete']);
+    return $actions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate(array $form, array &$form_state) {
+    parent::validate($form, $form_state);
+
+    // Ensure each path is unique.
+    $path = $this->entityQuery->get('search')
+      ->condition('path', $form_state['values']['path'])
+      ->condition('id', $form_state['values']['id'], '<>')
+      ->execute();
+    if ($path) {
+      $this->setFormError('path', $this->t('The search path must be unique'));
+    }
+
+    if ($this->plugin instanceof PluginFormInterface) {
+      $this->plugin->validateConfigurationForm($form, $form_state);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submit(array $form, array &$form_state) {
+    parent::submit($form, $form_state);
+
+    if ($this->plugin instanceof PluginFormInterface) {
+      $this->plugin->submitConfigurationForm($form, $form_state);
+    }
+    return $this->entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, array &$form_state) {
+    $this->entity->save();
+    drupal_set_message($this->t('The search has been successfully saved.'));
+
+    $form_state['redirect_route']['route_name'] = 'search.settings';
+  }
+
+}
diff --git a/core/modules/search/lib/Drupal/search/Form/SearchSettingsForm.php b/core/modules/search/lib/Drupal/search/Form/SearchSettingsForm.php
deleted file mode 100644
index d7e566f..0000000
--- a/core/modules/search/lib/Drupal/search/Form/SearchSettingsForm.php
+++ /dev/null
@@ -1,281 +0,0 @@
-<?php
-/**
- * @file
- * Contains \Drupal\search\Form\SearchSettingsForm.
- */
-
-namespace Drupal\search\Form;
-
-use Drupal\Core\Cache\Cache;
-use Drupal\Core\Config\ConfigFactory;
-use Drupal\Core\Config\Context\ContextInterface;
-use Drupal\Core\Extension\ModuleHandlerInterface;
-use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
-use Drupal\Core\Plugin\PluginFormInterface;
-use Drupal\search\SearchPluginManager;
-use Drupal\Core\Form\ConfigFormBase;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Configure search settings for this site.
- */
-class SearchSettingsForm extends ConfigFormBase {
-
-  /**
-   * A configuration object with the current search settings.
-   *
-   * @var \Drupal\Core\Config\Config
-   */
-  protected $searchSettings;
-
-  /**
-   * A search plugin manager object.
-   *
-   * @var \Drupal\search\SearchPluginManager
-   */
-  protected $searchPluginManager;
-
-  /**
-   * The module handler.
-   *
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface
-   */
-  protected $moduleHandler;
-
-  /**
-   * The Drupal state storage service.
-   *
-   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
-   */
-  protected $state;
-
-  /**
-   * Constructs a \Drupal\search\Form\SearchSettingsForm object.
-   *
-   * @param \Drupal\Core\Config\ConfigFactory $config_factory
-   *   The configuration factory object that manages search settings.
-   * @param \Drupal\Core\Config\Context\ContextInterface $context
-   *   The context interface
-   * @param \Drupal\search\SearchPluginManager $manager
-   *   The manager for search plugins.
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   *   The module handler
-   * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state
-   *   The state key/value store interface, gives access to state based config settings.
-   */
-  public function __construct(ConfigFactory $config_factory, ContextInterface $context, SearchPluginManager $manager, ModuleHandlerInterface $module_handler, KeyValueStoreInterface $state) {
-    parent::__construct($config_factory, $context);
-    $this->searchSettings = $config_factory->get('search.settings');
-    $this->searchPluginManager = $manager;
-    $this->moduleHandler = $module_handler;
-    $this->state = $state;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('config.factory'),
-      $container->get('config.context.free'),
-      $container->get('plugin.manager.search'),
-      $container->get('module_handler'),
-      $container->get('state')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'search_admin_settings';
-  }
-
-  /**
-   * Returns names of available search plugins.
-   *
-   * @return array
-   *   An array of the names of available search plugins.
-   */
-  protected function getOptions() {
-    $options = array();
-    foreach ($this->searchPluginManager->getDefinitions() as $plugin_id => $search_info) {
-      $options[$plugin_id] = $search_info['title'] . ' (' . $plugin_id . ')';
-    }
-    asort($options, SORT_STRING);
-    return $options;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, array &$form_state) {
-
-    // Collect some stats.
-    $remaining = 0;
-    $total = 0;
-
-    foreach ($this->searchPluginManager->getActiveIndexingPlugins() as $plugin) {
-      if ($status = $plugin->indexStatus()) {
-        $remaining += $status['remaining'];
-        $total += $status['total'];
-      }
-    }
-    $active_plugins = $this->searchPluginManager->getActivePlugins();
-    $this->moduleHandler->loadAllIncludes('admin.inc');
-    $count = format_plural($remaining, 'There is 1 item left to index.', 'There are @count items left to index.');
-    $percentage = ((int) min(100, 100 * ($total - $remaining) / max(1, $total))) . '%';
-    $status = '<p><strong>' . $this->t('%percentage of the site has been indexed.', array('%percentage' => $percentage)) . ' ' . $count . '</strong></p>';
-    $form['status'] = array(
-      '#type' => 'details',
-      '#title' => $this->t('Indexing status'),
-    );
-    $form['status']['status'] = array('#markup' => $status);
-    $form['status']['wipe'] = array(
-      '#type' => 'submit',
-      '#value' => $this->t('Re-index site'),
-      '#submit' => array(array($this, 'searchAdminReindexSubmit')),
-    );
-
-    $items = drupal_map_assoc(array(10, 20, 50, 100, 200, 500));
-
-    // Indexing throttle:
-    $form['indexing_throttle'] = array(
-      '#type' => 'details',
-      '#title' => $this->t('Indexing throttle')
-    );
-    $form['indexing_throttle']['cron_limit'] = array(
-      '#type' => 'select',
-      '#title' => $this->t('Number of items to index per cron run'),
-      '#default_value' => $this->searchSettings->get('index.cron_limit'),
-      '#options' => $items,
-      '#description' => $this->t('The maximum number of items indexed in each pass of a <a href="@cron">cron maintenance task</a>. If necessary, reduce the number of items to prevent timeouts and memory errors while indexing.', array('@cron' => $this->url('system.status')))
-    );
-    // Indexing settings:
-    $form['indexing_settings'] = array(
-      '#type' => 'details',
-      '#title' => $this->t('Indexing settings')
-    );
-    $form['indexing_settings']['info'] = array(
-      '#markup' => $this->t('<p><em>Changing the settings below will cause the site index to be rebuilt. The search index is not cleared but systematically updated to reflect the new settings. Searching will continue to work but new content won\'t be indexed until all existing content has been re-indexed.</em></p><p><em>The default settings should be appropriate for the majority of sites.</em></p>')
-    );
-    $form['indexing_settings']['minimum_word_size'] = array(
-      '#type' => 'number',
-      '#title' => $this->t('Minimum word length to index'),
-      '#default_value' => $this->searchSettings->get('index.minimum_word_size'),
-      '#min' => 1,
-      '#max' => 1000,
-      '#description' => $this->t('The number of characters a word has to be to be indexed. A lower setting means better search result ranking, but also a larger database. Each search query must contain at least one keyword that is this size (or longer).')
-    );
-    $form['indexing_settings']['overlap_cjk'] = array(
-      '#type' => 'checkbox',
-      '#title' => $this->t('Simple CJK handling'),
-      '#default_value' => $this->searchSettings->get('index.overlap_cjk'),
-      '#description' => $this->t('Whether to apply a simple Chinese/Japanese/Korean tokenizer based on overlapping sequences. Turn this off if you want to use an external preprocessor for this instead. Does not affect other languages.')
-    );
-
-    $form['active'] = array(
-      '#type' => 'details',
-      '#title' => $this->t('Active search plugins')
-    );
-    $options = $this->getOptions();
-    $form['active']['active_plugins'] = array(
-      '#type' => 'checkboxes',
-      '#title' => $this->t('Active plugins'),
-      '#title_display' => 'invisible',
-      '#default_value' => $this->searchSettings->get('active_plugins'),
-      '#options' => $options,
-      '#description' => $this->t('Choose which search plugins are active from the available plugins.')
-    );
-    $form['active']['default_plugin'] = array(
-      '#title' => $this->t('Default search plugin'),
-      '#type' => 'radios',
-      '#default_value' => $this->searchSettings->get('default_plugin'),
-      '#options' => $options,
-      '#description' => $this->t('Choose which search plugin is the default.')
-    );
-
-    // Per plugin settings.
-    foreach ($active_plugins as $plugin) {
-      if ($plugin instanceof PluginFormInterface) {
-        $form = $plugin->buildConfigurationForm($form, $form_state);
-      }
-    }
-    // Set #submit so we are sure it's invoked even if one of
-    // the active search plugins added its own #submit.
-    $form['#submit'][] = array($this, 'submitForm');
-
-    return parent::buildForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, array &$form_state) {
-    parent::validateForm($form, $form_state);
-
-    // Check whether we selected a valid default.
-    if ($form_state['triggering_element']['#value'] != $this->t('Reset to defaults')) {
-      $new_plugins = array_filter($form_state['values']['active_plugins']);
-      $default = $form_state['values']['default_plugin'];
-      if (!in_array($default, $new_plugins, TRUE)) {
-        $this->setFormError('default_plugin', $form_state, $this->t('Your default search plugin is not selected as an active plugin.'));
-      }
-    }
-    // Handle per-plugin validation logic.
-    foreach ($this->searchPluginManager->getActivePlugins() as $plugin) {
-      if ($plugin instanceof PluginFormInterface) {
-        $plugin->validateConfigurationForm($form, $form_state);
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, array &$form_state) {
-    parent::submitForm($form, $form_state);
-
-    // If these settings change, the index needs to be rebuilt.
-    if (($this->searchSettings->get('index.minimum_word_size') != $form_state['values']['minimum_word_size']) || ($this->searchSettings->get('index.overlap_cjk') != $form_state['values']['overlap_cjk'])) {
-      $this->searchSettings->set('index.minimum_word_size', $form_state['values']['minimum_word_size']);
-      $this->searchSettings->set('index.overlap_cjk', $form_state['values']['overlap_cjk']);
-      drupal_set_message($this->t('The index will be rebuilt.'));
-      search_reindex();
-    }
-    $this->searchSettings->set('index.cron_limit', $form_state['values']['cron_limit']);
-    $this->searchSettings->set('default_plugin', $form_state['values']['default_plugin']);
-
-    // Handle per-plugin submission logic.
-    foreach ($this->searchPluginManager->getActivePlugins() as $plugin) {
-      if ($plugin instanceof PluginFormInterface) {
-        $plugin->submitConfigurationForm($form, $form_state);
-      }
-    }
-
-    // Check whether we are resetting the values.
-    if ($form_state['triggering_element']['#value'] == $this->t('Reset to defaults')) {
-      $new_plugins = array('node_search', 'user_search');
-    }
-    else {
-      $new_plugins = array_filter($form_state['values']['active_plugins']);
-    }
-    if ($this->searchSettings->get('active_plugins') != $new_plugins) {
-      $this->searchSettings->set('active_plugins', $new_plugins);
-      drupal_set_message($this->t('The active search plugins have been changed.'));
-      $this->state->set('menu_rebuild_needed', TRUE);
-      Cache::deleteTags(array('local_task' => TRUE));
-    }
-    $this->searchSettings->save();
-  }
-
-  /**
-   * Form submission handler for the reindex button on the search admin settings
-   * form.
-   */
-  public function searchAdminReindexSubmit(array $form, array &$form_state) {
-    // send the user to the confirmation page
-    $form_state['redirect_route']['route_name'] = 'search.reindex_confirm';
-  }
-
-}
diff --git a/core/modules/search/lib/Drupal/search/Plugin/ConfigurableSearchPluginBase.php b/core/modules/search/lib/Drupal/search/Plugin/ConfigurableSearchPluginBase.php
new file mode 100644
index 0000000..ef771d9
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/Plugin/ConfigurableSearchPluginBase.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search\Plugin\ConfigurableSearchPluginBase.
+ */
+
+namespace Drupal\search\Plugin;
+
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+
+/**
+ * @todo.
+ */
+abstract class ConfigurableSearchPluginBase extends SearchPluginBase implements ConfigurablePluginInterface, PluginFormInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->configuration += $this->defaultConfiguration();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfiguration() {
+    return $this->configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfiguration(array $configuration) {
+    $this->configuration = $configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, array &$form_state) {
+  }
+
+}
diff --git a/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php b/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php
index f18e699..6adf06d 100644
--- a/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php
+++ b/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php
@@ -20,19 +20,22 @@ class SearchLocalTask extends DerivativeBase {
   public function getDerivativeDefinitions(array $base_plugin_definition) {
     $this->derivatives = array();
 
-    $default_info = search_get_default_plugin_info();
-    if ($default_info) {
-      foreach (\Drupal::service('plugin.manager.search')->getActiveDefinitions() as $plugin_id => $search_info) {
-        $this->derivatives[$plugin_id] = array(
-          'title' => $search_info['title'],
-          'route_name' => 'search.view_' . $plugin_id,
-          'tab_root_id' => 'search.plugins:' . $default_info['id'],
+    if ($default = \Drupal::config('search.settings')->get('default_type')) {
+      $search_storage = \Drupal::entityManager()->getStorageController('search');
+      foreach ($search_storage->getActiveSearches() as $entity_id => $entity) {
+        /** @var $entity \Drupal\search\SearchInterface */
+        $plugin = $entity->getPluginDefinition();
+        // @todo Swap the plugin title and entity label.
+        $this->derivatives[$entity_id] = array(
+          'title' => $plugin['title'],
+          'route_name' => 'search.view_' . $entity_id,
+          'tab_root_id' => 'search.local_tasks:' . $default,
         );
-        if ($plugin_id == $default_info['id']) {
-          $this->derivatives[$plugin_id]['weight'] = -10;
+        if ($entity_id == $default) {
+          $this->derivatives[$entity_id]['weight'] = -10;
         }
         else {
-          $this->derivatives[$plugin_id]['weight'] = 0;
+          $this->derivatives[$entity_id]['weight'] = 0;
         }
       }
     }
diff --git a/core/modules/search/lib/Drupal/search/Plugin/SearchPluginBase.php b/core/modules/search/lib/Drupal/search/Plugin/SearchPluginBase.php
index 7f534d5..ec808ad 100644
--- a/core/modules/search/lib/Drupal/search/Plugin/SearchPluginBase.php
+++ b/core/modules/search/lib/Drupal/search/Plugin/SearchPluginBase.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Plugin\PluginBase;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Defines a base class for plugins wishing to support search.
@@ -39,6 +40,13 @@
   /**
    * {@inheritdoc}
    */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+    return new static($configuration, $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function setSearch($keywords, array $parameters, array $attributes) {
     $this->keywords = (string) $keywords;
     $this->searchParameters = $parameters;
diff --git a/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php b/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php
index 258d8db..ee62b82 100644
--- a/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php
+++ b/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\search\Routing;
 
+use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Routing\RouteSubscriberBase;
-use Drupal\search\SearchPluginManager;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
 
@@ -18,41 +18,41 @@
 class SearchRouteSubscriber extends RouteSubscriberBase {
 
   /**
-   * The search plugin manager.
+   * The search storage.
    *
-   * @var \Drupal\search\SearchPluginManager
+   * @var \Drupal\search\SearchStorageInterface
    */
-  protected $searchManager;
+  protected $searchStorage;
 
   /**
    * Constructs a new search route subscriber.
    *
-   * @param \Drupal\search\SearchPluginManager $search_plugin_manager
-   *   The search plugin manager.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
    */
-  public function __construct(SearchPluginManager $search_plugin_manager) {
-    $this->searchManager = $search_plugin_manager;
+  public function __construct(EntityManagerInterface $entity_manager) {
+    $this->searchStorage = $entity_manager->getStorageController('search');
   }
 
   /**
    * {@inheritdoc}
    */
   protected function routes(RouteCollection $collection) {
-    foreach ($this->searchManager->getActiveDefinitions() as $plugin_id => $search_info) {
-      $path = 'search/' . $search_info['path'] . '/{keys}';
+    foreach ($this->searchStorage->getActiveSearches() as $entity_id => $entity) {
+      $path = 'search/' . $entity->getPath() . '/{keys}';
       $defaults = array(
         '_content' => 'Drupal\search\Controller\SearchController::view',
-        '_title' => $search_info['title'],
-        'plugin_id' => $plugin_id,
+        '_title' => $entity->label(),
+        'entity_id' => $entity_id,
         'keys' => '',
       );
       $requirements = array(
         'keys' => '.+',
-        '_search_plugin_view_access' => $plugin_id,
+        '_search_plugin_view_access' => $entity_id,
         '_permission' => 'search content',
       );
       $route = new Route($path, $defaults, $requirements);
-      $collection->add('search.view_' . $plugin_id, $route);
+      $collection->add('search.view_' . $entity_id, $route);
     }
   }
 
diff --git a/core/modules/search/lib/Drupal/search/SearchAccessController.php b/core/modules/search/lib/Drupal/search/SearchAccessController.php
new file mode 100644
index 0000000..9f6dd51
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/SearchAccessController.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search\SearchAccessController.
+ */
+
+namespace Drupal\search;
+
+use Drupal\Core\Access\AccessibleInterface;
+use Drupal\Core\Entity\EntityAccessController;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Defines the access controller for the search entity type.
+ */
+class SearchAccessController extends EntityAccessController {
+
+  protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
+    if (in_array($operation, array('delete', 'disable')) && $entity->isDefaultSearch()) {
+      return FALSE;
+    }
+    if ($operation == 'view') {
+      if (!$entity->status()) {
+        return FALSE;
+      }
+      $plugin = $entity->getPlugin();
+      if ($plugin instanceof AccessibleInterface) {
+        return $plugin->access($operation, $account);
+      }
+      return TRUE;
+    }
+    return parent::checkAccess($entity, $operation, $langcode, $account);
+  }
+
+}
diff --git a/core/modules/search/lib/Drupal/search/SearchInterface.php b/core/modules/search/lib/Drupal/search/SearchInterface.php
new file mode 100644
index 0000000..19618e1
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/SearchInterface.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search\SearchInterface.
+ */
+
+namespace Drupal\search;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+
+/**
+ * Provides an interface defining a search entity.
+ */
+interface SearchInterface extends ConfigEntityInterface {
+
+  /**
+   * Returns whether or not this search entity is configurable.
+   *
+   * @return bool
+   *   TRUE if this search entity is configurable, FALSE otherwise.
+   */
+  public function isConfigurable();
+
+  /**
+   * Returns the search plugin.
+   *
+   * @return \Drupal\search\Plugin\SearchInterface
+   *   The search plugin used by this search entity.
+   */
+  public function getPlugin();
+
+  /**
+   * Sets the search plugin.
+   *
+   * @param string $plugin_id
+   *   The search plugin ID.
+   */
+  public function setPlugin($plugin_id);
+
+  /**
+   * Returns the definition of the search plugin.
+   *
+   * @return array
+   *   The plugin definition.
+   */
+  public function getPluginDefinition();
+
+  /**
+   * Determines if this search entity is currently the default search.
+   *
+   * @return bool
+   *   TRUE if this search entity is the default search, FALSE otherwise.
+   */
+  public function isDefaultSearch();
+
+  /**
+   * @return string
+   */
+  public function getPath();
+
+  /**
+   * @return string
+   */
+  public function getProvider();
+
+  /**
+   * @return string
+   */
+  public function getTitle();
+
+}
diff --git a/core/modules/search/lib/Drupal/search/SearchListController.php b/core/modules/search/lib/Drupal/search/SearchListController.php
new file mode 100644
index 0000000..023c63b
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/SearchListController.php
@@ -0,0 +1,289 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search\SearchListController.
+ */
+
+namespace Drupal\search;
+
+use Drupal\Component\Utility\MapArray;
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Config\Context\ContextInterface;
+use Drupal\Core\Config\Entity\ConfigEntityListController;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Form\FormBuilderInterface;
+use Drupal\Core\Form\FormInterface;
+use Drupal\search\Plugin\SearchIndexingInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a listing of search entities.
+ */
+class SearchListController extends ConfigEntityListController implements FormInterface {
+
+  /**
+   * Stores the configuration factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactory
+   */
+  protected $configFactory;
+
+  /**
+   * The form builder.
+   *
+   * @var \Drupal\Core\Form\FormBuilderInterface
+   */
+  protected $formBuilder;
+
+  /**
+   * Constructs a new SearchListController object.
+   *
+   * @param string $entity_type
+   *   The type of entity to be listed.
+   * @param array $entity_info
+   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
+   *   The entity storage controller class.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke hooks on.
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\Context\ContextInterface $context
+   *   The configuration context to use.
+   * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
+   *   The form builder.
+   */
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, ConfigFactory $config_factory, ContextInterface $context, FormBuilderInterface $form_builder) {
+    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+
+    $this->configFactory = $config_factory;
+    $this->configFactory->enterContext($context);
+
+    $this->formBuilder = $form_builder;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+    return new static(
+      $entity_type,
+      $entity_info,
+      $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('module_handler'),
+      $container->get('config.factory'),
+      $container->get('config.context.free'),
+      $container->get('form_builder')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    return 'search_admin_settings';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render() {
+    return $this->formBuilder->getForm($this);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildHeader() {
+    $header['label'] = array(
+      'data' => $this->t('Label'),
+    );
+    $header['plugin'] = array(
+      'data' => $this->t('Plugin'),
+      'class' => array(RESPONSIVE_PRIORITY_LOW),
+    );
+    $header['status'] = array(
+      'data' => $this->t('Status'),
+      'class' => array(RESPONSIVE_PRIORITY_LOW),
+    );
+    return $header + parent::buildHeader();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRow(EntityInterface $entity) {
+    $row['label'] = $this->getLabel($entity);
+    $definition = $entity->getPlugin()->getPluginDefinition();
+    $row['plugin'] = $definition['title'];
+    $row['status'] = $entity->status() ? $this->t('Enabled') : $this->t('Disabled');
+    return $row + parent::buildRow($entity);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state) {
+    $search_settings = $this->configFactory->get('search.settings');
+    // Collect some stats.
+    $remaining = 0;
+    $total = 0;
+    $entities = $this->load();
+    foreach ($entities as $entity) {
+      if ($entity instanceof SearchIndexingInterface && $entity->status() && $status = $entity->getPlugin()->indexStatus()) {
+        $remaining += $status['remaining'];
+        $total += $status['total'];
+      }
+    }
+
+    $this->moduleHandler->loadAllIncludes('admin.inc');
+    $count = format_plural($remaining, 'There is 1 item left to index.', 'There are @count items left to index.');
+    $percentage = ((int) min(100, 100 * ($total - $remaining) / max(1, $total))) . '%';
+    $status = '<p><strong>' . $this->t('%percentage of the site has been indexed.', array('%percentage' => $percentage)) . ' ' . $count . '</strong></p>';
+    $form['status'] = array(
+      '#type' => 'details',
+      '#title' => $this->t('Indexing status'),
+    );
+    $form['status']['status'] = array('#markup' => $status);
+    $form['status']['wipe'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Re-index site'),
+      '#submit' => array(array($this, 'searchAdminReindexSubmit')),
+    );
+
+    $items = MapArray::copyValuesToKeys(array(10, 20, 50, 100, 200, 500));
+
+    // Indexing throttle:
+    $form['indexing_throttle'] = array(
+      '#type' => 'details',
+      '#title' => $this->t('Indexing throttle')
+    );
+    $form['indexing_throttle']['cron_limit'] = array(
+      '#type' => 'select',
+      '#title' => $this->t('Number of items to index per cron run'),
+      '#default_value' => $search_settings->get('index.cron_limit'),
+      '#options' => $items,
+      '#description' => $this->t('The maximum number of items indexed in each pass of a <a href="@cron">cron maintenance task</a>. If necessary, reduce the number of items to prevent timeouts and memory errors while indexing.', array('@cron' => $this->url('system.status')))
+    );
+    // Indexing settings:
+    $form['indexing_settings'] = array(
+      '#type' => 'details',
+      '#title' => $this->t('Indexing settings')
+    );
+    $form['indexing_settings']['info'] = array(
+      '#markup' => $this->t('<p><em>Changing the settings below will cause the site index to be rebuilt. The search index is not cleared but systematically updated to reflect the new settings. Searching will continue to work but new content won\'t be indexed until all existing content has been re-indexed.</em></p><p><em>The default settings should be appropriate for the majority of sites.</em></p>')
+    );
+    $form['indexing_settings']['minimum_word_size'] = array(
+      '#type' => 'number',
+      '#title' => $this->t('Minimum word length to index'),
+      '#default_value' => $search_settings->get('index.minimum_word_size'),
+      '#min' => 1,
+      '#max' => 1000,
+      '#description' => $this->t('The number of characters a word has to be to be indexed. A lower setting means better search result ranking, but also a larger database. Each search query must contain at least one keyword that is this size (or longer).')
+    );
+    $form['indexing_settings']['overlap_cjk'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Simple CJK handling'),
+      '#default_value' => $search_settings->get('index.overlap_cjk'),
+      '#description' => $this->t('Whether to apply a simple Chinese/Japanese/Korean tokenizer based on overlapping sequences. Turn this off if you want to use an external preprocessor for this instead. Does not affect other languages.')
+    );
+
+    $form['search_types'] = array(
+      '#type' => 'details',
+      '#title' => $this->t('Search types'),
+    );
+    $form['search_types']['inline_actions'] = array(
+      '#prefix' => '<ul class="action-links">',
+      '#suffix' => '</ul>',
+    );
+    $form['search_types']['inline_actions']['add'] = array(
+      '#theme' => 'menu_local_action',
+      '#link' => array(
+        'title' => $this->t('Add new search type'),
+        'href' => 'admin/config/search/settings/add',
+      ),
+    );
+    $rows = array();
+    foreach ($entities as $entity) {
+      $rows[$entity->id()] = $this->buildRow($entity);
+    }
+    $form['search_types']['default_type'] = array(
+      '#type' => 'tableselect',
+      '#header' => $this->buildHeader(),
+      '#options' => $rows,
+      '#required' => TRUE,
+      '#multiple' => FALSE,
+      '#default_value' => $search_settings->get('default_type'),
+      '#empty' => $this->t('No search has been configured.'),
+    );
+
+    $form['actions']['#type'] = 'actions';
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Save configuration'),
+      '#button_type' => 'primary',
+    );
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOperations(EntityInterface $entity) {
+    $operations = parent::getOperations($entity);
+
+    // Prevent the default search from being disabled or deleted.
+    if ($entity->isDefaultSearch()) {
+      unset($operations['disable'], $operations['delete']);
+    }
+
+    return $operations;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $search_settings = $this->configFactory->get('search.settings');
+    // If these settings change, the index needs to be rebuilt.
+    if (($search_settings->get('index.minimum_word_size') != $form_state['values']['minimum_word_size']) || ($search_settings->get('index.overlap_cjk') != $form_state['values']['overlap_cjk'])) {
+      $search_settings->set('index.minimum_word_size', $form_state['values']['minimum_word_size']);
+      $search_settings->set('index.overlap_cjk', $form_state['values']['overlap_cjk']);
+      drupal_set_message($this->t('The index will be rebuilt.'));
+      search_reindex();
+    }
+
+    // If the default search is disabled, enable it.
+    $entity = $this->storage->load($form_state['values']['default_type']);
+    if (!$entity->status()) {
+      $entity->enable()->save();
+    }
+
+    $search_settings
+      ->set('index.cron_limit', $form_state['values']['cron_limit'])
+      ->set('default_type', $form_state['values']['default_type'])
+      ->save();
+
+    drupal_set_message($this->t('The configuration options have been saved.'));
+  }
+
+  /**
+   * Form submission handler for the reindex button on the search admin settings
+   * form.
+   */
+  public function searchAdminReindexSubmit(array &$form, array &$form_state) {
+    // Send the user to the confirmation page.
+    $form_state['redirect_route']['route_name'] = 'search.reindex_confirm';
+  }
+
+}
diff --git a/core/modules/search/lib/Drupal/search/SearchPluginManager.php b/core/modules/search/lib/Drupal/search/SearchPluginManager.php
index dac919b..6c5ead2 100644
--- a/core/modules/search/lib/Drupal/search/SearchPluginManager.php
+++ b/core/modules/search/lib/Drupal/search/SearchPluginManager.php
@@ -7,12 +7,11 @@
 
 namespace Drupal\search;
 
-use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Config\ConfigFactory;
 use Drupal\Core\Plugin\DefaultPluginManager;
-use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Language\LanguageManager;
+use Drupal\search\Plugin\SearchIndexingInterface;
 
 /**
  * SearchExecute plugin manager.
@@ -50,73 +49,21 @@ public function processDefinition(&$definition, $plugin_id) {
   }
 
   /**
-   * Returns an instance for each active search plugin.
-   *
-   * @return \Drupal\search\Plugin\SearchInterface[]
-   *   An array of active search plugins, keyed by their ID.
-   */
-  public function getActivePlugins() {
-    $plugins = array();
-    foreach ($this->getActiveDefinitions() as $plugin_id => $definition) {
-      $plugins[$plugin_id] = $this->createInstance($plugin_id);
-    }
-    return $plugins;
-  }
-
-  /**
    * Returns an instance for each active plugin that implements \Drupal\search\Plugin\SearchIndexingInterface.
    *
    * @return \Drupal\search\Plugin\SearchInterface[]
    *   An array of active search plugins, keyed by their ID.
    */
   public function getActiveIndexingPlugins() {
-    $plugins = array();
-    foreach ($this->getActiveDefinitions() as $plugin_id => $definition) {
-      if (is_subclass_of($definition['class'], '\Drupal\search\Plugin\SearchIndexingInterface')) {
-        $plugins[$plugin_id] = $this->createInstance($plugin_id);
+    $search_storage = \Drupal::entityManager()->getStorageController('search');
+    $indexable_plugins = array();
+    foreach ($search_storage->getActiveSearches() as $entity_id => $entity) {
+      if (($plugin = $entity->getPlugin()) && $plugin instanceof SearchIndexingInterface) {
+        $indexable_plugins[$entity_id] = $plugin;
       }
-    }
-    return $plugins;
-  }
 
-  /**
-   * Returns definitions for active search plugins keyed by their ID.
-   *
-   * @return array
-   *   An array of active search plugin definitions, keyed by their ID.
-   */
-  public function getActiveDefinitions() {
-    $active_definitions = array();
-    $active_config = $this->configFactory->get('search.settings')->get('active_plugins');
-    $active_plugins = $active_config ? array_flip($active_config) : array();
-    foreach ($this->getDefinitions() as $plugin_id => $definition) {
-      if (isset($active_plugins[$plugin_id])) {
-        $active_definitions[$plugin_id] = $definition;
-      }
     }
-    return $active_definitions;
+    return $indexable_plugins;
   }
 
-  /**
-   * Check whether access is allowed to search results from a given plugin.
-   *
-   * @param string $plugin_id
-   *   The id of the plugin being checked.
-   * @param \Drupal\Core\Session\AccountInterface $account
-   *   The account being checked for access
-   *
-   * @return bool
-   *   TRUE if access is allowed, FALSE otherwise.
-   */
-  public function pluginAccess($plugin_id, AccountInterface $account) {
-    $definition = $this->getDefinition($plugin_id);
-    if (empty($definition['class'])) {
-      return FALSE;
-    }
-    // Plugins that implement AccessibleInterface can deny access.
-    if (is_subclass_of($definition['class'], '\Drupal\Core\Access\AccessibleInterface')) {
-      return $this->createInstance($plugin_id)->access('view', $account);
-    }
-    return TRUE;
-  }
 }
diff --git a/core/modules/search/lib/Drupal/search/SearchStorage.php b/core/modules/search/lib/Drupal/search/SearchStorage.php
new file mode 100644
index 0000000..9db7204
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/SearchStorage.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search\SearchStorage;
+ */
+
+namespace Drupal\search;
+
+use Drupal\Core\Config\Entity\ConfigStorageController;
+
+/**
+ * Provides the storage class for Search entities.
+ */
+class SearchStorage extends ConfigStorageController implements SearchStorageInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getActiveSearches() {
+    $ids = $this->getQuery()
+      ->condition('status', TRUE)
+      ->execute();
+    return $this->loadMultiple($ids);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isSearchActive() {
+    return (bool) $this->getQuery()
+      ->condition('status', TRUE)
+      ->range(0, 1)
+      ->execute();
+  }
+
+}
diff --git a/core/modules/search/lib/Drupal/search/SearchStorageInterface.php b/core/modules/search/lib/Drupal/search/SearchStorageInterface.php
new file mode 100644
index 0000000..58550b3
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/SearchStorageInterface.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search\SearchStorageInterface;
+ */
+
+namespace Drupal\search;
+
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+
+/**
+ * Provides the interface for a storage class for Search entities.
+ */
+interface SearchStorageInterface extends EntityStorageControllerInterface {
+
+  /**
+   * Returns all active search entities.
+   *
+   * @return \Drupal\search\SearchInterface[]
+   *   An array of active search entities.
+   */
+  public function getActiveSearches();
+
+  /**
+   * Returns whether search is active.
+   *
+   * @return bool
+   *   TRUE if at least one search is active, FALSE otherwise.
+   */
+  public function isSearchActive();
+
+}
diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchBlockTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchBlockTest.php
index 1a52877..2fa66a7 100644
--- a/core/modules/search/lib/Drupal/search/Tests/SearchBlockTest.php
+++ b/core/modules/search/lib/Drupal/search/Tests/SearchBlockTest.php
@@ -52,7 +52,8 @@ protected function testSearchFormBlock() {
 
     // Test a normal search via the block form, from the front page.
     $terms = array('search_block_form' => 'test');
-    $this->drupalPostForm('node', $terms, t('Search'));
+    $this->drupalPostForm('', $terms, t('Search'));
+    $this->assertResponse(200);
     $this->assertText('Your search yielded no results');
 
     // Test a search from the block on a 404 page.
@@ -66,7 +67,8 @@ protected function testSearchFormBlock() {
     $visibility['path']['pages'] = 'search';
     $block->set('visibility', $visibility);
 
-    $this->drupalPostForm('node', $terms, t('Search'));
+    $this->drupalPostForm('', $terms, t('Search'));
+    $this->assertResponse(200);
     $this->assertText('Your search yielded no results');
 
     // Confirm that the user is redirected to the search page.
@@ -78,7 +80,8 @@ protected function testSearchFormBlock() {
 
     // Test an empty search via the block form, from the front page.
     $terms = array('search_block_form' => '');
-    $this->drupalPostForm('node', $terms, t('Search'));
+    $this->drupalPostForm('', $terms, t('Search'));
+    $this->assertResponse(200);
     $this->assertText('Please enter some keywords');
 
     // Confirm that the user is redirected to the search page, when form is
diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php
index 205071d..c74e17a 100644
--- a/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php
+++ b/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php
@@ -95,19 +95,23 @@ function testSearchModuleSettingsPage() {
     $this->assertNoText(t('Extra type settings'));
     $this->assertNoText(t('Boost method'));
 
+    $this->clickLink(t('Add new search type'));
+
     // Ensure that the test plugin is listed as an option
-    $this->assertTrue($this->xpath('//input[@id="edit-active-plugins-search-extra-type-search"]'), 'Checkbox for activating search for an extra plugin is visible');
-    $this->assertTrue($this->xpath('//input[@id="edit-default-plugin-search-extra-type-search"]'), 'Radio button for setting extra plugin as default search plugin is visible');
+    $this->assertLinkByHref('admin/config/search/settings/add/search_extra_type_search');
 
     // Enable search for the test plugin
-    $edit['active_plugins[search_extra_type_search]'] = 'search_extra_type_search';
-    $edit['default_plugin'] = 'search_extra_type_search';
+    $edit = array(
+      'default_type' => 'dummy_search_type',
+      'minimum_word_size' => 5,
+    );
     $this->drupalPostForm('admin/config/search/settings', $edit, t('Save configuration'));
+    $this->assertTrue($this->xpath('//input[@id="edit-minimum-word-size" and @value="5"]'), 'Common search settings can be modified if a module-specific form is active');
 
-    // Ensure that the settings fieldset is visible after enabling search for
-    // the test plugin
-    $this->assertText(t('Extra type settings'));
-    $this->assertText(t('Boost method'));
+    // Ensure that the modifications took effect.
+    $this->assertText(t('The configuration options have been saved.'));
+    $this->assertText(t('Dummy search type'));
+    $this->drupalGet('admin/config/search/settings/manage/dummy_search_type');
 
     // Ensure that the default setting was picked up from the default config
     $this->assertTrue($this->xpath('//select[@id="edit-extra-type-settings-boost"]//option[@value="bi" and @selected="selected"]'), 'Module specific settings are picked up from the default config');
@@ -115,14 +119,13 @@ function testSearchModuleSettingsPage() {
     // Change extra type setting and also modify a common search setting.
     $edit = array(
       'extra_type_settings[boost]' => 'ii',
-      'minimum_word_size' => 5,
     );
-    $this->drupalPostForm('admin/config/search/settings', $edit, t('Save configuration'));
+    $this->drupalPostForm(NULL, $edit, t('Save'));
 
     // Ensure that the modifications took effect.
-    $this->assertText(t('The configuration options have been saved.'));
+    $this->assertText(t('The search has been successfully saved.'));
+    $this->drupalGet('admin/config/search/settings/manage/dummy_search_type');
     $this->assertTrue($this->xpath('//select[@id="edit-extra-type-settings-boost"]//option[@value="ii" and @selected="selected"]'), 'Module specific settings can be changed');
-    $this->assertTrue($this->xpath('//input[@id="edit-minimum-word-size" and @value="5"]'), 'Common search settings can be modified if a plugin-specific form is active');
   }
 
   /**
@@ -145,7 +148,7 @@ function testSearchModuleDisabling() {
         'keys' => $this->search_user->getUsername(),
         'text' => $this->search_user->getEmail(),
       ),
-      'search_extra_type_search' => array(
+      'dummy_search_type' => array(
         'path' => 'dummy_path',
         'title' => 'Dummy search type',
         'keys' => 'foo',
@@ -153,26 +156,30 @@ function testSearchModuleDisabling() {
       ),
     );
     $plugins = array_keys($plugin_info);
+    /** @var $entities \Drupal\search\SearchInterface[] */
+    $entities = entity_load_multiple('search');
+    foreach ($entities as $entity) {
+      $entity->disable()->save();
+    }
 
     // Test each plugin if it's enabled as the only search plugin.
-    foreach ($plugins as $plugin) {
+    foreach ($entities as $entity_id => $entity) {
+      $entity->enable()->save();
       // Enable the one plugin and disable other ones.
-      $info = $plugin_info[$plugin];
       $edit = array();
-      foreach ($plugins as $other) {
-        $edit['active_plugins[' . $other . ']'] = (($other == $plugin) ? $plugin : FALSE);
-      }
-      $edit['default_plugin'] = $plugin;
+      $edit['default_type'] = $entity_id;
       $this->drupalPostForm('admin/config/search/settings', $edit, t('Save configuration'));
 
       // Run a search from the correct search URL.
+      $info = $plugin_info[$entity_id];
       $this->drupalGet('search/' . $info['path'] . '/' . $info['keys']);
+      $this->assertResponse(200);
       $this->assertNoText('no results', $info['title'] . ' search found results');
       $this->assertText($info['text'], 'Correct search text found');
 
       // Verify that other plugin search tab titles are not visible.
       foreach ($plugins as $other) {
-        if ($other != $plugin) {
+        if ($other != $entity_id) {
           $title = $plugin_info[$other]['title'];
           $this->assertNoText($title, $title . ' search tab is not shown');
         }
@@ -189,26 +196,29 @@ function testSearchModuleDisabling() {
 
       // Try an invalid search path. Should redirect to our active plugin.
       $this->drupalGet('search/not_a_plugin_path');
+      $this->assertResponse(200);
       $this->assertEqual(
         $this->getURL(),
         url('search/' . $info['path'], array('absolute' => TRUE)),
         'Invalid search path redirected to default search page');
+
+      $entity->disable()->save();
     }
 
     // Test with all search plugins enabled. When you go to the search
     // page or run search, all plugins should be shown.
     $edit = array();
-    foreach ($plugins as $plugin) {
-      $edit['active_plugins[' . $plugin . ']'] = $plugin;
+    foreach (entity_load_multiple('search') as $entity) {
+      $entity->enable()->save();
     }
-    $edit['default_plugin'] = 'node_search';
+    $edit['default_type'] = 'node_search';
 
     $this->drupalPostForm('admin/config/search/settings', $edit, t('Save configuration'));
 
     foreach (array('search/node/pizza', 'search/node') as $path) {
       $this->drupalGet($path);
-      foreach ($plugins as $plugin) {
-        $title = $plugin_info[$plugin]['title'];
+      foreach ($plugins as $entity_id) {
+        $title = $plugin_info[$entity_id]['title'];
         $this->assertText($title, format_string('%title search tab is shown', array('%title' => $title)));
       }
     }
diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php
index 67deae3..d004a45 100644
--- a/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php
+++ b/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php
@@ -115,7 +115,7 @@ public function testRankings() {
     array_pop($node_ranks);
 
     // Test that the settings form displays the context ranking section.
-    $this->drupalGet('admin/config/search/settings');
+    $this->drupalGet('admin/config/search/settings/manage/node_search');
     $this->assertText(t('Content ranking'));
 
     // Check that all rankings are visible and set to 0.
@@ -128,7 +128,8 @@ public function testRankings() {
     foreach ($node_ranks as $node_rank) {
       // Enable the ranking we are testing.
       $edit['node_rank_' . $node_rank] = 10;
-      $this->drupalPostForm('admin/config/search/settings', $edit, t('Save configuration'));
+      $this->drupalPostForm('admin/config/search/settings/manage/node_search', $edit, t('Save'));
+      $this->drupalGet('admin/config/search/settings/manage/node_search');
       $this->assertTrue($this->xpath('//select[@id="edit-node-rank-' . $node_rank . '"]//option[@value="10"]'), 'Select list to prioritize ' . $node_rank . ' for node ranks is visible and set to 10.');
 
       // Do the search and assert the results.
@@ -141,7 +142,8 @@ public function testRankings() {
 
     // Save the final node_rank change then check that all rankings are visible
     // and have been set back to 0.
-    $this->drupalPostForm('admin/config/search/settings', $edit, t('Save configuration'));
+    $this->drupalPostForm('admin/config/search/settings/manage/node_search', $edit, t('Save'));
+    $this->drupalGet('admin/config/search/settings/manage/node_search');
     foreach ($node_ranks as $node_rank) {
       $this->assertTrue($this->xpath('//select[@id="edit-node-rank-' . $node_rank . '"]//option[@value="0"]'), 'Select list to prioritize ' . $node_rank . ' for node ranks is visible and set to 0.');
     }
diff --git a/core/modules/search/search.install b/core/modules/search/search.install
index 824a624..9143795 100644
--- a/core/modules/search/search.install
+++ b/core/modules/search/search.install
@@ -170,7 +170,7 @@ function _search_update_8000_modules_mapto_plugins(array $map) {
   }
   $default_module = update_variable_get('search_default_module', 'node');
   if (isset($map[$default_module])) {
-    $config->set('default_plugin', $map[$default_module]);
+    $config->set('default_type', $map[$default_module]);
     update_variable_del('search_default_module');
   }
   $config->save();
diff --git a/core/modules/search/search.module b/core/modules/search/search.module
index 4afd6dc..4381263 100644
--- a/core/modules/search/search.module
+++ b/core/modules/search/search.module
@@ -171,20 +171,25 @@ function search_menu() {
 }
 
 /**
- * Returns information about the default search plugin.
+ * Returns the default search entity.
  *
- * @return array
- *   The search plugin definition for the default search plugin, if any.
+ * @return \Drupal\search\SearchInterface
+ *   The search entity.
  */
-function search_get_default_plugin_info() {
-  $info = \Drupal::service('plugin.manager.search')->getActiveDefinitions();
-  $default = \Drupal::config('search.settings')->get('default_plugin');
-  if (isset($info[$default])) {
-    return $info[$default];
+function search_get_default_type() {
+  $default = \Drupal::config('search.settings')->get('default_type');
+  $query = \Drupal::entityQuery('search')
+    ->condition('status', TRUE)
+    ->condition('id', $default);
+  if (!$id = $query->execute()) {
+    $id = \Drupal::entityQuery('search')
+      ->condition('status', TRUE)
+      ->range(0, 1)
+      ->execute();
+  }
+  if ($id) {
+    return reset($id);
   }
-  // The config setting does not match any active plugin, so just return
-  // the info for the first active plugin (if any).
-  return reset($info);
 }
 
 /**
@@ -852,3 +857,11 @@ function search_simplify_excerpt_match($key, $text, $offset, $boundary, $langcod
 function _search_excerpt_match_filter($var) {
   return strlen(trim($var[0]));
 }
+
+/**
+ * Implements hook_module_preinstall().
+ */
+function search_module_preinstall() {
+  // @todo Remove once plugin managers are cleared during module install.
+  \Drupal::service('plugin.manager.search')->clearCachedDefinitions();
+}
diff --git a/core/modules/search/search.routing.yml b/core/modules/search/search.routing.yml
index 9502d77..2b5225d 100644
--- a/core/modules/search/search.routing.yml
+++ b/core/modules/search/search.routing.yml
@@ -1,7 +1,7 @@
 search.settings:
   path: '/admin/config/search/settings'
   defaults:
-    _form: '\Drupal\search\Form\SearchSettingsForm'
+    _entity_list: 'search'
     _title: 'Search settings'
   requirements:
     _permission: 'administer search'
@@ -25,3 +25,43 @@ search.view:
     keys: '.+'
     _permission: 'search content'
     _search_access: 'TRUE'
+
+search.add:
+  path: '/admin/config/search/settings/add'
+  defaults:
+    _content: '\Drupal\search\Controller\SearchController::addSearchType'
+    _title: 'Add new search configuration'
+  requirements:
+    _permission: 'administer search'
+
+search.add_type:
+  path: '/admin/config/search/settings/add/{search_plugin_id}'
+  defaults:
+    _entity_form: 'search.add'
+    _title: 'Add new search'
+  requirements:
+    _entity_create_access: 'search'
+
+search.edit:
+  path: '/admin/config/search/settings/manage/{search}'
+  defaults:
+    _entity_form: 'search.edit'
+    _title_callback: '\Drupal\search\Controller\SearchController::editTitle'
+  requirements:
+    _entity_access: 'search.update'
+
+search.disable:
+  path: '/admin/config/search/settings/manage/{search}/{op}'
+  defaults:
+    _controller: '\Drupal\search\Controller\SearchController::performOperation'
+  requirements:
+    _permission: 'administer search'
+    _csrf_token: 'TRUE'
+    op: 'enable|disable'
+
+search.delete:
+  path: '/admin/config/search/settings/manage/{search}/delete'
+  defaults:
+    _entity_form: 'search.delete'
+  requirements:
+    _entity_access: 'search.delete'
diff --git a/core/modules/search/search.services.yml b/core/modules/search/search.services.yml
index 88f6ea9..d234111 100644
--- a/core/modules/search/search.services.yml
+++ b/core/modules/search/search.services.yml
@@ -5,18 +5,18 @@ services:
 
   access_check.search:
     class: Drupal\search\Access\SearchAccessCheck
-    arguments: ['@plugin.manager.search']
+    arguments: ['@entity.manager']
     tags:
       - { name: access_check }
 
   access_check.search_plugin:
     class: Drupal\search\Access\SearchPluginAccessCheck
-    arguments: ['@plugin.manager.search']
+    arguments: ['@entity.manager']
     tags:
       - { name: access_check }
 
   route_subscriber.search:
     class: Drupal\search\Routing\SearchRouteSubscriber
-    arguments: ['@plugin.manager.search']
+    arguments: ['@entity.manager']
     tags:
      - { name: event_subscriber }
diff --git a/core/modules/search/tests/modules/search_extra_type/config/search.search.dummy_search_type.yml b/core/modules/search/tests/modules/search_extra_type/config/search.search.dummy_search_type.yml
new file mode 100644
index 0000000..cfebc79
--- /dev/null
+++ b/core/modules/search/tests/modules/search_extra_type/config/search.search.dummy_search_type.yml
@@ -0,0 +1,9 @@
+id: dummy_search_type
+label: 'Dummy search type'
+uuid: b55858d4-f428-474c-8200-ef47a4597aef
+status: '1'
+langcode: en
+path: dummy_path
+title: 'Dummy search type'
+plugin: search_extra_type_search
+configuration: {  }
diff --git a/core/modules/search/tests/modules/search_extra_type/config/search_extra_type.settings.yml b/core/modules/search/tests/modules/search_extra_type/config/search_extra_type.settings.yml
deleted file mode 100644
index db64246..0000000
--- a/core/modules/search/tests/modules/search_extra_type/config/search_extra_type.settings.yml
+++ /dev/null
@@ -1 +0,0 @@
-boost: bi
diff --git a/core/modules/search/tests/modules/search_extra_type/lib/Drupal/search_extra_type/Plugin/Search/SearchExtraTypeSearch.php b/core/modules/search/tests/modules/search_extra_type/lib/Drupal/search_extra_type/Plugin/Search/SearchExtraTypeSearch.php
index 2ea35eb..dc3de4a 100644
--- a/core/modules/search/tests/modules/search_extra_type/lib/Drupal/search_extra_type/Plugin/Search/SearchExtraTypeSearch.php
+++ b/core/modules/search/tests/modules/search_extra_type/lib/Drupal/search_extra_type/Plugin/Search/SearchExtraTypeSearch.php
@@ -7,12 +7,7 @@
 
 namespace Drupal\search_extra_type\Plugin\Search;
 
-use Drupal\Core\Annotation\Translation;
-use Drupal\Core\Config\Config;
-use Drupal\Core\Plugin\PluginFormInterface;
-use Drupal\search\Plugin\SearchPluginBase;
-use Drupal\search\Annotation\SearchPlugin;
-use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\search\Plugin\ConfigurableSearchPluginBase;
 
 /**
  * Executes a keyword search against the search index.
@@ -23,41 +18,7 @@
  *   path = "dummy_path"
  * )
  */
-class SearchExtraTypeSearch extends SearchPluginBase implements PluginFormInterface {
-
-  /**
-   * @var \Drupal\Core\Config\Config
-   */
-  protected $configSettings;
-
-  /**
-   * {@inheritdoc}
-   */
-  static public function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
-    return new static(
-      $container->get('config.factory')->get('search_extra_type.settings'),
-      $configuration,
-      $plugin_id,
-      $plugin_definition
-    );
-  }
-
-  /**
-   * Creates a SearchExtraTypeSearch object.
-   *
-   * @param Config $config_settings
-   *   The extra config settings.
-   * @param array $configuration
-   *   A configuration array containing information about the plugin instance.
-   * @param string $plugin_id
-   *   The plugin_id for the plugin instance.
-   * @param array $plugin_definition
-   *   The plugin implementation definition.
-   */
-  public function __construct(Config $config_settings, array $configuration, $plugin_id, array $plugin_definition) {
-    $this->configSettings = $config_settings;
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-  }
+class SearchExtraTypeSearch extends ConfigurableSearchPluginBase {
 
   /**
    * {@inheritdoc}
@@ -140,7 +101,7 @@ public function buildConfigurationForm(array $form, array &$form_state) {
         'bi' => t('Bistromathic'),
         'ii' => t('Infinite Improbability'),
       ),
-      '#default_value' => $this->configSettings->get('boost'),
+      '#default_value' => $this->configuration['boost'],
     );
     return $form;
   }
@@ -148,16 +109,17 @@ public function buildConfigurationForm(array $form, array &$form_state) {
   /**
    * {@inheritdoc}
    */
-  public function validateConfigurationForm(array &$form, array &$form_state) {
+  public function submitConfigurationForm(array &$form, array &$form_state) {
+    $this->configuration['boost'] = $form_state['values']['extra_type_settings']['boost'];
   }
 
   /**
    * {@inheritdoc}
    */
-  public function submitConfigurationForm(array &$form, array &$form_state) {
-    $this->configSettings
-      ->set('boost', $form_state['values']['extra_type_settings']['boost'])
-      ->save();
+  public function defaultConfiguration() {
+    return array(
+      'boost' => 'bi',
+    );
   }
 
 }
diff --git a/core/modules/user/config/search.search.user_search.yml b/core/modules/user/config/search.search.user_search.yml
new file mode 100644
index 0000000..94c43ce
--- /dev/null
+++ b/core/modules/user/config/search.search.user_search.yml
@@ -0,0 +1,9 @@
+id: user_search
+label: 'User search'
+uuid: c0d6b9a7-09a7-415f-b71a-26957bef635c
+status: '1'
+langcode: en
+path: user
+title: Users
+plugin: user_search
+configuration: {  }
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearch.php b/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearch.php
index 75399f8..aa6f033 100644
--- a/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearch.php
+++ b/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearch.php
@@ -7,16 +7,13 @@
 
 namespace Drupal\user\Plugin\Search;
 
-use Drupal\Core\Annotation\Translation;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Access\AccessibleInterface;
-use Drupal\search\Annotation\SearchPlugin;
 use Drupal\search\Plugin\SearchPluginBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Executes a keyword search for users against the {users} database table.
@@ -101,7 +98,7 @@ public function __construct(Connection $database, EntityManagerInterface $entity
   /**
    * {@inheritdoc}
    */
-  public function access($operation = 'view', AccountInterface $account = NULL) {
+  public function access($operation, AccountInterface $account = NULL) {
     return !empty($account) && $account->hasPermission('access user profiles');
   }
 
