diff --git a/src/Entity/WorkflowTransition.php b/src/Entity/WorkflowTransition.php
index 7cb28aa0e..9a535ab18 100644
--- a/src/Entity/WorkflowTransition.php
+++ b/src/Entity/WorkflowTransition.php
@@ -8,13 +8,15 @@
 use Drupal\Core\Language\Language;
 use Drupal\user\Entity\User;
 use Drupal\user\UserInterface;
+use Drupal\workflow\Event\WorkflowTransitionEvent;
 use Drupal\workflow\WorkflowTypeAttributeTrait;
 
 /**
  * Implements an actual, executed, Transition.
  *
  * If a transition is executed, the new state is saved in the Field.
- * If a transition is saved, it is saved in table {workflow_transition_history}.
+ * If a transition is saved, it is saved in table
+ * {workflow_transition_history}.
  *
  * @ContentEntityType(
  *   id = "workflow_transition",
@@ -35,7 +37,8 @@
  *        "add" = "Drupal\workflow\Form\WorkflowTransitionForm",
  *        "delete" = "Drupal\Core\Entity\EntityDeleteForm",
  *        "edit" = "Drupal\workflow\Form\WorkflowTransitionForm",
- *        "revert" = "Drupal\workflow_operations\Form\WorkflowTransitionRevertForm",
+ *        "revert" =
+ *   "Drupal\workflow_operations\Form\WorkflowTransitionRevertForm",
  *      },
  *     "views_data" = "Drupal\workflow\WorkflowTransitionViewsData",
  *   },
@@ -71,15 +74,22 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
    * Cache data.
    */
   protected $entity = NULL; // Use WorkflowTransition->getTargetEntity() to fetch this.
+
   protected $user = NULL; // Use WorkflowTransition->getOwner() to fetch this.
 
   /*
    * Extra data: describe the state of the transition.
    */
   protected $is_scheduled;
+
   protected $is_executed;
+
   protected $is_forced = FALSE;
 
+  const PRE_TRANSITION = "pre_transition";
+
+  const POST_TRANSITION = "post_transition";
+
   /**
    * Entity class functions.
    */
@@ -93,12 +103,15 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition
    *
    * @param bool $bundle
    * @param array $translations
-   * @internal param string $entity_type The entity type of the attached $entity.
+   *
+   * @internal param string $entity_type The entity type of the attached
+   *   $entity.
    * @see entity_create()
    *
    * No arguments passed, when loading from DB.
    * All arguments must be passed, when creating an object programmatically.
