diff --git a/core/lib/Drupal/Core/Entity/EntityEvent.php b/core/lib/Drupal/Core/Entity/EntityEvent.php
new file mode 100644
index 0000000..f504666
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityEvent.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Represents various entity events.
+ *
+ * @see \Drupal\Core\Entity\EntityEvents
+ * @see \Drupal\Core\Entity\EntityStorageBase::invokeHook()
+ */
+class EntityEvent extends Event {
+
+  /**
+   * The entity object.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $entity;
+
+  /**
+   * EntityEvent constructor.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity object.
+   */
+  public function __construct(EntityInterface $entity) {
+    $this->entity = $entity;
+  }
+
+  /**
+   * Returns the entity wrapped by this event.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   The entity object.
+   */
+  public function getEntity() {
+    return $this->entity;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityEvents.php b/core/lib/Drupal/Core/Entity/EntityEvents.php
new file mode 100644
index 0000000..94972df
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityEvents.php
@@ -0,0 +1,183 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Defines events for entity CRUD.
+ *
+ * @see \Drupal\Core\Entity\EntityEvent
+ * @see \Drupal\Core\Entity\EntityStorageBase::invokeHook()
+ */
+final class EntityEvents {
+
+  /**
+   * Maps entity hook names to event names.
+   *
+   * @var string[]
+   */
+  public static $hookToEventMap = [
+    'create' => self::CREATE,
+    'presave' => self::PRESAVE,
+    'insert' => self::INSERT,
+    'update' => self::UPDATE,
+    'predelete' => self::PREDELETE,
+    'delete' => self::DELETE,
+  ];
+
+  /**
+   * Name of the event fired when creating an entity.
+   *
+   * This hook runs after a new entity object has just been instantiated.
+   *
+   * @see hook_entity_create()
+   *
+   * @Event
+   *
+   * @var string
+   */
+  const CREATE = 'entity.create';
+
+  /**
+   * Name of the event fired before an entity is created or updated.
+   *
+   * You can get the original entity object from $entity->original when it is an
+   * update of the entity.
+   *
+   * @see hook_entity_presave()
+   *
+   * @Event
+   *
+   * @var string
+   */
+  const PRESAVE = 'entity.presave';
+
+  /**
+   * Name of the event fired after creating an entity.
+   *
+   * This event fires once the entity has been stored. Note that changes to the
+   * entity made by subscribers to the event will not be saved.
+   *
+   * @see hook_entity_insert()
+   *
+   * @Event
+   *
+   * @var string
+   */
+  const INSERT = 'entity.insert';
+
+  /**
+   * Name of the event fired after updating an existing entity.
+   *
+   * This event fires once the entity has been stored. Note that changes to the
+   * entity made by subscribers to the event will not be saved. Get the original
+   * entity object from $entity->original.
+   *
+   * @see hook_entity_update()
+   *
+   * @Event
+   *
+   * @var string
+   */
+  const UPDATE = 'entity.update';
+
+  /**
+   * Name of the event fired before deleting an entity.
+   *
+   * @see hook_entity_predelete()
+   *
+   * @Event
+   *
+   * @var string
+   */
+  const PREDELETE = 'entity.predelete';
+
+  /**
+   * Name of the event fired after deleting an entity.
+   *
+   * @see hook_entity_delete()
+   *
+   * @Event
+   *
+   * @var string
+   */
+  const DELETE = 'entity.delete';
+
+  /**
+   * Returns the event name for creation of an entity of a specific type.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return string
+   *   The event name.
+   */
+  public static function create($entity_type_id) {
+    return self::CREATE . '.' . $entity_type_id;
+  }
+
+  /**
+   * Returns the event name when presaving an entity of a specific type.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return string
+   *   The event name.
+   */
+  public static function presave($entity_type_id) {
+    return self::PRESAVE . '.' . $entity_type_id;
+  }
+
+  /**
+   * Returns the event name for inserting an entity of a specific type.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return string
+   *   The event name.
+   */
+  public static function insert($entity_type_id) {
+    return self::INSERT . '.' . $entity_type_id;
+  }
+
+  /**
+   * Returns the event name for updating an entity of a specific type.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return string
+   *   The event name.
+   */
+  public static function update($entity_type_id) {
+    return self::UPDATE . '.' . $entity_type_id;
+  }
+
+  /**
+   * Returns the event name just before deleting an entity of a specific type.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return string
+   *   The event name.
+   */
+  public static function predelete($entity_type_id) {
+    return self::PREDELETE . '.' . $entity_type_id;
+  }
+
+  /**
+   * Returns the event name for deletion of an entity of a specific type.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return string
+   *   The event name.
+   */
+  public static function delete($entity_type_id) {
+    return self::DELETE . '.' . $entity_type_id;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 99698df..cf95247 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -167,6 +167,23 @@ protected function invokeHook($hook, EntityInterface $entity) {
     $this->moduleHandler()->invokeAll($this->entityTypeId . '_' . $hook, [$entity]);
     // Invoke the respective entity-level hook.
     $this->moduleHandler()->invokeAll('entity_' . $hook, [$entity]);
+
+    // Dispatch events for the invoked hook.
+    /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */
+    $event_dispatcher = \Drupal::service('event_dispatcher');
+    $event = new EntityEvent($entity);
+
+    // Dispatch an event for the entity type-specific hook (i.e.,
+    // hook_ENTITY_TYPE_$hook).
+    if (method_exists(EntityEvents::class, $hook)) {
+      $event_name = call_user_func([EntityEvents::class, $hook], $this->entityTypeId);
+      $event_dispatcher->dispatch($event_name, $event);
+    }
+
+    // Dispatch an event for the entity-level hook (i.e., hook_entity_$hook).
+    if (isset(EntityEvents::$hookToEventMap[$hook])) {
+      $event_dispatcher->dispatch(EntityEvents::$hookToEventMap[$hook], $event);
+    }
   }
 
   /**
diff --git a/core/modules/system/tests/modules/entity_test_event/entity_test_event.info.yml b/core/modules/system/tests/modules/entity_test_event/entity_test_event.info.yml
new file mode 100644
index 0000000..5089f4b
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test_event/entity_test_event.info.yml
@@ -0,0 +1,4 @@
+name: entity_test_event
+type: module
+package: Testing
+core: 8.x
diff --git a/core/modules/system/tests/modules/entity_test_event/entity_test_event.services.yml b/core/modules/system/tests/modules/entity_test_event/entity_test_event.services.yml
new file mode 100644
index 0000000..e20270c
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test_event/entity_test_event.services.yml
@@ -0,0 +1,5 @@
+services:
+  entity_test_event.event_subscriber:
+    class: \Drupal\entity_test_event\EventSubscriber\TestEventSubscriber
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/system/tests/modules/entity_test_event/src/EventSubscriber/TestEventSubscriber.php b/core/modules/system/tests/modules/entity_test_event/src/EventSubscriber/TestEventSubscriber.php
new file mode 100644
index 0000000..8566ba0
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test_event/src/EventSubscriber/TestEventSubscriber.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\entity_test_event\EventSubscriber;
+
+use Drupal\entity_test\Entity\EntityTest;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Drupal\Core\Entity\EntityEvents;
+use Drupal\Core\Entity\EntityEvent;
+
+class TestEventSubscriber implements EventSubscriberInterface {
+
+  public function onInsert(EntityEvent $event) {
+    if ($event->getEntity()->name->value === 'hei') {
+      $event->getEntity()->name->value .= ' ho';
+    }
+  }
+
+  public function onUpdate(EntityEvent $event) {
+    if ($event->getEntity()->name->value === 'hei') {
+      $event->getEntity()->name->value .= ' ho';
+    }
+  }
+
+  public function onDelete(EntityEvent $event) {
+    if ($event->getEntity()->name->value === 'hei ho') {
+      EntityTest::create([
+        'name' => 'hei_ho'
+      ])->save();
+      $event->getEntity()->name->value .= ' ho';
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[EntityEvents::INSERT] = 'onInsert';
+    $events[EntityEvents::UPDATE] = 'onUpdate';
+    $events[EntityEvents::DELETE] = 'onDelete';
+    return $events;
+  }
+
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityEventsTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityEventsTest.php
new file mode 100644
index 0000000..99faba98
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityEventsTest.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Entity;
+
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * @group Entity
+ */
+class EntityEventsTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['entity_test', 'entity_test_event', 'user'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('entity_test');
+  }
+
+  public function testEventsInsert() {
+    $entity = EntityTest::create([
+      'name' => 'hei',
+    ]);
+    $entity->save();
+    $this->assertEquals('hei ho', $entity->name->value);
+  }
+
+  public function testEventsUpdate() {
+    $entity = EntityTest::create([
+      'name' => 'meh',
+    ]);
+    $entity->save();
+    $this->assertEquals('meh', $entity->name->value);
+
+    $entity->name->value = 'hei';
+    $entity->save();
+    $this->assertEquals('hei ho', $entity->name->value);
+  }
+
+  public function testEventsDelete() {
+    $entities = \Drupal::entityTypeManager()->getStorage('entity_test')
+      ->loadByProperties(['name' => 'hei_ho']);
+    $this->assertCount(0, $entities);
+
+    $entity = EntityTest::create([
+      'name' => 'hei',
+    ]);
+    $entity->save();
+    $entity->delete();
+
+    // Note the delete event creates another entity.
+    $entities = \Drupal::entityTypeManager()->getStorage('entity_test')
+      ->loadByProperties(['name' => 'hei_ho']);
+    $this->assertCount(1, $entities);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php
index 4942701..6890147 100644
--- a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php
@@ -8,6 +8,7 @@
 use Drupal\Core\Language\Language;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * @coversDefaultClass \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage
@@ -119,12 +120,14 @@ protected function setUpKeyValueEntityStorage($uuid_key = 'uuid') {
     $this->languageManager->expects($this->any())
       ->method('getCurrentLanguage')
       ->will($this->returnValue($language));
+    $event_dispatcher = $this->prophesize(EventDispatcherInterface::class);
 
     $this->entityStorage = new KeyValueEntityStorage($this->entityType, $this->keyValueStore, $this->uuidService, $this->languageManager);
     $this->entityStorage->setModuleHandler($this->moduleHandler);
 
     $container = new ContainerBuilder();
     $container->set('entity.manager', $this->entityManager);
+    $container->set('event_dispatcher', $event_dispatcher->reveal());
     $container->set('language_manager', $this->languageManager);
     $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
     \Drupal::setContainer($container);
diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
index b3e339b..e18ae59 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Language\Language;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * @coversDefaultClass \Drupal\Core\Entity\Sql\SqlContentEntityStorage
@@ -1000,10 +1001,12 @@ public function testCreate() {
     $language_manager->expects($this->any())
       ->method('getCurrentLanguage')
       ->will($this->returnValue($language));
+    $event_dispatcher = $this->prophesize(EventDispatcherInterface::class);
 
     $this->container->set('language_manager', $language_manager);
     $this->container->set('entity.manager', $this->entityManager);
     $this->container->set('module_handler', $this->moduleHandler);
+    $this->container->set('event_dispatcher', $event_dispatcher->reveal());
 
     $entity = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
       ->disableOriginalConstructor()
