diff --git a/core/core.services.yml b/core/core.services.yml
index 60fd6d5..9139f90 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -433,7 +433,7 @@ services:
     arguments: ['@app.root', '@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@config.manager', '@router.builder']
   entity.manager:
     class: Drupal\Core\Entity\EntityManager
-    arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@entity.definitions.installed', '@event_dispatcher']
+    arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@keyvalue', '@event_dispatcher']
     parent: container.trait
     tags:
       - { name: plugin_manager_cache_clear }
@@ -442,11 +442,6 @@ services:
     arguments: ['@entity.manager']
     tags:
       - { name: event_subscriber }
-  entity.definitions.installed:
-    class: Drupal\Core\KeyValueStore\KeyValueStoreInterface
-    factory_method: get
-    factory_service: keyvalue
-    arguments: ['entity.definitions.installed']
   entity.definition_update_manager:
     class: Drupal\Core\Entity\EntityDefinitionUpdateManager
     arguments: ['@entity.manager']
diff --git a/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php
index 5037628..496bb1b 100644
--- a/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php
+++ b/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Entity;
 
 use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldDefinitionListenerInterface;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
 
@@ -20,7 +21,7 @@
  *
  * For example, configurable fields defined and exposed by field.module.
  */
-interface DynamicallyFieldableEntityStorageInterface extends FieldableEntityStorageInterface, FieldStorageDefinitionListenerInterface {
+interface DynamicallyFieldableEntityStorageInterface extends FieldableEntityStorageInterface, FieldStorageDefinitionListenerInterface, FieldDefinitionListenerInterface {
 
   /**
    * Determines if the storage contains any data.
@@ -31,37 +32,6 @@
   public function hasData();
 
   /**
-   * Reacts to the creation of a field.
-   *
-   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
-   *   The field definition created.
-   */
-  public function onFieldDefinitionCreate(FieldDefinitionInterface $field_definition);
-
-  /**
-   * Reacts to the update of a field.
-   *
-   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
-   *   The field definition being updated.
-   * @param \Drupal\Core\Field\FieldDefinitionInterface $original
-   *   The original field definition; i.e., the definition before the update.
-   */
-  public function onFieldDefinitionUpdate(FieldDefinitionInterface $field_definition, FieldDefinitionInterface $original);
-
-  /**
-   * Reacts to the deletion of a field.
-   *
-   * Stored values should not be wiped at once, but marked as 'deleted' so that
-   * they can go through a proper purge process later on.
-   *
-   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
-   *   The field definition being deleted.
-   *
-   * @see purgeFieldData()
-   */
-  public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition);
-
-  /**
    * Purges a batch of field data.
    *
    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index a49377f..d749734 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -18,10 +18,13 @@
 use Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldStorageDefinitionEvent;
 use Drupal\Core\Field\FieldStorageDefinitionEvents;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
+use Drupal\Core\KeyValueStore\KeyValueFactory;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
 use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
@@ -121,11 +124,11 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
   protected $languageManager;
 
   /**
-   * The keyvalue collection for tracking installed definitions.
+   * The keyvalue factory.
    *
-   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
    */
-  protected $installedDefinitions;
+  protected $keyValueFactory;
 
   /**
    * The event dispatcher.
@@ -193,12 +196,12 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
    *   The class resolver.
    * @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
    *   The typed data manager.
-   * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $installed_definitions
-   *   The keyvalue collection for tracking installed definitions.
+   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
+   *   The keyvalue factory.
    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
    *   The event dispatcher.
    */
-  public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, TranslationInterface $translation_manager, ClassResolverInterface $class_resolver, TypedDataManager $typed_data_manager, KeyValueStoreInterface $installed_definitions, EventDispatcherInterface $event_dispatcher) {
+  public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, TranslationInterface $translation_manager, ClassResolverInterface $class_resolver, TypedDataManager $typed_data_manager, KeyValueFactoryInterface $key_value_factory, EventDispatcherInterface $event_dispatcher) {
     parent::__construct('Entity', $namespaces, $module_handler, 'Drupal\Core\Entity\EntityInterface');
 
     $this->setCacheBackend($cache, 'entity_type', array('entity_types'));
@@ -209,7 +212,7 @@ public function __construct(\Traversable $namespaces, ModuleHandlerInterface $mo
     $this->translationManager = $translation_manager;
     $this->classResolver = $class_resolver;
     $this->typedDataManager = $typed_data_manager;
-    $this->installedDefinitions = $installed_definitions;
+    $this->keyValueFactory = $key_value_factory;
     $this->eventDispatcher = $event_dispatcher;
   }
 
@@ -642,19 +645,43 @@ public function getFieldMap() {
         $this->fieldMap = $cache->data;
       }
       else {
-        // Rebuild the definitions and put it into the cache.
+        // The field map is built in two steps. First, add all base fields, by
+        // looping over all fieldable entity types. They always exist for all
+        // bundles, and we do not expect to have so many different entity
+        // types for this to become a bottleneck.
         foreach ($this->getDefinitions() as $entity_type_id => $entity_type) {
           if ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
-            foreach ($this->getBundleInfo($entity_type_id) as $bundle => $bundle_info) {
-              foreach ($this->getFieldDefinitions($entity_type_id, $bundle) as $field_name => $field_definition) {
-                $this->fieldMap[$entity_type_id][$field_name]['type'] = $field_definition->getType();
-                $this->fieldMap[$entity_type_id][$field_name]['bundles'][] = $bundle;
-              }
+            $bundles = array_keys($this->getBundleInfo($entity_type_id));
+            foreach ($this->getBaseFieldDefinitions($entity_type_id) as $field_name => $base_field_definition) {
+              $this->fieldMap[$entity_type_id][$field_name] = [
+                'type' => $base_field_definition->getType(),
+                'bundles' => array_combine($bundles, $bundles),
+              ];
             }
           }
         }
 
-        $this->cacheSet($cid, $this->fieldMap, Cache::PERMANENT, array('entity_types', 'entity_field_info'));
+        // In the second step, the per-bundle fields are added, based on the
+        // persistent bundle field map stored in a key value collection. This
+        // data is managed in the EntityManager::onFieldDefinitionCreate()
+        // and EntityManager::onFieldDefinitionDelete() methods. Rebuilding this
+        // information in the same way as base fields would not scale, as the
+        // time to query would grow exponentially with more fields and bundles.
+        // A cache would be deleted during cache clears, which is the only time
+        // it is needed, so a key value collection is used.
+        $bundle_field_maps = $this->keyValueFactory->get('entity.definitions.bundle_field_map')->getAll();
+        foreach ($bundle_field_maps as $entity_type_id => $bundle_field_map) {
+          foreach ($bundle_field_map as $field_name => $map_entry) {
+            if (!isset($this->fieldMap[$entity_type_id][$field_name])) {
+              $this->fieldMap[$entity_type_id][$field_name] = $map_entry;
+            }
+            else {
+              $this->fieldMap[$entity_type_id][$field_name]['bundles'] += $map_entry['bundles'];
+            }
+          }
+        }
+
+        $this->cacheSet($cid, $this->fieldMap, Cache::PERMANENT, array('entity_types'));
       }
     }
     return $this->fieldMap;
