diff --git a/modules/salesforce_mapping/src/Entity/MappedObject.php b/modules/salesforce_mapping/src/Entity/MappedObject.php
index ffbddc9bfb0ad51fbcd16e954e34d40367ae9422..271c1e6ca26b87baaec1c519cfd1e178bb995efa 100644
--- a/modules/salesforce_mapping/src/Entity/MappedObject.php
+++ b/modules/salesforce_mapping/src/Entity/MappedObject.php
@@ -23,6 +23,7 @@ use Drupal\salesforce_mapping\Event\SalesforcePushParamsEvent;
 use Drupal\salesforce_mapping\MappingConstants;
 use Drupal\salesforce_mapping\Plugin\Field\FieldType\SalesforceLinkItemList;
 use Drupal\salesforce_mapping\PushParams;
+use Drupal\user\Entity\User;
 
 /**
  * Defines a Salesforce Mapped Object entity class.
@@ -40,6 +41,9 @@ use Drupal\salesforce_mapping\PushParams;
  *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
  *     "views_data" = "Drupal\views\EntityViewsData",
  *     "access" = "Drupal\salesforce_mapping\MappedObjectAccessControlHandler",
+ *     "route_provider" = {
+ *       "revision" = \Drupal\Core\Entity\Routing\RevisionHtmlRouteProvider::class,
+ *     },
  *   },
  *   base_table = "salesforce_mapped_object",
  *   revision_table = "salesforce_mapped_object_revision",
@@ -98,24 +102,6 @@ class MappedObject extends RevisionableContentEntityBase implements MappedObject
     parent::__construct($values, 'salesforce_mapped_object');
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
-    // Revision uid, timestamp, and message are required for D9.
-    if ($this->isNewRevision()) {
-      if (empty($this->getRevisionUserId())) {
-        $this->setRevisionUserId(1);
-      }
-      if (empty($this->getRevisionCreationTime())) {
-        $this->setRevisionCreationTime(time());
-      }
-      if (empty($this->getRevisionLogMessage())) {
-        $this->setRevisionLogMessage('New revision');
-      }
-    }
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -124,6 +110,11 @@ class MappedObject extends RevisionableContentEntityBase implements MappedObject
     if ($this->isNew()) {
       $this->created = $this->getRequestTime();
     }
+    if ($this->isNewRevision()) {
+      $this
+        ->setRevisionCreationTime($this->getRequestTime())
+        ->setRevisionUserId($this->getCurrentUserId());
+    }
     return parent::save();
   }
 
@@ -467,6 +458,7 @@ class MappedObject extends RevisionableContentEntityBase implements MappedObject
     $this
       ->set('last_sync_action', 'push_delete')
       ->set('last_sync_status', TRUE)
+      ->setRevisionLogMessage('')
       ->save();
     return $this;
   }
@@ -593,18 +585,59 @@ class MappedObject extends RevisionableContentEntityBase implements MappedObject
     $drupal_entity->salesforce_pull = TRUE;
     $drupal_entity->save();
 
+    $this->setNewRevision(TRUE);
+
     // Update mapping object.
     $this
       ->set('drupal_entity', $drupal_entity)
       ->set('entity_updated', $this->getRequestTime())
-      ->set('last_sync_action', 'pull')
+      ->set('last_sync_action', $this->isNew() ? 'pull_create' : 'pull_update')
       ->set('last_sync_status', TRUE)
       ->set('force_pull', 0)
+      ->setRevisionLogMessage('')
       ->save();
 
     return $this;
   }
 
+  /**
+   * {@inheritDoc}
+   */
+  public function getRevisionLogMessage() {
+    return parent::getRevisionLogMessage() ?: $this->last_sync_action->value;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function getRevisionUserId() {
+    return parent::getRevisionUserId() ?: 1;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function getRevisionUser() {
+    return parent::getRevisionuser() ?: User::load(1);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function getRevisionCreationTime() {
+    return parent::getRevisionCreationTime() ?: $this->entity_updated->value;
+  }
+
+  /**
+   * Testable func to return current user id.
+   *
+   * @return int
+   *   The current user id.
+   */
+  protected function getCurrentUserId() {
+    return \Drupal::currentUser()->id();
+  }
+
   /**
    * Testable func to return the request time server variable.
    *
diff --git a/modules/salesforce_mapping_ui/salesforce_mapping_ui.module b/modules/salesforce_mapping_ui/salesforce_mapping_ui.module
index 0e6da92ccc8d7b68de6b428725e262ac0a02d336..a9d9c8b4d1990c8343c77e1b80cb97d13b21b296 100644
--- a/modules/salesforce_mapping_ui/salesforce_mapping_ui.module
+++ b/modules/salesforce_mapping_ui/salesforce_mapping_ui.module
@@ -58,6 +58,7 @@ function salesforce_mapping_ui_entity_type_alter(array &$entity_types) {
   $entity_types['salesforce_mapped_object']->setLinkTemplate('edit-form', '/admin/content/salesforce/{salesforce_mapped_object}/edit');
   $entity_types['salesforce_mapped_object']->setLinkTemplate('delete-form', '/admin/content/salesforce/{salesforce_mapped_object}/delete');
   $entity_types['salesforce_mapped_object']->setLinkTemplate('canonical', '/admin/content/salesforce/{salesforce_mapped_object}');
+  $entity_types['salesforce_mapped_object']->setLinkTemplate('version-history', '/admin/content/salesforce/{salesforce_mapped_object}/revisions');
 }
 
 /**
diff --git a/modules/salesforce_mapping_ui/src/Controller/MappedObjectController.php b/modules/salesforce_mapping_ui/src/Controller/MappedObjectController.php
index 1baa1ab9b0e9a5b4447e3494aaecae3552644341..e7466872b192743a4f9f8aa24a3e4dd775e8c155 100644
--- a/modules/salesforce_mapping_ui/src/Controller/MappedObjectController.php
+++ b/modules/salesforce_mapping_ui/src/Controller/MappedObjectController.php
@@ -106,9 +106,13 @@ class MappedObjectController extends ControllerBase {
       ];
     }
 
-    // Show the entity view for the mapped object.
-    $builder = $this->entityTypeManager()->getListBuilder('salesforce_mapped_object');
-    return $builder->setEntityIds(array_keys($salesforce_mapped_objects))->render();
+    // Show one table of revisions per mapped object.
+    foreach ($salesforce_mapped_objects as $mapped_object) {
+      $builder = $this->entityTypeManager()
+        ->getListBuilder('salesforce_mapped_object');
+      return $builder->setEntityIds(array_keys($salesforce_mapped_objects))
+        ->render();
+    }
   }
 
 }
diff --git a/modules/salesforce_pull/src/Plugin/QueueWorker/PullBase.php b/modules/salesforce_pull/src/Plugin/QueueWorker/PullBase.php
index 4fc36225d68219fdbe53e82885110769bba33be8..7389ce70a7b4515f4bfa279e3e7380f4b6d5f37f 100644
--- a/modules/salesforce_pull/src/Plugin/QueueWorker/PullBase.php
+++ b/modules/salesforce_pull/src/Plugin/QueueWorker/PullBase.php
@@ -224,6 +224,15 @@ abstract class PullBase extends QueueWorkerBase implements ContainerFactoryPlugi
     }
     catch (\Exception $e) {
       $this->eventDispatcher->dispatch(new SalesforceErrorEvent($e, 'Failed to update entity %label from Salesforce object %sfobjectid.', ['%label' => (isset($entity)) ? $entity->label() : "Unknown", '%sfobjectid' => (string) $sf_object->id()]), SalesforceEvents::WARNING);
+
+      $mapped_object->setNewRevision(TRUE);
+      // Update mapped object.
+      $mapped_object
+        ->set('last_sync_action', 'pull_update')
+        ->set('last_sync_status', FALSE)
+        ->setRevisionLogMessage($e->getMessage())
+        ->save();
+
       // Throwing a new exception keeps current item in cron queue.
       throw $e;
     }
diff --git a/modules/salesforce_push/salesforce_push.module b/modules/salesforce_push/salesforce_push.module
index 95185487faa0820cb78f70899c6cf7fe9bd9ed56..9ec68d18ebf79b123887d3bb0338026bafdc1850 100644
--- a/modules/salesforce_push/salesforce_push.module
+++ b/modules/salesforce_push/salesforce_push.module
@@ -194,6 +194,7 @@ function salesforce_push_entity_crud_mapping(EntityInterface $entity, $op, Sales
 
     if (!$mapped_object->isNew()) {
       // Only update existing mapped objects.
+      $mapped_object->setNewRevision(TRUE);
       $mapped_object
         ->set('last_sync_action', $op)
         ->set('last_sync_status', FALSE)