diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 6c8e48b..7cb3f26 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -128,27 +128,6 @@
  *     - access callback: As in hook_menu(). 'user_access' will be assumed if
  *       no value is provided.
  *     - access arguments: As in hook_menu().
- * - view_modes: An array describing the view modes for the entity type. View
- *   modes let entities be displayed differently depending on the context.
- *   For instance, a node can be displayed differently on its own page
- *   ('full' mode), on the home page or taxonomy listings ('teaser' mode), or
- *   in an RSS feed ('rss' mode). Modules taking part in the display of the
- *   entity (notably the Field API) can adjust their behavior depending on
- *   the requested view mode. An additional 'default' view mode is available
- *   for all entity types. This view mode is not intended for actual entity
- *   display, but holds default display settings. For each available view
- *   mode, administrators can configure whether it should use its own set of
- *   field display settings, or just replicate the settings of the 'default'
- *   view mode, thus reducing the amount of display configurations to keep
- *   track of. Keys of the array are view mode names. Each view mode is
- *   described by an array with the following key/value pairs:
- *   - label: The human-readable name of the view mode.
- *   - custom_settings: A boolean specifying whether the view mode should by
- *     default use its own custom field display settings. If FALSE, entities
- *     displayed in this view mode will reuse the 'default' display settings
- *     by default (e.g. right after the module exposing the view mode is
- *     enabled), but administrators can later use the Field UI to apply custom
- *     display settings specific to the view mode.
  * - menu_base_path: (optional) The base menu router path to which the entity
  *   administration user interface responds. It can be used to generate UI
  *   links and to attach additional router items to the entity UI in a generic