-   * One argument $entity may be passed, only to directly call delete() afterwards.
+   * One argument $entity may be passed, only to directly call delete()
+   *   afterwards.
    */
   public function __construct(array $values = [], $entityType = 'workflow_transition', $bundle = FALSE, $translations = []) {
     // Please be aware that $entity_type and $entityType are different things!
@@ -151,7 +164,8 @@ public function setValues($to_sid, $uid = NULL, $timestamp = NULL, $comment = ''
 
     $this->set('to_sid', $to_sid);
     $this->setOwnerId($uid);
-    $this->setTimestamp($timestamp == NULL ? \Drupal::time()->getRequestTime() : $timestamp);
+    $this->setTimestamp($timestamp == NULL ? \Drupal::time()
+      ->getRequestTime() : $timestamp);
     $this->setComment($comment);
 
     // If constructor is called with new() and arguments.
@@ -283,10 +297,11 @@ public static function loadMultipleByProperties($entity_type, array $entity_ids,
 
   /**
    * Determines if the Transition is valid and can be executed.
-   * @todo: add to isAllowed() ?
-   * @todo: add checks to WorkflowTransitionElement ?
    *
    * @return bool
+   * @todo: add checks to WorkflowTransitionElement ?
+   *
+   * @todo: add to isAllowed() ?
    */
   public function isValid() {
     // Load the entity, if not already loaded.
@@ -333,7 +348,7 @@ protected function isEmpty() {
       }
     }
 
-    return true;
+    return TRUE;
   }
 
   /**
@@ -366,7 +381,8 @@ public function isAllowed(UserInterface $user, $force = FALSE) {
     /**
      * Get the object and its permissions.
      */
-    $config_transitions = $this->getWorkflow()->getTransitionsByStateId($this->getFromSid(), $this->getToSid());
+    $config_transitions = $this->getWorkflow()
+      ->getTransitionsByStateId($this->getFromSid(), $this->getToSid());
 
     /**
      * Determine if user has Access.
@@ -388,6 +404,7 @@ public function isAllowed(UserInterface $user, $force = FALSE) {
 
   /**
    * Determines if the State changes by this Transition.
+   *
    * @return bool
    */
   public function hasStateChange() {
@@ -475,7 +492,8 @@ public function execute($force = FALSE) {
       // Modules may veto the transition by returning FALSE.
       // (Even if $force is TRUE, but they shouldn't do that.)
       // P.S. The D7 hook_workflow 'transition permitted' is removed, in favour of below hook_workflow 'transition pre'.
-      $permitted = \Drupal::moduleHandler()->invokeAll('workflow', ['transition pre', $this, $user]);
+      $permitted = \Drupal::moduleHandler()
+        ->invokeAll('workflow', ['transition pre', $this, $user]);
       // Stop if a module says so.
       if (in_array(FALSE, $permitted, TRUE)) {
         // @todo: There is a watchdog error, but no UI-error. Is this OK?
@@ -492,7 +510,9 @@ public function execute($force = FALSE) {
       /*
        * Log the transition in {workflow_transition_scheduled}.
        */
+      $this->dispatchTransitionEvent($this::PRE_TRANSITION);
       $this->save();
+      $this->dispatchTransitionEvent($this::POST_TRANSITION);
     }
     else {
       // The transition is allowed, but not scheduled.
@@ -508,7 +528,9 @@ public function execute($force = FALSE) {
         /*
          * Log the transition in {workflow_transition_history}.
          */
+        $this->dispatchTransitionEvent($this::PRE_TRANSITION);
         $this->save();
+        $this->dispatchTransitionEvent($this::POST_TRANSITION);
 
         // Register state change with watchdog.
         if ($this->hasStateChange() && !empty($this->getWorkflow()->options['watchdog_log'])) {
@@ -592,11 +614,40 @@ private function _updateEntity() {
     $workflow_options = $this->getWorkflow()->options;
     $always_update_entity = isset($workflow_options['always_update_entity']) ?
       $workflow_options['always_update_entity'] : 0;
-    if ($always_update_entity) $entity->changed = $this->getTimestamp();
+    if ($always_update_entity) {
+      $entity->changed = $this->getTimestamp();
+    }
 
     $entity->save();
   }
 
+  /**
+   * Dispatches a transition event for the given phase.
+   *
+   * @param string $phase
+   *   The phase: pre_transition OR post_transition.
+   */
+  protected function dispatchTransitionEvent($phase) {
+
+    $workflow = $this->getWorkflow();
+
+    $event = new WorkflowTransitionEvent($this, $workflow, $this->getTargetEntity());
+    $event_dispatcher = \Drupal::getContainer()->get('event_dispatcher');
+
+    $events = [
+      // For example: 'my_custom_workflow.my_custom_transition.pre_transition'.
+      $this->getWorkflowId() . '.' . $this->getToSid() . '.' . $phase,
+      // For example: 'my_custom_workflow.pre_transition'.
+      $this->getWorkflowId() . '.' . $phase,
+      // For example: 'workflow.pre_transition'.
+      'workflow.' . $phase,
+    ];
+   
+    foreach ($events as $event_id) {
+      $event_dispatcher->dispatch($event_id, $event);
+    }
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -605,7 +656,11 @@ public function post_execute($force = FALSE) {
     workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: Test this snippet.
     if (!$this->isEmpty()) {
       $user = $this->getOwner();
-      \Drupal::moduleHandler()->invokeAll('workflow', ['transition post', $this, $user]);
+      \Drupal::moduleHandler()->invokeAll('workflow', [
+        'transition post',
+        $this,
+        $user,
+      ]);
     }
   }
 
@@ -653,7 +708,9 @@ public function getTargetEntity() {
 
     $entity_type = $this->getTargetEntityTypeId();
     if ($id = $this->getTargetEntityId()) {
-      $this->entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($id);
+      $this->entity = \Drupal::entityTypeManager()
+        ->getStorage($entity_type)
+        ->load($id);
     }
     return $this->entity;
   }
@@ -862,7 +919,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setReadOnly(TRUE)
       ->setSetting('unsigned', TRUE);
 
-//    $fields['wid'] = BaseFieldDefinition::create('string')
+    //    $fields['wid'] = BaseFieldDefinition::create('string')
     $fields['wid'] = BaseFieldDefinition::create('entity_reference')
       ->setLabel(t('Workflow Type'))
       ->setDescription(t('The workflow type the transition relates to.'))
@@ -870,17 +927,17 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setRequired(TRUE)
       ->setTranslatable(FALSE)
       ->setRevisionable(FALSE)
-//      ->setSetting('max_length', 32)
-//      ->setDisplayOptions('view', [
-//        'label' => 'hidden',
-//        'type' => 'string',
-//        'weight' => -5,
-//      ])
-//      ->setDisplayOptions('form', [
-//        'type' => 'string_textfield',
-//        'weight' => -5,
-//      ])
-//      ->setDisplayConfigurable('form', TRUE)
+      //      ->setSetting('max_length', 32)
+      //      ->setDisplayOptions('view', [
+      //        'label' => 'hidden',
+      //        'type' => 'string',
+      //        'weight' => -5,
+      //      ])
+      //      ->setDisplayOptions('form', [
+      //        'type' => 'string_textfield',
+      //        'weight' => -5,
+      //      ])
+      //      ->setDisplayConfigurable('form', TRUE)
     ;
 
     $fields['entity_type'] = BaseFieldDefinition::create('string')
@@ -910,17 +967,17 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setTranslatable(FALSE)
       ->setRevisionable(FALSE)
       ->setSetting('max_length', 32)
-//      ->setDisplayConfigurable('form', FALSE)
-//      ->setDisplayOptions('form', [
-//        'type' => 'string_textfield',
-//        'weight' => -5,
-//      ])
-//      ->setDisplayConfigurable('view', FALSE)
-//      ->setDisplayOptions('view', [
-//        'label' => 'hidden',
-//        'type' => 'string',
-//        'weight' => -5,
-//      ])
+      //      ->setDisplayConfigurable('form', FALSE)
+      //      ->setDisplayOptions('form', [
+      //        'type' => 'string_textfield',
+      //        'weight' => -5,
+      //      ])
+      //      ->setDisplayConfigurable('view', FALSE)
+      //      ->setDisplayOptions('view', [
+      //        'label' => 'hidden',
+      //        'type' => 'string',
+      //        'weight' => -5,
+      //      ])
     ;
 
     $fields['langcode'] = BaseFieldDefinition::create('language')
@@ -951,12 +1008,12 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setLabel(t('To state'))
       ->setDescription(t('The {workflow_states}.sid the entity transitioned to.'))
       ->setSetting('target_type', 'workflow_state')
-// @todo D8: activate this. Test with both Form and Widget.
-//      ->setDisplayOptions('form', [
-//        'type' => 'select',
-//        'weight' => -5,
-//      ])
-//      ->setDisplayConfigurable('form', TRUE)
+      // @todo D8: activate this. Test with both Form and Widget.
+      //      ->setDisplayOptions('form', [
+      //        'type' => 'select',
+      //        'weight' => -5,
+      //      ])
+      //      ->setDisplayConfigurable('form', TRUE)
       ->setReadOnly(TRUE);
 
     $fields['uid'] = BaseFieldDefinition::create('entity_reference')
@@ -965,43 +1022,43 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setTranslatable(TRUE)
       ->setSetting('target_type', 'user')
       ->setDefaultValue(0)
-//      ->setQueryable(FALSE)
-//      ->setSetting('handler', 'default')
-//      ->setDefaultValueCallback('Drupal\node\Entity\Node::getCurrentUserId')
-//      ->setTranslatable(TRUE)
-//      ->setDisplayOptions('view', [
-//        'label' => 'hidden',
-//        'type' => 'author',
-//        'weight' => 0,
-//      ])
-//      ->setDisplayOptions('form', [
-//        'type' => 'entity_reference_autocomplete',
-//        'weight' => 5,
-//        'settings' => [
-//          'match_operator' => 'CONTAINS',
-//          'size' => '60',
-//          'placeholder' => '',
-//        ],
-//      ])
-//      ->setDisplayConfigurable('form', TRUE),
+      //      ->setQueryable(FALSE)
+      //      ->setSetting('handler', 'default')
+      //      ->setDefaultValueCallback('Drupal\node\Entity\Node::getCurrentUserId')
+      //      ->setTranslatable(TRUE)
+      //      ->setDisplayOptions('view', [
+      //        'label' => 'hidden',
+      //        'type' => 'author',
+      //        'weight' => 0,
+      //      ])
+      //      ->setDisplayOptions('form', [
+      //        'type' => 'entity_reference_autocomplete',
+      //        'weight' => 5,
+      //        'settings' => [
+      //          'match_operator' => 'CONTAINS',
+      //          'size' => '60',
+      //          'placeholder' => '',
+      //        ],
+      //      ])
+      //      ->setDisplayConfigurable('form', TRUE),
       ->setRevisionable(TRUE);
 
     $fields['timestamp'] = BaseFieldDefinition::create('created')
       ->setLabel(t('Timestamp'))
       ->setDescription(t('The time that the current transition was executed.'))
-//      ->setQueryable(FALSE)
-//      ->setTranslatable(TRUE)
-//      ->setDisplayOptions('view', [
-//        'label' => 'hidden',
-//        'type' => 'timestamp',
-//        'weight' => 0,
-//      ])
-// @todo D8: activate this. Test with both Form and Widget.
-//      ->setDisplayOptions('form', [
-//        'type' => 'datetime_timestamp',
-//        'weight' => 10,
-//      ])
-//      ->setDisplayConfigurable('form', TRUE);
+      //      ->setQueryable(FALSE)
+      //      ->setTranslatable(TRUE)
+      //      ->setDisplayOptions('view', [
+      //        'label' => 'hidden',
+      //        'type' => 'timestamp',
+      //        'weight' => 0,
+      //      ])
+      // @todo D8: activate this. Test with both Form and Widget.
+      //      ->setDisplayOptions('form', [
+      //        'type' => 'datetime_timestamp',
+      //        'weight' => 10,
+      //      ])
+      //      ->setDisplayConfigurable('form', TRUE);
       ->setRevisionable(TRUE);
 
     $fields['comment'] = BaseFieldDefinition::create('string_long')
@@ -1009,15 +1066,15 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setDescription(t('The comment explaining this transition.'))
       ->setRevisionable(TRUE)
       ->setTranslatable(TRUE)
-// @todo D8: activate this. Test with both Form and Widget.
-//      ->setDisplayOptions('form', [
-//        'type' => 'string_textarea',
-//        'weight' => 25,
-//        'settings' => [
-//          'rows' => 4,
-//        ],
-//      ])
-//      ->setDisplayConfigurable('form', FALSE)
+      // @todo D8: activate this. Test with both Form and Widget.
+      //      ->setDisplayOptions('form', [
+      //        'type' => 'string_textarea',
+      //        'weight' => 25,
+      //        'settings' => [
+      //          'rows' => 4,
+      //        ],
+      //      ])
+      //      ->setDisplayConfigurable('form', FALSE)
     ;
 
     return $fields;
@@ -1043,8 +1100,12 @@ public function logError($message, $type = 'error', $from_sid = '', $to_sid = ''
       '%entity_id' => $this->getTargetEntityId(),
       '%entity_label' => $entity ? $entity->label() : '',
       '@entity_type' => ($entity) ? $entity->getEntityTypeId() : '',
-      '@entity_type_label' => ($entity) ? $entity->getEntityType()->getLabel() : '',
-      'link' => ($this->getTargetEntityId() && $this->getTargetEntity()->hasLinkTemplate('canonical')) ? $this->getTargetEntity()->toLink(t('View'))->toString() : '',
+      '@entity_type_label' => ($entity) ? $entity->getEntityType()
+        ->getLabel() : '',
+      'link' => ($this->getTargetEntityId() && $this->getTargetEntity()
+          ->hasLinkTemplate('canonical')) ? $this->getTargetEntity()
+        ->toLink(t('View'))
+        ->toString() : '',
     ];
     ($type == 'error') ? \Drupal::logger('workflow')->error($message, $t_args)
       : \Drupal::logger('workflow')->notice($message, $t_args);
@@ -1056,7 +1117,8 @@ public function logError($message, $type = 'error', $from_sid = '', $to_sid = ''
   public function dpm($function = '') {
     $transition = $this;
     $entity = $transition->getTargetEntity();
-    $time = \Drupal::service('date.formatter')->format($transition->getTimestamp());
+    $time = \Drupal::service('date.formatter')
+      ->format($transition->getTimestamp());
     // Do this extensive $user_name lines, for some troubles with Action.
     $user = $transition->getOwner();
     $user_name = ($user) ? $user->getAccountName() : 'unknown username';
diff --git a/src/Event/WorkflowTransitionEvent.php b/src/Event/WorkflowTransitionEvent.php
new file mode 100644
index 000000000..7d102e124
--- /dev/null
+++ b/src/Event/WorkflowTransitionEvent.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace Drupal\workflow\Event;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\workflow\Entity\WorkflowInterface;
+use Drupal\workflow\Entity\WorkflowTransition;
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Defines the workflow transition event.
+ */
+class WorkflowTransitionEvent extends Event {
+
+  /**
+   * The transition.
+   *
+   * @var \Drupal\workflow\Entity\WorkflowTransition
+   */
+  protected $transition;
+
+  /**
+   * The workflow.
+   *
+   * @var \Drupal\workflow\Entity\WorkflowInterface
+   */
+  protected $workflow;
+
+  /**
+   * The entity.
+   *
+   * @var \Drupal\Core\Entity\ContentEntityInterface
+   */
+  protected $entity;
+
+  /**
+   * The state field name.
+   *
+   * @var string
+   */
+  //protected $fieldName;
+
+  /**
+   * Constructs a new WorkflowTransitionEvent object.
+   *
+   * @param \Drupal\workflow\Entity\WorkflowTransition $transition
+   *   The transition.
+   * @param \Drupal\workflow\Entity\WorkflowInterface $workflow
+   *   The workflow.
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   */
+  public function __construct(WorkflowTransition $transition, WorkflowInterface $workflow, EntityInterface $entity) {
+    $this->transition = $transition;
+    $this->workflow = $workflow;
+    $this->entity = $entity;
+  }
+
+  /**
+   * Gets the transition.
+   *
+   * @return \Drupal\workflow\Entity\WorkflowTransition
+   *   The transition.
+   */
+  public function getTransition() {
+    return $this->transition;
+  }
+
+  /**
+   * Gets the workflow.
+   *
+   * @return \Drupal\workflow\Entity\WorkflowInterface
+   *   The workflow.
+   */
+  public function getWorkflow() {
+    return $this->workflow;
+  }
+
+  /**
+   * Gets the entity.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   The entity.
+   */
+  public function getEntity() {
+    return $this->entity;
+  }
+
+  /**
+   * Gets the state field name.
+   *
+   * @return string
+   *   The state field name.
+   */
+  /*public function getFieldName() {
+    return $this->fieldName;
+  }*/
+
+  /**
+   * Gets the state field.
+   *
+   * @return \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface
+   *   The state field.
+   */
+  /* public function getField() {
+     /** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface $field * /
+     $field = $this->entity->get($this->fieldName)->first();
+     return $field;
+   }
+  */
+  /**
+   * Gets the "from" state.
+   *
+   * @return \Drupal\workflow\Entity\WorkflowState
+   *   The "from" state.
+   *
+   */
+  public function getFromState() {
+    return $this->transition->getFromState();
+  }
+
+  /**
+   * Gets the "to" state.
+   *
+   * @return \Drupal\workflow\Entity\WorkflowState
+   *   The "to" state.
+   *
+   */
+  public function getToState() {
+    return $this->transition->getToState();
+  }
+
+}
