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..3c62b17
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityEvents.php
@@ -0,0 +1,105 @@
+<?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[]
+   */
+  const HOOK_TO_EVENT_MAP = [
+    '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';
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index f583121..2173485 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -167,6 +167,11 @@ protected function invokeHook($hook, EntityInterface $entity) {
     $this->moduleHandler()->invokeAll($this->entityTypeId . '_' . $hook, array($entity));
     // Invoke the respective entity-level hook.
     $this->moduleHandler()->invokeAll('entity_' . $hook, array($entity));
+
+    $hook_to_event_map = EntityEvents::HOOK_TO_EVENT_MAP;
+    if (isset($hook_to_event_map[$hook])) {
+      \Drupal::service('event_dispatcher')->dispatch($hook_to_event_map[$hook], new EntityEvent($entity));
+    }
   }
 
   /**
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 4fbdba3..ef4a51c 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 56b4b00..064f61e 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
@@ -14,6 +14,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
@@ -980,10 +981,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()