@@ -270,15 +249,24 @@ protected function processDefinition(&$definition, $plugin_id) {
     parent::processDefinition($definition, $plugin_id);
 
     // @todo Remove this check once http://drupal.org/node/1780396 is resolved.
-    if (!module_exists($definition['module'])) {
+    if ($definition['module'] != 'Core' && !module_exists($definition['module'])) {
       $definition = NULL;
       return;
     }
 
-    foreach ($definition['view_modes'] as $view_mode => $view_mode_info) {
-      $definition['view_modes'][$view_mode] += array(
-        'custom_settings' => FALSE,
-      );
+    // Add view modes.
+    // We cannot use entity_load_multiple() as that leads to recursion.
+    $view_modes = drupal_container()->get('config.storage')->listAll('view_mode.');
+    $config_factory = drupal_container()->get('config.factory');
+    foreach ($view_modes as $config_name) {
+      $view_mode = $config_factory->get($config_name)->load()->get();
+      if ($view_mode['targetEntityType'] == $plugin_id) {
+        $entity_type = strtok($view_mode['id'], '.');
+        $definition['view_modes'][strtok('.')] = array(
+          'label' => check_plain($view_mode['label']),
+          'custom_settings' => $view_mode['hasSettings'],
+        );
+      }
     }
 
     // If no bundle key is provided, assume a single bundle, named after
@@ -286,6 +274,7 @@ protected function processDefinition(&$definition, $plugin_id) {
     if (empty($definition['entity_keys']['bundle']) && empty($definition['bundles'])) {
       $definition['bundles'] = array($plugin_id => array('label' => $definition['label']));
     }
+
     // Prepare entity schema fields SQL info for
     // Drupal\Core\Entity\DatabaseStorageControllerInterface::buildQuery().
     if (isset($definition['base_table'])) {
diff --git a/core/lib/Drupal/Core/Entity/ViewMode/EntityViewModeStorageController.php b/core/lib/Drupal/Core/Entity/ViewMode/EntityViewModeStorageController.php
new file mode 100644
index 0000000..5d6b193
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/ViewMode/EntityViewModeStorageController.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\ViewMode\EntityViewModeStorageController.
+ */
+
+namespace Drupal\Core\Entity\ViewMode;
+
+use Drupal\Core\Config\Entity\ConfigStorageController;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Defines the storage controller class for entity view modes.
+ */
+class EntityViewModeStorageController extends ConfigStorageController {
+
+  /**
+   * Overrides ConfigStorageController::preSave().
+   */
+  protected function preSave(EntityInterface $view_mode) {
+    entity_info_cache_clear();
+  }
+
+  /**
+   * Overrides ConfigStorageController::preDelete().
+   */
+  protected function preDelete($view_modes) {
+    entity_info_cache_clear();
+  }
+
+}
+
diff --git a/core/lib/Drupal/Core/Plugin/Core/Entity/EntityViewMode.php b/core/lib/Drupal/Core/Plugin/Core/Entity/EntityViewMode.php
new file mode 100644
index 0000000..bebd5eb
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Core/Entity/EntityViewMode.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Plugin\Core\Entity\EntityViewMode.
+ */
+
+namespace Drupal\Core\Plugin\Core\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Defines the view mode configuration entity class.
+ *
+ * @Plugin(
+ *   id = "view_mode",
+ *   label = @Translation("View mode"),
+ *   module = "Core",
+ *   controller_class = "Drupal\Core\Entity\ViewMode\EntityViewModeStorageController",
+ *   config_prefix = "view_mode",
+ *   fieldable = FALSE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid"
+ *   }
+ * )
+ */
+class EntityViewMode extends ConfigEntityBase {
+
+  /**
+   * The id of the view mode.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The UUID of the view mode.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The human-readable name of the view mode.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The entity type this view mode is used for.
+   *
+   * This is not to be confused with EntityViewMode::entityType which is
+   * inherited from Entity::entityType and equals 'view_mode' for any view mode
+   * entity.
+   *
+   * @var string
+   */
+  public $targetEntityType;
+
+  /**
+   * Whether or not this view mode has custom settings by default.
+   *
+   * @var bool
+   */
+  public $hasSettings = FALSE;
+
+  /**
+   * Whether or not this view mode can be removed.
+   *
+   * @todo Move this to ConfigEntityBase: http://drupal.org/node/1831928
+   *
+   * @var bool
+   */
+  public $locked = FALSE;
+
+  /**
+   * Returns the raw view mode name, exluding the entity type.
+   *
+   * @return string
+   *   The raw view mode name.
+   */
+  public function name() {
+    $name = strtok($this->name, '.');
+    return strtok('.');
+  }
+
+}
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index 062097a..e48b548 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -246,17 +246,6 @@ function book_admin_paths() {
 }
 
 /**
- * Implements hook_entity_info().
- */
-function book_entity_info(&$info) {
-  // Add the 'Print' view mode for nodes.
-  $info['node']['view_modes']['print'] = array(
-    'label' => t('Print'),
-    'custom_settings' => FALSE,
-  );
-}
-
-/**
  * Implements hook_block_info().
  */
 function book_block_info() {
diff --git a/core/modules/book/config/view_mode.node.print.yml b/core/modules/book/config/view_mode.node.print.yml
new file mode 100644
index 0000000..c1290fd
--- /dev/null
+++ b/core/modules/book/config/view_mode.node.print.yml
@@ -0,0 +1,5 @@
+id: node.print
+label: Print
+hasSettings: '0'
+targetEntityType: node
+locked: '1'
diff --git a/core/modules/comment/config/view_mode.comment.full.yml b/core/modules/comment/config/view_mode.comment.full.yml
new file mode 100644
index 0000000..1549c46
--- /dev/null
+++ b/core/modules/comment/config/view_mode.comment.full.yml
@@ -0,0 +1,5 @@
+id: comment.full
+label: Full comment
+hasSettings: '0'
+targetEntityType: comment
+locked: '1'
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php
index e0bbfd9..e3b31a0 100644
--- a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php
@@ -34,12 +34,6 @@
  *     "bundle" = "node_type",
  *     "label" = "subject",
  *     "uuid" = "uuid"
- *   },
- *   view_modes = {
- *     "full" = {
- *       "label" = "Full comment",
- *       "custom_settings" = FALSE
- *     }
  *   }
  * )
  */
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index a04c0dd..babb0b4 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -1,9 +1,12 @@
 <?php
+
 /**
  * @file
  * Allows administrators to attach custom fields to fieldable types.
  */
 
+use Drupal\Core\Plugin\Core\Entity\EntityViewMode;
+
 /**
  * Implements hook_help().
  */
@@ -412,3 +415,18 @@ function field_ui_library_info() {
 
   return $libraries;
 }
+
+/**
+ * Implements hook_view_mode_presave().
+ */
+function field_ui_view_mode_presave(EntityViewMode $view_mode) {
+  state()->set('menu_rebuild_needed', TRUE);
+}
+
+/**
+ * Implements hook_view_mode_delete().
+ */
+function field_ui_view_mode_delete(EntityViewMode $view_mode) {
+  state()->set('menu_rebuild_needed', TRUE);
+}
+
diff --git a/core/modules/file/config/view_mode.file.full.yml b/core/modules/file/config/view_mode.file.full.yml
new file mode 100644
index 0000000..743e2d1
--- /dev/null
+++ b/core/modules/file/config/view_mode.file.full.yml
@@ -0,0 +1,5 @@
+id: file.full
+label: File default
+hasSettings: '0'
+targetEntityType: file
+locked: '1'
diff --git a/core/modules/file/lib/Drupal/file/Plugin/Core/Entity/File.php b/core/modules/file/lib/Drupal/file/Plugin/Core/Entity/File.php
index bc54514..d7f2a6b 100644
--- a/core/modules/file/lib/Drupal/file/Plugin/Core/Entity/File.php
+++ b/core/modules/file/lib/Drupal/file/Plugin/Core/Entity/File.php
@@ -26,12 +26,6 @@
  *     "id" = "fid",
  *     "label" = "filename",
  *     "uuid" = "uuid"
- *   },
- *   view_modes = {
- *     "full" = {
- *       "label" = "File default",
- *       "custom_settings" = FALSE
- *     }
  *   }
  * )
  */
diff --git a/core/modules/node/config/view_mode.node.full.yml b/core/modules/node/config/view_mode.node.full.yml
new file mode 100644
index 0000000..e3fe426
--- /dev/null
+++ b/core/modules/node/config/view_mode.node.full.yml
@@ -0,0 +1,5 @@
+id: node.full
+label: Full content
+hasSettings: '0'
+targetEntityType: node
+locked: '1'
diff --git a/core/modules/node/config/view_mode.node.rss.yml b/core/modules/node/config/view_mode.node.rss.yml
new file mode 100644
index 0000000..5b29601
--- /dev/null
+++ b/core/modules/node/config/view_mode.node.rss.yml
@@ -0,0 +1,5 @@
+id: node.rss
+label: RSS
+hasSettings: '0'
+targetEntityType: node
+locked: '1'
diff --git a/core/modules/node/config/view_mode.node.teaser.yml b/core/modules/node/config/view_mode.node.teaser.yml
new file mode 100644
index 0000000..8d83192
--- /dev/null
+++ b/core/modules/node/config/view_mode.node.teaser.yml
@@ -0,0 +1,5 @@
+id: node.teaser
+label: Teaser
+hasSettings: '1'
+targetEntityType: node
+locked: '1'
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
index 1fd8fc8..1267f3a 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
@@ -38,20 +38,6 @@
  *   },
  *   bundle_keys = {
  *     "bundle" = "type"
- *   },
- *   view_modes = {
- *     "full" = {
- *       "label" = "Full content",
- *       "custom_settings" = FALSE
- *     },
- *     "teaser" = {
- *       "label" = "Teaser",
- *       "custom_settings" = TRUE
- *     },
- *     "rss" = {
- *       "label" = "RSS",
- *       "custom_settings" = FALSE
- *     }
  *   }
  * )
  */
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 08d7810..83d5627 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -182,19 +182,6 @@ function node_entity_info(&$info) {
     $info['node']['translation']['node'] = TRUE;
   }
 