@@ -680,6 +707,90 @@ public function getFieldMapByFieldType($field_type) {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function onFieldDefinitionCreate(FieldDefinitionInterface $field_definition) {
+    $entity_type_id = $field_definition->getTargetEntityTypeId();
+    $bundle = $field_definition->getTargetBundle();
+    $field_name = $field_definition->getName();
+
+    // Notify the storage about the new field.
+    $this->getStorage($entity_type_id)->onFieldDefinitionCreate($field_definition);
+
+    // Update the bundle field map, used by EntityManager::getFieldMap().
+    $bundle_field_map = $this->keyValueFactory->get('entity.definitions.bundle_field_map')->get($entity_type_id);
+
+    if (!isset($bundle_field_map[$field_name])) {
+      // This field did not exist yet, initialize it with the type and empty
+      // bundle list.
+      $bundle_field_map[$field_name] = [
+        'type' => $field_definition->getType(),
+        'bundles' => [],
+      ];
+    }
+    $bundle_field_map[$field_name]['bundles'][$bundle] = $bundle;
+
+    // Update the field map collection.
+    $this->keyValueFactory->get('entity.definitions.bundle_field_map')->set($entity_type_id, $bundle_field_map);
+    $this->cacheBackend->delete('entity_field_map');
+
+    // If the field map is initialized, update it as well, so that calls to it
+    // do not have to rebuild it again.
+    if ($this->fieldMap) {
+      if (!isset($this->fieldMap[$entity_type_id][$field_name])) {
+        // This field did not exist yet, initialize it with the type and empty
+        // bundle list.
+        $this->fieldMap[$entity_type_id][$field_name] = [
+          'type' => $field_definition->getType(),
+          'bundles' => [],
+        ];
+      }
+      $this->fieldMap[$entity_type_id][$field_name]['bundles'][$bundle] = $bundle;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onFieldDefinitionUpdate(FieldDefinitionInterface $field_definition, FieldDefinitionInterface $original) {
+    // Notify the storage about the updated field.
+    $this->getStorage($field_definition->getTargetEntityTypeId())->onFieldDefinitionUpdate($field_definition, $original);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition) {
+    $entity_type_id = $field_definition->getTargetEntityTypeId();
+    $bundle = $field_definition->getTargetBundle();
+    $field_name = $field_definition->getName();
+
+    // Notify the storage about the field deletion.
+    $this->getStorage($entity_type_id)->onFieldDefinitionDelete($field_definition);
+
+    // Update the bundle field map, used by EntityManager::getFieldMap().
+    $bundle_field_map = $this->keyValueFactory->get('entity.definitions.bundle_field_map')->get($entity_type_id);
+
+    // Unset the bundle. If there are no bundles left, remove the field from
+    // the map.
+    unset($bundle_field_map[$field_name]['bundles'][$bundle]);
+    if (empty($bundle_field_map[$field_name]['bundles'])) {
+      unset($bundle_field_map[$field_name]);
+    }
+    $this->keyValueFactory->get('entity.definitions.bundle_field_map')->set($entity_type_id, $bundle_field_map);
+    $this->cacheBackend->delete('entity_field_map');
+
+    // If the field map is initialized, update it as well, so that calls to it
+    // do not have to rebuild it again.
+    if ($this->fieldMap) {
+      unset($this->fieldMap[$entity_type_id][$field_name]['bundles'][$bundle]);
+      if (empty($this->fieldMap[$entity_type_id][$field_name]['bundles'])) {
+        unset($this->fieldMap[$entity_type_id][$field_name]);
+      }
+    }
+  }
+
+  /**
    * Builds field storage definitions for an entity type.
    *
    * @param string $entity_type_id
@@ -1243,7 +1354,7 @@ public function onBundleDelete($bundle, $entity_type_id) {
    * {@inheritdoc}
    */
   public function getLastInstalledDefinition($entity_type_id) {
-    return $this->installedDefinitions->get($entity_type_id . '.entity_type');
+    return $this->keyValueFactory->get('entity.definitions.installed')->get($entity_type_id . '.entity_type');
   }
 
   /**
@@ -1267,7 +1378,7 @@ public function useCaches($use_caches = FALSE) {
    */
   protected function setLastInstalledDefinition(EntityTypeInterface $entity_type) {
     $entity_type_id = $entity_type->id();
-    $this->installedDefinitions->set($entity_type_id . '.entity_type', $entity_type);
+    $this->keyValueFactory->get('entity.definitions.installed')->set($entity_type_id . '.entity_type', $entity_type);
   }
 
   /**
@@ -1277,18 +1388,18 @@ protected function setLastInstalledDefinition(EntityTypeInterface $entity_type)
    *   The entity type definition identifier.
    */
   protected function deleteLastInstalledDefinition($entity_type_id) {
-    $this->installedDefinitions->delete($entity_type_id . '.entity_type');
+    $this->keyValueFactory->get('entity.definitions.installed')->delete($entity_type_id . '.entity_type');
     // Clean up field storage definitions as well. Even if the entity type
     // isn't currently fieldable, there might be legacy definitions or an
     // empty array stored from when it was.
-    $this->installedDefinitions->delete($entity_type_id . '.field_storage_definitions');
+    $this->keyValueFactory->get('entity.definitions.installed')->delete($entity_type_id . '.field_storage_definitions');
   }
 
   /**
    * {@inheritdoc}
    */
   public function getLastInstalledFieldStorageDefinitions($entity_type_id) {
-    return $this->installedDefinitions->get($entity_type_id . '.field_storage_definitions', array());
+    return $this->keyValueFactory->get('entity.definitions.installed')->get($entity_type_id . '.field_storage_definitions', array());
   }
 
   /**
@@ -1300,7 +1411,7 @@ public function getLastInstalledFieldStorageDefinitions($entity_type_id) {
    *   An array of field storage definitions.
    */
   protected function setLastInstalledFieldStorageDefinitions($entity_type_id, array $storage_definitions) {
-    $this->installedDefinitions->set($entity_type_id . '.field_storage_definitions', $storage_definitions);
+    $this->keyValueFactory->get('entity.definitions.installed')->set($entity_type_id . '.field_storage_definitions', $storage_definitions);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
index d75e28f..02f7763 100644
--- a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
@@ -9,12 +9,14 @@
 
 use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
 use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldDefinitionListenerInterface;
 use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
 
 /**
  * Provides an interface for entity type managers.
  */
-interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListenerInterface, EntityBundleListenerInterface, FieldStorageDefinitionListenerInterface, CachedDiscoveryInterface {
+interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListenerInterface, EntityBundleListenerInterface, FieldStorageDefinitionListenerInterface, FieldDefinitionListenerInterface, CachedDiscoveryInterface {
 
   /**
    * Builds a list of entity type labels suitable for a Form API options list.
diff --git a/core/lib/Drupal/Core/Field/FieldDefinitionListenerInterface.php b/core/lib/Drupal/Core/Field/FieldDefinitionListenerInterface.php
new file mode 100644
index 0000000..b74bcf3
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/FieldDefinitionListenerInterface.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\FieldDefinitionListenerInterface.
+ */
+
+namespace Drupal\Core\Field;
+
+/**
+ * Defines an interface for reacting to field creation, deletion, and updates.
+ */
+interface FieldDefinitionListenerInterface {
+
+  /**
+   * Reacts to the creation of a field.
+   *
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The field definition created.
+   */
+  public function onFieldDefinitionCreate(FieldDefinitionInterface $field_definition);
+
+  /**
+   * Reacts to the update of a field.
+   *
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The field definition being updated.
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $original
+   *   The original field definition; i.e., the definition before the update.
+   */
+  public function onFieldDefinitionUpdate(FieldDefinitionInterface $field_definition, FieldDefinitionInterface $original);
+
+  /**
+   * Reacts to the deletion of a field.
+   *
+   * Stored values should not be wiped at once, but marked as 'deleted' so that
+   * they can go through a proper purge process later on.
+   *
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The field definition being deleted.
+   */
+  public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition);
+
+}
diff --git a/core/modules/field/src/Entity/FieldConfig.php b/core/modules/field/src/Entity/FieldConfig.php
index 20d168e..500fa42 100644
--- a/core/modules/field/src/Entity/FieldConfig.php
+++ b/core/modules/field/src/Entity/FieldConfig.php
@@ -160,7 +160,7 @@ public function preSave(EntityStorageInterface $storage) {
 
     if ($this->isNew()) {
       // Notify the entity storage.
-      $entity_manager->getStorage($this->entity_type)->onFieldDefinitionCreate($this);
+      $entity_manager->onFieldDefinitionCreate($this);
     }
     else {
       // Some updates are always disallowed.
@@ -174,7 +174,7 @@ public function preSave(EntityStorageInterface $storage) {
         throw new FieldException("Cannot change an existing field's storage.");
       }
       // Notify the entity storage.
-      $entity_manager->getStorage($this->entity_type)->onFieldDefinitionUpdate($this, $this->original);
+      $entity_manager->onFieldDefinitionUpdate($this, $this->original);
     }
 
     parent::preSave($storage);
@@ -221,7 +221,7 @@ public static function postDelete(EntityStorageInterface $storage, array $fields
     // Notify the entity storage.
     foreach ($fields as $field) {
       if (!$field->deleted) {
-        \Drupal::entityManager()->getStorage($field->entity_type)->onFieldDefinitionDelete($field);
+        \Drupal::entityManager()->onFieldDefinitionDelete($field);
       }
     }
 
diff --git a/core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php b/core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php
index 0866f7a..f1385ca 100644
--- a/core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php
@@ -66,6 +66,11 @@ public function testCustomBundleFieldUsage() {
       'type' => 'custom',
     ]);
     $this->assertTrue($entity->hasField('custom_bundle_field'));
+
+    // Ensure that the field exists in the field map.
+    $field_map = \Drupal::entityManager()->getFieldMap();
+    $this->assertEqual($field_map['entity_test']['custom_bundle_field'], ['type' => 'string', 'bundles' => ['custom' => 'custom']]);
+
     $entity->custom_bundle_field->value = 'swanky';
     $entity->save();
     $storage->resetCache();
@@ -102,6 +107,10 @@ public function testCustomBundleFieldUsage() {
       ->execute();
     $this->assertEqual(1, $result->fetchField(), 'Field data has been deleted');
 
+    // Ensure that the field no longer exists in the field map.
+    $field_map = \Drupal::entityManager()->getFieldMap();
+    $this->assertFalse(isset($field_map['entity_test']['custom_bundle_field']));
+
     // @todo Test field purge and table deletion once supported. See
     //   https://www.drupal.org/node/2282119.
     // $this->assertFalse($this->database->schema()->tableExists($table), 'Custom field table was deleted');
diff --git a/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module
index 1da8630..45892a8 100644
--- a/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module
+++ b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module
@@ -68,6 +68,21 @@ function entity_schema_test_entity_bundle_field_info(EntityTypeInterface $entity
 }
 
 /**
+ * Implements hook_entity_bundle_create().
+ */
+function entity_schema_test_entity_bundle_create($entity_type_id, $bundle) {
+  if ($entity_type_id == 'entity_test' && $bundle == 'custom') {
+    $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id);
+    $field_definitions = entity_schema_test_entity_bundle_field_info($entity_type, $bundle);
+    $field_definitions['custom_bundle_field']
+      ->setTargetEntityTypeId($entity_type_id)
+      ->setTargetBundle($bundle);
+    // Notify the entity storage that we just created a new field.
+    \Drupal::entityManager()->onFieldDefinitionCreate($field_definitions['custom_bundle_field']);
+  }
+}
+
+/**
  * Implements hook_entity_bundle_delete().
  */
 function entity_schema_test_entity_bundle_delete($entity_type_id, $bundle) {
@@ -78,7 +93,6 @@ function entity_schema_test_entity_bundle_delete($entity_type_id, $bundle) {
       ->setTargetEntityTypeId($entity_type_id)
       ->setTargetBundle($bundle);
     // Notify the entity storage that our field is gone.
-    \Drupal::entityManager()->getStorage($entity_type_id)
-      ->onFieldDefinitionDelete($field_definitions['custom_bundle_field']);
+    \Drupal::entityManager()->onFieldDefinitionDelete($field_definitions['custom_bundle_field']);
   }
 }
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
index 9b1053f..d4effdd 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
@@ -114,11 +114,11 @@ class EntityManagerTest extends UnitTestCase {
   protected $typedDataManager;
 
   /**
-   * The keyvalue collection for tracking installed definitions.
+   * The keyvalue factory.
    *
-   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface|\PHPUnit_Framework_MockObject_MockObject
+   * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface|\PHPUnit_Framework_MockObject_MockObject
    */
-  protected $installedDefinitions;
+  protected $keyValueFactory;
 
   /**
    * The event dispatcher.
@@ -183,7 +183,7 @@ protected function setUp() {
 
     $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
 
-    $this->installedDefinitions = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
+    $this->keyValueFactory = $this->getMock('Drupal\Core\KeyValueStore\KeyValueFactoryInterface');
 
     $this->container = $this->getContainerWithCacheTagsInvalidator($this->cacheTagsInvalidator);
     $this->container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
@@ -239,7 +239,7 @@ protected function setUpEntityManager($definitions = array()) {
       ->method('getDefinitions')
       ->will($this->returnValue($definitions));
 
-    $this->entityManager = new TestEntityManager(new \ArrayObject(), $this->moduleHandler, $this->cacheBackend, $this->languageManager, $this->translationManager, $this->getClassResolverStub(), $this->typedDataManager, $this->installedDefinitions, $this->eventDispatcher);
+    $this->entityManager = new TestEntityManager(new \ArrayObject(), $this->moduleHandler, $this->cacheBackend, $this->languageManager, $this->translationManager, $this->getClassResolverStub(), $this->typedDataManager, $this->keyValueFactory, $this->eventDispatcher);
     $this->entityManager->setContainer($this->container);
     $this->entityManager->setDiscovery($this->discovery);
   }
@@ -1250,7 +1250,7 @@ public function testGetFieldMap() {
     $id_definition = $this->getMockBuilder('Drupal\Core\Field\BaseFieldDefinition')
       ->disableOriginalConstructor()
       ->getMock();
-    $id_definition->expects($this->exactly(2))
+    $id_definition->expects($this->once())
       ->method('getType')
       ->will($this->returnValue('integer'));
     $base_field_definitions = array(
@@ -1258,21 +1258,20 @@ public function testGetFieldMap() {
     );
     $entity_class::$baseFieldDefinitions = $base_field_definitions;
 
-    // Set up a by bundle field definition that only exists on one bundle.
-    $bundle_definition = $this->getMockBuilder('Drupal\Core\Field\BaseFieldDefinition')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $bundle_definition->expects($this->once())
-      ->method('getType')
-      ->will($this->returnValue('string'));
-    $entity_class::$bundleFieldDefinitions = array(
-      'test_entity_type' => array(
-        'first_bundle' => array(),
-        'second_bundle' => array(
-          'by_bundle' => $bundle_definition,
-        ),
-      ),
-    );
+    // Set up the stored bundle field map.
+    $key_value_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
+    $this->keyValueFactory->expects($this->once())
+      ->method('get')
+      ->with('entity.definitions.bundle_field_map')
+      ->willReturn($key_value_store);
+    $key_value_store->expects($this->once())
+      ->method('getAll')
+      ->willReturn(['test_entity_type' => [
+        'by_bundle' => [
+          'type' => 'string',
+          'bundles' => ['second_bundle' => 'second_bundle'],
+        ],
+      ]]);
 
     // Set up a non-content entity type.
     $non_content_entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
@@ -1303,11 +1302,11 @@ public function testGetFieldMap() {
       'test_entity_type' => array(
         'id' => array(
           'type' => 'integer',
-          'bundles' => array('first_bundle', 'second_bundle'),
+          'bundles' => array('first_bundle' => 'first_bundle', 'second_bundle' => 'second_bundle'),
         ),
         'by_bundle' => array(
           'type' => 'string',
-          'bundles' => array('second_bundle'),
+          'bundles' => array('second_bundle' => 'second_bundle'),
         ),
       )
     );
@@ -1322,11 +1321,11 @@ public function testGetFieldMapFromCache() {
       'test_entity_type' => array(
         'id' => array(
           'type' => 'integer',
-          'bundles' => array('first_bundle', 'second_bundle'),
+          'bundles' => array('first_bundle' => 'first_bundle', 'second_bundle' => 'second_bundle'),
         ),
         'by_bundle' => array(
           'type' => 'string',
-          'bundles' => array('second_bundle'),
+          'bundles' => array('second_bundle' => 'second_bundle'),
         ),
       )
     );
@@ -1393,7 +1392,7 @@ public function testGetFieldMapByFieldType() {
     $id_definition = $this->getMockBuilder('Drupal\Core\Field\BaseFieldDefinition')
       ->disableOriginalConstructor()
       ->getMock();
-    $id_definition->expects($this->exactly(2))
+    $id_definition->expects($this->once())
       ->method('getType')
       ->will($this->returnValue('integer'));
     $base_field_definitions = array(
@@ -1401,21 +1400,20 @@ public function testGetFieldMapByFieldType() {
     );
     $entity_class::$baseFieldDefinitions = $base_field_definitions;
 
-    // Set up a by bundle field definition that only exists on one bundle.
-    $bundle_definition = $this->getMockBuilder('Drupal\Core\Field\BaseFieldDefinition')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $bundle_definition->expects($this->once())
-      ->method('getType')
-      ->will($this->returnValue('string'));
-    $entity_class::$bundleFieldDefinitions = array(
-      'test_entity_type' => array(
-        'first_bundle' => array(),
-        'second_bundle' => array(
-          'by_bundle' => $bundle_definition,
-        ),
-      ),
-    );
+    // Set up the stored bundle field map.
+    $key_value_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
+    $this->keyValueFactory->expects($this->once())
+      ->method('get')
+      ->with('entity.definitions.bundle_field_map')
+      ->willReturn($key_value_store);
+    $key_value_store->expects($this->once())
+      ->method('getAll')
+      ->willReturn(['test_entity_type' => [
+        'by_bundle' => [
+          'type' => 'string',
+          'bundles' => ['second_bundle' => 'second_bundle'],
+        ],
+      ]]);
 
     // Mock the base field definition override.
     $override_entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
@@ -1447,6 +1445,271 @@ public function testGetFieldMapByFieldType() {
   }
 
   /**
+   * @covers ::onFieldDefinitionCreate
+   */
+  public function testonFieldDefinitionCreateNewField() {
+    $field_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getTargetEntityTypeId')
+      ->willReturn('test_entity_type');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getTargetBundle')
+      ->willReturn('test_bundle');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getName')
+      ->willReturn('test_field');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getType')
+      ->willReturn('test_type');
+
+    $storage = $this->getMock('Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface');
+
+    $class = get_class($storage);
+    $entity = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $entity->expects($this->once())
+      ->method('getHandlerClass')
+      ->with('storage')
+      ->will($this->returnValue($class));
+    $this->setUpEntityManager(array('test_entity_type' => $entity));
+
+    // The entity manager will instantiate a new object with the given class
+    // name. Define the mock expectations on that.
+    $storage = $this->entityManager->getStorage('test_entity_type');
+    $storage->expects($this->once())
+      ->method('onFieldDefinitionCreate')
+      ->with($field_definition);
+
+    // Set up the stored bundle field map.
+    $key_value_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
+    $this->keyValueFactory->expects($this->exactly(2))
+      ->method('get')
+      ->with('entity.definitions.bundle_field_map')
+      ->willReturn($key_value_store);
+    $key_value_store->expects($this->once())
+      ->method('get')
+      ->with('test_entity_type')
+      ->willReturn([]);
+    $key_value_store->expects($this->once())
+      ->method('set')
+      ->with('test_entity_type', [
+        'test_field' => [
+          'type' => 'test_type',
+          'bundles' => ['test_bundle' => 'test_bundle'],
+        ],
+      ]);
+
+    $this->entityManager->onFieldDefinitionCreate($field_definition);
+  }
+
+  /**
+   * @covers ::onFieldDefinitionCreate
+   */
+  public function testonFieldDefinitionCreateExistingField() {
+    $field_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getTargetEntityTypeId')
+      ->willReturn('test_entity_type');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getTargetBundle')
+      ->willReturn('test_bundle');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getName')
+      ->willReturn('test_field');
+
+    $storage = $this->getMock('Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface');
+    $class = get_class($storage);
+    $entity = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $entity->expects($this->once())
+      ->method('getHandlerClass')
+      ->with('storage')
+      ->will($this->returnValue($class));
+    $this->setUpEntityManager(array('test_entity_type' => $entity));
+
+    // The entity manager will instantiate a new object with the given class
+    // name. Define the mock expectations on that.
+    $storage = $this->entityManager->getStorage('test_entity_type');
+    $storage->expects($this->once())
+      ->method('onFieldDefinitionCreate')
+      ->with($field_definition);
+
+    // Set up the stored bundle field map.
+    $key_value_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
+    $this->keyValueFactory->expects($this->exactly(2))
+      ->method('get')
+      ->with('entity.definitions.bundle_field_map')
+      ->willReturn($key_value_store);
+    $key_value_store->expects($this->once())
+      ->method('get')
+      ->with('test_entity_type')
+      ->willReturn([
+        'test_field' => [
+          'type' => 'test_type',
+          'bundles' => ['existing_bundle' => 'existing_bundle'],
+        ],
+      ]);
+    $key_value_store->expects($this->once())
+      ->method('set')
+      ->with('test_entity_type', [
+        'test_field' => [
+          'type' => 'test_type',
+          'bundles' => ['existing_bundle' => 'existing_bundle', 'test_bundle' => 'test_bundle'],
+        ],
+      ]);
+
+    $this->entityManager->onFieldDefinitionCreate($field_definition);
+  }
+
+  /**
+   * @covers ::onFieldDefinitionUpdate
+   */
+  public function testonFieldDefinitionUpdate() {
+    $field_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getTargetEntityTypeId')
+      ->willReturn('test_entity_type');
+
+    $storage = $this->getMock('Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface');
+
+    $class = get_class($storage);
+    $entity = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $entity->expects($this->once())
+      ->method('getHandlerClass')
+      ->with('storage')
+      ->will($this->returnValue($class));
+    $this->setUpEntityManager(array('test_entity_type' => $entity));
+
+    // The entity manager will instantiate a new object with the given class
+    // name. Define the mock expectations on that.
+    $storage = $this->entityManager->getStorage('test_entity_type');
+    $storage->expects($this->once())
+      ->method('onFieldDefinitionUpdate')
+      ->with($field_definition);
+
+    $this->entityManager->onFieldDefinitionUpdate($field_definition, $field_definition);
+  }
+
+  /**
+   * @covers ::onFieldDefinitionDelete
+   */
+  public function testonFieldDefinitionDeleteMultipleBundles() {
+    $field_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getTargetEntityTypeId')
+      ->willReturn('test_entity_type');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getTargetBundle')
+      ->willReturn('test_bundle');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getName')
+      ->willReturn('test_field');
+
+    $storage = $this->getMock('Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface');
+    $class = get_class($storage);
+    $entity = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $entity->expects($this->once())
+      ->method('getHandlerClass')
+      ->with('storage')
+      ->will($this->returnValue($class));
+    $this->setUpEntityManager(array('test_entity_type' => $entity));
+
+    // The entity manager will instantiate a new object with the given class
+    // name. Define the mock expectations on that.
+    $storage = $this->entityManager->getStorage('test_entity_type');
+    $storage->expects($this->once())
+      ->method('onFieldDefinitionDelete')
+      ->with($field_definition);
+
+    // Set up the stored bundle field map.
+    $key_value_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
+    $this->keyValueFactory->expects($this->exactly(2))
+      ->method('get')
+      ->with('entity.definitions.bundle_field_map')
+      ->willReturn($key_value_store);
+    $key_value_store->expects($this->once())
+      ->method('get')
+      ->with('test_entity_type')
+      ->willReturn([
+        'test_field' => [
+          'type' => 'test_type',
+          'bundles' => ['test_bundle' => 'test_bundle'],
+        ],
+        'second_field' => [
+          'type' => 'test_type',
+          'bundles' => ['test_bundle' => 'test_bundle'],
+        ],
+      ]);
+    $key_value_store->expects($this->once())
+      ->method('set')
+      ->with('test_entity_type', [
+        'second_field' => [
+          'type' => 'test_type',
+          'bundles' => ['test_bundle' => 'test_bundle'],
+        ],
+      ]);
+
+    $this->entityManager->onFieldDefinitionDelete($field_definition);
+  }
+
+
+  /**
+   * @covers ::onFieldDefinitionDelete
+   */
+  public function testonFieldDefinitionDeleteSingleBundles() {
+    $field_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getTargetEntityTypeId')
+      ->willReturn('test_entity_type');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getTargetBundle')
+      ->willReturn('test_bundle');
+    $field_definition->expects($this->atLeastOnce())
+      ->method('getName')
+      ->willReturn('test_field');
+
+    $storage = $this->getMock('Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface');
+    $class = get_class($storage);
+    $entity = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $entity->expects($this->once())
+      ->method('getHandlerClass')
+      ->with('storage')
+      ->will($this->returnValue($class));
+    $this->setUpEntityManager(array('test_entity_type' => $entity));
+
+    // The entity manager will instantiate a new object with the given class
+    // name. Define the mock expectations on that.
+    $storage = $this->entityManager->getStorage('test_entity_type');
+    $storage->expects($this->once())
+      ->method('onFieldDefinitionDelete')
+      ->with($field_definition);
+
+    // Set up the stored bundle field map.
+    $key_value_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
+    $this->keyValueFactory->expects($this->exactly(2))
+      ->method('get')
+      ->with('entity.definitions.bundle_field_map')
+      ->willReturn($key_value_store);
+    $key_value_store->expects($this->once())
+      ->method('get')
+      ->with('test_entity_type')
+      ->willReturn([
+        'test_field' => [
+          'type' => 'test_type',
+          'bundles' => ['test_bundle' => 'test_bundle', 'second_bundle' => 'second_bundle'],
+        ],
+      ]);
+    $key_value_store->expects($this->once())
+      ->method('set')
+      ->with('test_entity_type', [
+        'test_field' => [
+          'type' => 'test_type',
+          'bundles' => ['second_bundle' => 'second_bundle'],
+        ],
+      ]);
+
+    $this->entityManager->onFieldDefinitionDelete($field_definition);
+  }
+
+  /**
    * @covers ::getEntityTypeFromClass
    */
   public function testGetEntityTypeFromClass() {