-  // Search integration is provided by node.module, so search-related
-  // view modes for nodes are defined here and not in search.module.
-  if (module_exists('search')) {
-    $info['node']['view_modes']['search_index'] = array(
-      'label' => t('Search index'),
-      'custom_settings' => FALSE,
-    );
-    $info['node']['view_modes']['search_result'] = array(
-      'label' => t('Search result'),
-      'custom_settings' => FALSE,
-    );
-  }
-
   // Bundles must provide a human readable name so we can create help and error
   // messages, and the path to attach Field admin pages to.
   node_type_cache_reset();
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
index acf4802..09e1e09 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
@@ -116,6 +116,8 @@ protected function entityCreate($entity_type) {
         return entity_create('node', array('title' => $this->randomString()));
       case 'user':
         return entity_create('user', array('name' => $this->randomName()));
+      case 'view_mode':
+        return entity_create('view_mode', array('name' => $this->randomName(), 'machineName' => 'node.test'));
       default:
         return entity_create($entity_type, array());
     }
diff --git a/core/modules/search/config/view_mode.node.search_index.yml b/core/modules/search/config/view_mode.node.search_index.yml
new file mode 100644
index 0000000..cd98947
--- /dev/null
+++ b/core/modules/search/config/view_mode.node.search_index.yml
@@ -0,0 +1,5 @@
+id: node.search_index
+label: Search index
+hasSettings: '0'
+targetEntityType: node
+locked: '1'
diff --git a/core/modules/search/config/view_mode.node.search_result.yml b/core/modules/search/config/view_mode.node.search_result.yml
new file mode 100644
index 0000000..ab6fe86
--- /dev/null
+++ b/core/modules/search/config/view_mode.node.search_result.yml
@@ -0,0 +1,5 @@
+id: node.search_result
+label: Search result
+hasSettings: '0'
+targetEntityType: node
+locked: '1'
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php
index e1df73e..4147c27 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php
@@ -96,5 +96,14 @@ public function testFilledStandardUpgrade() {
     $blog_type = node_type_load('blog');
     $this->assertEqual($blog_type->module, 'node', "Content type 'blog' has been reassigned from the blog module to the node module.");
     $this->assertEqual($blog_type->base, 'node_content', "The base string used to construct callbacks corresponding to content type 'Blog' has been reassigned to 'node_content'.");
+
+    // Verify all core view modes have been converted to config.
+    $entity_info = entity_get_info();
+    foreach ($entity_info as $type => $info) {
+      if (!empty($info['fieldable'])) {
+        $this->assertTrue(!empty($info['view_modes']), format_string('View modes for @entity_type have been converted', array('@entity_type' => $info['label'])));
+      }
+    }
+
   }
 }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 9d823ca..8c0f129 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -2232,6 +2232,57 @@ function system_update_8036() {
 }
 
 /**
+ * Moves entity view modes to config.
+ *
+ * @ingroup config_upgrade
+ */
+function system_update_8037() {
+  // We cannot call entity_get_info() in an update hook, so we hardcode the view
+  // modes. Only the node entity type's teaser view mode is set to custom by
+  // default, we check specifically for that below. The only way to add custom
+  // view modes in Drupal 7 was hook_entity_info_alter(), which still works in
+  // Drupal 8.
+  $entity_view_modes = array(
+    'node' => array(
+      'full' => 'Full content',
+      'teaser' => 'Teaser',
+      'rss' => 'RSS',
+      'search_index' => 'Search index',
+      'search_result' => 'Search result',
+      'print' => 'Print',
+    ),
+    'file' => array(
+      'full' => 'File default',
+    ),
+    'comment' => array(
+      'full' => 'Full comment',
+    ),
+    'user' => array(
+      'full' => 'User account',
+    ),
+    'taxonomy_term' => array(
+      'full' => 'Taxonomy term page',
+    ),
+    'taxonomy_vocabulary' => array(
+      'full' => 'Taxonomy vocabulary',
+    ),
+  );
+
+  foreach ($entity_view_modes as $entity_type => $view_modes) {
+    foreach ($view_modes as $key => $name) {
+      $custom = ($key == 'teaser');
+      config('view_mode.' . $entity_type . '.' . $key)
+        ->set('machineName', $entity_type . '.' . $key)
+        ->set('name', $name)
+        ->set('targetEntityType', $entity_type)
+        ->set('locked', TRUE)
+        ->set('custom', $custom)
+        ->save();
+    }
+  }
+}
+
+/**
  * @} End of "defgroup updates-7.x-to-8.x".
  * The next series of updates should start at 9000.
  */
diff --git a/core/modules/taxonomy/config/view_mode.taxonomy_term.full.yml b/core/modules/taxonomy/config/view_mode.taxonomy_term.full.yml
new file mode 100644
index 0000000..4203a25
--- /dev/null
+++ b/core/modules/taxonomy/config/view_mode.taxonomy_term.full.yml
@@ -0,0 +1,5 @@
+id: taxonomy_term.full
+label: Taxonomy term page
+hasSettings: '0'
+targetEntityType: taxonomy_term
+locked: '1'
diff --git a/core/modules/taxonomy/config/view_mode.taxonomy_vocabulary.full.yml b/core/modules/taxonomy/config/view_mode.taxonomy_vocabulary.full.yml
new file mode 100644
index 0000000..1f8cfd9
--- /dev/null
+++ b/core/modules/taxonomy/config/view_mode.taxonomy_vocabulary.full.yml
@@ -0,0 +1,5 @@
+id: vocabulary.full
+label: Taxonomy vocabulary
+hasSettings: '0'
+targetEntityType: taxonomy_vocabulary
+locked: '1'
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php
index 51468af..7d9ce49 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php
@@ -37,12 +37,6 @@
  *   bundle_keys = {
  *     "bundle" = "machine_name"
  *   },
- *   view_modes = {
- *     "full" = {
- *       "label" = "Taxonomy term page",
- *       "custom_settings" = FALSE
- *     }
- *   },
  *   menu_base_path = "taxonomy/term/%taxonomy_term"
  * )
  */
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php
index 0ecbae8..beee8d1 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php
@@ -26,12 +26,6 @@
  *   entity_keys = {
  *     "id" = "vid",
  *     "label" = "name"
- *   },
- *   view_modes = {
- *     "full" = {
- *       "label" = "Taxonomy vocabulary",
- *       "custom_settings" = FALSE
- *     }
  *   }
  * )
  */
diff --git a/core/modules/user/config/view_mode.user.full.yml b/core/modules/user/config/view_mode.user.full.yml
new file mode 100644
index 0000000..cd81d36
--- /dev/null
+++ b/core/modules/user/config/view_mode.user.full.yml
@@ -0,0 +1,5 @@
+id: user.full
+label: User account
+hasSettings: '0'
+targetEntityType: user
+locked: '1'
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php
index dbedf89..da41eaa 100644
--- a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php
+++ b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php
@@ -42,12 +42,6 @@
  *         "access arguments" = {"administer users"}
  *       }
  *     }
- *   },
- *   view_modes = {
- *     "full" = {
- *       "label" = "User account",
- *       "custom_settings" = FALSE
- *     }
  *   }
  * )
  */
