diff --git a/core/modules/workspaces/src/EntityQuery/QueryTrait.php b/core/modules/workspaces/src/EntityQuery/QueryTrait.php
index 415ef4b7a08633e04ba32d516a735fb2ba65edb5..eef973cc45639c56507af443be2586153b0014a4 100644
--- a/core/modules/workspaces/src/EntityQuery/QueryTrait.php
+++ b/core/modules/workspaces/src/EntityQuery/QueryTrait.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\workspaces\WorkspaceAssociation;
 use Drupal\workspaces\WorkspaceInformationInterface;
 use Drupal\workspaces\WorkspaceManagerInterface;
 
@@ -76,7 +77,8 @@ public function prepare() {
       // can properly include live content along with a possible workspace
       // revision.
       $id_field = $this->entityType->getKey('id');
-      $this->sqlQuery->leftJoin('workspace_association', 'workspace_association', "[%alias].[target_entity_type_id] = '{$this->entityTypeId}' AND [%alias].[target_entity_id] = [base_table].[$id_field] AND [%alias].[workspace] = '{$active_workspace->id()}'");
+      $target_id_field = WorkspaceAssociation::getIdField($this->entityTypeId);
+      $this->sqlQuery->leftJoin('workspace_association', 'workspace_association', "[%alias].[target_entity_type_id] = '{$this->entityTypeId}' AND [%alias].[$target_id_field] = [base_table].[$id_field] AND [%alias].[workspace] = '{$active_workspace->id()}'");
     }
 
     return $this;
diff --git a/core/modules/workspaces/src/EntityQuery/Tables.php b/core/modules/workspaces/src/EntityQuery/Tables.php
index e67e107bfbb04390855653a093112c4af32c8a81..199d5cc1559729921f1263198464db62162cf1f6 100644
--- a/core/modules/workspaces/src/EntityQuery/Tables.php
+++ b/core/modules/workspaces/src/EntityQuery/Tables.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Entity\Query\Sql\Tables as BaseTables;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\workspaces\WorkspaceAssociation;
 
 /**
  * Alters entity queries to use a workspace revision instead of the default one.
@@ -144,10 +145,11 @@ public function addWorkspaceAssociationJoin($entity_type_id, $base_table_alias,
     if (!isset($this->contentWorkspaceTables[$base_table_alias])) {
       $entity_type = $this->entityTypeManager->getActiveDefinition($entity_type_id);
       $id_field = $entity_type->getKey('id');
+      $target_id_field = WorkspaceAssociation::getIdField($entity_type_id);
 
       // LEFT join the Workspace association entity's table so we can properly
       // include live content along with a possible workspace-specific revision.
-      $this->contentWorkspaceTables[$base_table_alias] = $this->sqlQuery->leftJoin('workspace_association', NULL, "[%alias].[target_entity_type_id] = '$entity_type_id' AND [%alias].[target_entity_id] = [$base_table_alias].[$id_field] AND [%alias].[workspace] = '$active_workspace_id'");
+      $this->contentWorkspaceTables[$base_table_alias] = $this->sqlQuery->leftJoin('workspace_association', NULL, "[%alias].[target_entity_type_id] = '$entity_type_id' AND [%alias].[$target_id_field] = [$base_table_alias].[$id_field] AND [%alias].[workspace] = '$active_workspace_id'");
 
       $this->baseTablesEntityType[$base_table_alias] = $entity_type->id();
     }
diff --git a/core/modules/workspaces/src/ViewsQueryAlter.php b/core/modules/workspaces/src/ViewsQueryAlter.php
index 4cfb5d78dad3e6582a811d5991351c72995f862f..f409a20b0d9c5fc52793bda381dc25b9d2e16501 100644
--- a/core/modules/workspaces/src/ViewsQueryAlter.php
+++ b/core/modules/workspaces/src/ViewsQueryAlter.php
@@ -311,7 +311,7 @@ protected function ensureWorkspaceAssociationTable($entity_type_id, Sql $query,
     // Construct the join.
     $definition = [
       'table' => 'workspace_association',
-      'field' => 'target_entity_id',
+      'field' => WorkspaceAssociation::getIdField($entity_type_id),
       'left_table' => $relationship,
       'left_field' => $table_data['table']['base']['field'],
       'extra' => [
diff --git a/core/modules/workspaces/src/WorkspaceAssociation.php b/core/modules/workspaces/src/WorkspaceAssociation.php
index 8a93a83e34c5380f44ee5faa96d1994dacfb7bcf..25b476fab864e4c0ef65a82be426c613fb08bcf3 100644
--- a/core/modules/workspaces/src/WorkspaceAssociation.php
+++ b/core/modules/workspaces/src/WorkspaceAssociation.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\workspaces;
 
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\Query\PagerSelectExtender;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -60,6 +61,7 @@ public function trackEntity(RevisionableInterface $entity, WorkspaceInterface $w
     if (isset($tracked[$entity->getEntityTypeId()])) {
       $tracked_revision_id = key($tracked[$entity->getEntityTypeId()]);
     }
+    $id_field = static::getIdField($entity->getEntityTypeId());
 
     try {
       $transaction = $this->database->startTransaction();
@@ -72,7 +74,7 @@ public function trackEntity(RevisionableInterface $entity, WorkspaceInterface $w
           ])
           ->condition('workspace', $affected_workspaces, 'IN')
           ->condition('target_entity_type_id', $entity->getEntityTypeId())
-          ->condition('target_entity_id', $entity->id())
+          ->condition($id_field, $entity->id())
           // Only update descendant workspaces if they have the same initial
           // revision, which means they are currently inheriting content.
           ->condition('target_entity_revision_id', $tracked_revision_id)
@@ -86,15 +88,15 @@ public function trackEntity(RevisionableInterface $entity, WorkspaceInterface $w
         $insert_query = $this->database->insert(static::TABLE)
           ->fields([
             'workspace',
-            'target_entity_revision_id',
             'target_entity_type_id',
-            'target_entity_id',
+            $id_field,
+            'target_entity_revision_id',
           ]);
         foreach ($missing_workspaces as $workspace_id) {
           $insert_query->values([
             'workspace' => $workspace_id,
             'target_entity_type_id' => $entity->getEntityTypeId(),
-            'target_entity_id' => $entity->id(),
+            $id_field => $entity->id(),
             'target_entity_revision_id' => $entity->getRevisionId(),
           ]);
         }
@@ -128,8 +130,13 @@ public function workspaceInsert(WorkspaceInterface $workspace) {
    */
   public function getTrackedEntities($workspace_id, $entity_type_id = NULL, $entity_ids = NULL) {
     $query = $this->database->select(static::TABLE);
+    $query->fields(static::TABLE, [
+      'target_entity_type_id',
+      'target_entity_id',
+      'target_entity_id_string',
+      'target_entity_revision_id',
+    ]);
     $query
-      ->fields(static::TABLE, ['target_entity_type_id', 'target_entity_id', 'target_entity_revision_id'])
       ->orderBy('target_entity_revision_id', 'ASC')
       ->condition('workspace', $workspace_id);
 
@@ -137,13 +144,14 @@ public function getTrackedEntities($workspace_id, $entity_type_id = NULL, $entit
       $query->condition('target_entity_type_id', $entity_type_id, '=');
 
       if ($entity_ids) {
-        $query->condition('target_entity_id', $entity_ids, 'IN');
+        $query->condition(static::getIdField($entity_type_id), $entity_ids, 'IN');
       }
     }
 
     $tracked_revisions = [];
     foreach ($query->execute() as $record) {
-      $tracked_revisions[$record->target_entity_type_id][$record->target_entity_revision_id] = $record->target_entity_id;
+      $target_id = $record->{static::getIdField($record->target_entity_type_id)};
+      $tracked_revisions[$record->target_entity_type_id][$record->target_entity_revision_id] = $target_id;
     }
 
     return $tracked_revisions;
@@ -160,15 +168,21 @@ public function getTrackedEntitiesForListing($workspace_id, ?int $pager_id = NUL
       $query->element($pager_id);
     }
 
+    $query->fields(static::TABLE, [
+      'target_entity_type_id',
+      'target_entity_id',
+      'target_entity_id_string',
+      'target_entity_revision_id',
+    ]);
     $query
-      ->fields(static::TABLE, ['target_entity_type_id', 'target_entity_id', 'target_entity_revision_id'])
       ->orderBy('target_entity_type_id', 'ASC')
       ->orderBy('target_entity_revision_id', 'DESC')
       ->condition('workspace', $workspace_id);
 
     $tracked_revisions = [];
     foreach ($query->execute() as $record) {
-      $tracked_revisions[$record->target_entity_type_id][$record->target_entity_revision_id] = $record->target_entity_id;
+      $target_id = $record->{static::getIdField($record->target_entity_type_id)};
+      $tracked_revisions[$record->target_entity_type_id][$record->target_entity_revision_id] = $target_id;
     }
 
     return $tracked_revisions;
@@ -291,17 +305,18 @@ public function getAssociatedInitialRevisions(string $workspace_id, string $enti
    * {@inheritdoc}
    */
   public function getEntityTrackingWorkspaceIds(RevisionableInterface $entity, bool $latest_revision = FALSE) {
+    $id_field = static::getIdField($entity->getEntityTypeId());
     $query = $this->database->select(static::TABLE, 'wa')
       ->fields('wa', ['workspace'])
       ->condition('[wa].[target_entity_type_id]', $entity->getEntityTypeId())
-      ->condition('[wa].[target_entity_id]', $entity->id());
+      ->condition("[wa].[$id_field]", $entity->id());
 
     // Use a self-join to get only the workspaces in which the latest revision
     // of the entity is tracked.
     if ($latest_revision) {
       $inner_select = $this->database->select(static::TABLE, 'wai')
         ->condition('[wai].[target_entity_type_id]', $entity->getEntityTypeId())
-        ->condition('[wai].[target_entity_id]', $entity->id());
+        ->condition("[wai].[$id_field]", $entity->id());
       $inner_select->addExpression('MAX([wai].[target_entity_revision_id])', 'max_revision_id');
 
       $query->join($inner_select, 'waj', '[wa].[target_entity_revision_id] = [waj].[max_revision_id]');
@@ -341,7 +356,18 @@ public function deleteAssociations($workspace_id = NULL, $entity_type_id = NULL,
       $query->condition('target_entity_type_id', $entity_type_id, '=');
 
       if ($entity_ids) {
-        $query->condition('target_entity_id', $entity_ids, 'IN');
+        try {
+          $query->condition(static::getIdField($entity_type_id), $entity_ids, 'IN');
+        }
+        catch (PluginNotFoundException) {
+          // When an entity type is being deleted, we no longer have the ability
+          // to retrieve its identifier field type, so we try both.
+          $query->condition(
+            $query->orConditionGroup()
+              ->condition('target_entity_id', $entity_ids, 'IN')
+              ->condition('target_entity_id_string', $entity_ids, 'IN')
+          );
+        }
       }
 
       if ($revision_ids) {
@@ -366,6 +392,7 @@ public function initializeWorkspace(WorkspaceInterface $workspace) {
       $indexed_rows->fields(static::TABLE, [
         'target_entity_type_id',
         'target_entity_id',
+        'target_entity_id_string',
         'target_entity_revision_id',
       ]);
       $indexed_rows->condition('workspace', $parent_id);
@@ -399,4 +426,31 @@ public function onPostPublish(WorkspacePublishEvent $event): void {
     }
   }
 
+  /**
+   * Determines the target ID field name for an entity type.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return string
+   *   The name of the workspace association target ID field.
+   *
+   * @internal
+   */
+  public static function getIdField(string $entity_type_id): string {
+    static $id_field_map = [];
+
+    if (!isset($id_field_map[$entity_type_id])) {
+      $id_field = \Drupal::entityTypeManager()->getDefinition($entity_type_id)
+        ->getKey('id');
+      $field_map = \Drupal::service('entity_field.manager')->getFieldMap()[$entity_type_id];
+
+      $id_field_map[$entity_type_id] = $field_map[$id_field]['type'] !== 'integer'
+        ? 'target_entity_id_string'
+        : 'target_entity_id';
+    }
+
+    return $id_field_map[$entity_type_id];
+  }
+
 }
diff --git a/core/modules/workspaces/tests/fixtures/update/workspaces.php b/core/modules/workspaces/tests/fixtures/update/workspaces.php
new file mode 100644
index 0000000000000000000000000000000000000000..6928ddb66ced4356046fb9966121ee780fbf02dd
--- /dev/null
+++ b/core/modules/workspaces/tests/fixtures/update/workspaces.php
@@ -0,0 +1,90 @@
+<?php
+// phpcs:ignoreFile
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Entity\EntityTypeInterface;
+
+$connection = Database::getConnection();
+
+// Set the schema version.
+$connection->merge('key_value')
+  ->fields([
+    'value' => 'i:10000;',
+    'name' => 'workspaces',
+    'collection' => 'system.schema',
+  ])
+  ->condition('collection', 'system.schema')
+  ->condition('name', 'workspaces')
+  ->execute();
+
+// Update core.extension.
+$extensions = $connection->select('config')
+  ->fields('config', ['data'])
+  ->condition('collection', '')
+  ->condition('name', 'core.extension')
+  ->execute()
+  ->fetchField();
+$extensions = unserialize($extensions);
+$extensions['module']['workspaces'] = 0;
+$connection->update('config')
+  ->fields(['data' => serialize($extensions)])
+  ->condition('collection', '')
+  ->condition('name', 'core.extension')
+  ->execute();
+
+// Add all workspaces_removed_post_updates() as existing updates.
+require_once __DIR__ . '/../../../../workspaces/workspaces.post_update.php';
+$existing_updates = $connection->select('key_value')
+  ->fields('key_value', ['value'])
+  ->condition('collection', 'post_update')
+  ->condition('name', 'existing_updates')
+  ->execute()
+  ->fetchField();
+$existing_updates = unserialize($existing_updates);
+$existing_updates = array_merge(
+  $existing_updates,
+  array_keys(workspaces_removed_post_updates())
+);
+$connection->update('key_value')
+  ->fields(['value' => serialize($existing_updates)])
+  ->condition('collection', 'post_update')
+  ->condition('name', 'existing_updates')
+  ->execute();
+
+// Create the 'workspace_association' table.
+$spec = [
+  'description' => 'Stores the association between entity revisions and their workspace.',
+  'fields' => [
+    'workspace' => [
+      'type' => 'varchar_ascii',
+      'length' => 128,
+      'not null' => TRUE,
+      'default' => '',
+      'description' => 'The workspace ID.',
+    ],
+    'target_entity_type_id' => [
+      'type' => 'varchar_ascii',
+      'length' => EntityTypeInterface::ID_MAX_LENGTH,
+      'not null' => TRUE,
+      'default' => '',
+      'description' => 'The ID of the associated entity type.',
+    ],
+    'target_entity_id' => [
+      'type' => 'int',
+      'unsigned' => TRUE,
+      'not null' => TRUE,
+      'description' => 'The ID of the associated entity.',
+    ],
+    'target_entity_revision_id' => [
+      'type' => 'int',
+      'unsigned' => TRUE,
+      'not null' => TRUE,
+      'description' => 'The revision ID of the associated entity.',
+    ],
+  ],
+  'indexes' => [
+    'target_entity_revision_id' => ['target_entity_revision_id'],
+  ],
+  'primary key' => ['workspace', 'target_entity_type_id', 'target_entity_id'],
+];
+$connection->schema()->createTable('workspace_association', $spec);
diff --git a/core/modules/workspaces/tests/modules/workspaces_test/src/Entity/EntityTestMulRevPubStringId.php b/core/modules/workspaces/tests/modules/workspaces_test/src/Entity/EntityTestMulRevPubStringId.php
new file mode 100644
index 0000000000000000000000000000000000000000..f782cfee86622ff1b1d215be7fe3bdd7376ec6d3
--- /dev/null
+++ b/core/modules/workspaces/tests/modules/workspaces_test/src/Entity/EntityTestMulRevPubStringId.php
@@ -0,0 +1,73 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\workspaces_test\Entity;
+
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\entity_test\Entity\EntityTestMulRevPub;
+
+/**
+ * Defines the test entity class.
+ *
+ * @ContentEntityType(
+ *   id = "entity_test_mulrevpub_string_id",
+ *   label = @Translation("Test entity - revisions, data table, and published interface"),
+ *   handlers = {
+ *     "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
+ *     "access" = "Drupal\entity_test\EntityTestAccessControlHandler",
+ *     "form" = {
+ *       "default" = "Drupal\entity_test\EntityTestForm",
+ *       "delete" = "Drupal\entity_test\EntityTestDeleteForm",
+ *       "delete-multiple-confirm" = "Drupal\Core\Entity\Form\DeleteMultipleForm"
+ *     },
+ *     "views_data" = "Drupal\views\EntityViewsData",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
+ *   },
+ *   base_table = "entity_test_mulrevpub_string_id",
+ *   data_table = "entity_test_mulrevpub_string_id_property_data",
+ *   revision_table = "entity_test_mulrevpub_string_id_revision",
+ *   revision_data_table = "entity_test_mulrevpub_string_id_property_revision",
+ *   admin_permission = "administer entity_test content",
+ *   translatable = TRUE,
+ *   show_revision_ui = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid",
+ *     "bundle" = "type",
+ *     "revision" = "revision_id",
+ *     "label" = "name",
+ *     "langcode" = "langcode",
+ *     "published" = "status",
+ *   },
+ *   links = {
+ *     "add-form" = "/entity_test_mulrevpub/add",
+ *     "canonical" = "/entity_test_mulrevpub/manage/{entity_test_mulrevpub}",
+ *     "delete-form" = "/entity_test/delete/entity_test_mulrevpub/{entity_test_mulrevpub}",
+ *     "delete-multiple-form" = "/entity_test/delete",
+ *     "edit-form" = "/entity_test_mulrevpub/manage/{entity_test_mulrevpub}/edit",
+ *     "revision" = "/entity_test_mulrevpub/{entity_test_mulrevpub}/revision/{entity_test_mulrevpub_revision}/view",
+ *   }
+ * )
+ */
+class EntityTestMulRevPubStringId extends EntityTestMulRevPub {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields = parent::baseFieldDefinitions($entity_type);
+    $fields['id'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('ID'))
+      ->setDescription(t('The ID of the test entity.'))
+      ->setReadOnly(TRUE)
+      // In order to work around the InnoDB 191 character limit on utf8mb4
+      // primary keys, we set the character set for the field to ASCII.
+      ->setSetting('is_ascii', TRUE);
+    return $fields;
+  }
+
+}
diff --git a/core/modules/workspaces/tests/modules/workspaces_test/workspaces_test.info.yml b/core/modules/workspaces/tests/modules/workspaces_test/workspaces_test.info.yml
index 62886a0b12d219768250c0ad9c3f212734061789..532cbad02f45cdd33e73e272b4f56846e12d28ab 100644
--- a/core/modules/workspaces/tests/modules/workspaces_test/workspaces_test.info.yml
+++ b/core/modules/workspaces/tests/modules/workspaces_test/workspaces_test.info.yml
@@ -4,4 +4,5 @@ description: 'Provides supporting code for testing workspaces.'
 package: Testing
 version: VERSION
 dependencies:
+  - drupal:entity_test
   - drupal:workspaces
diff --git a/core/modules/workspaces/tests/src/Functional/Update/WorkspaceAssociationStringIdsUpdatePathTest.php b/core/modules/workspaces/tests/src/Functional/Update/WorkspaceAssociationStringIdsUpdatePathTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d36a8b3aa15c828f1f6139772f320a2830cad633
--- /dev/null
+++ b/core/modules/workspaces/tests/src/Functional/Update/WorkspaceAssociationStringIdsUpdatePathTest.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\workspaces\Functional\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+
+/**
+ * Tests the update path for string IDs in workspace_association.
+ *
+ * @group workspaces
+ */
+class WorkspaceAssociationStringIdsUpdatePathTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $checkEntityFieldDefinitionUpdates = FALSE;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles(): void {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-10.3.0.bare.standard.php.gz',
+      __DIR__ . '/../../../fixtures/update/workspaces.php',
+    ];
+  }
+
+  /**
+   * Tests the update path for string IDs in workspace_association.
+   */
+  public function testRunUpdates(): void {
+    $schema = \Drupal::database()->schema();
+    $find_primary_key_columns = new \ReflectionMethod(get_class($schema), 'findPrimaryKeyColumns');
+
+    $this->assertFalse($schema->fieldExists('workspace_association', 'target_entity_id_string'));
+    $primary_key_columns = ['workspace', 'target_entity_type_id', 'target_entity_id'];
+    $this->assertEquals($primary_key_columns, $find_primary_key_columns->invoke($schema, 'workspace_association'));
+
+    $this->runUpdates();
+
+    $this->assertTrue($schema->fieldExists('workspace_association', 'target_entity_id_string'));
+    $primary_key_columns = ['workspace', 'target_entity_type_id', 'target_entity_id', 'target_entity_id_string'];
+    $this->assertEquals($primary_key_columns, $find_primary_key_columns->invoke($schema, 'workspace_association'));
+  }
+
+}
diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceAssociationTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceAssociationTest.php
index 183d5247bfc5ab7df04fd014d7685f00d58e4ed4..7cb8f5515a4e1c19ebd7ffc830dc85e65943664c 100644
--- a/core/modules/workspaces/tests/src/Kernel/WorkspaceAssociationTest.php
+++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceAssociationTest.php
@@ -5,8 +5,6 @@
 namespace Drupal\Tests\workspaces\Kernel;
 
 use Drupal\KernelTests\KernelTestBase;
-use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
-use Drupal\Tests\node\Traits\NodeCreationTrait;
 use Drupal\Tests\user\Traits\UserCreationTrait;
 use Drupal\workspaces\Entity\Workspace;
 
@@ -19,8 +17,6 @@
  */
 class WorkspaceAssociationTest extends KernelTestBase {
 
-  use ContentTypeCreationTrait;
-  use NodeCreationTrait;
   use UserCreationTrait;
   use WorkspaceTestTrait;
 
@@ -35,13 +31,11 @@ class WorkspaceAssociationTest extends KernelTestBase {
    * {@inheritdoc}
    */
   protected static $modules = [
-    'field',
-    'filter',
-    'node',
-    'text',
+    'entity_test',
     'user',
     'system',
     'workspaces',
+    'workspaces_test',
   ];
 
   /**
@@ -52,17 +46,15 @@ protected function setUp(): void {
 
     $this->entityTypeManager = \Drupal::entityTypeManager();
 
-    $this->installEntitySchema('node');
+    $this->installEntitySchema('entity_test_mulrevpub');
+    $this->installEntitySchema('entity_test_mulrevpub_string_id');
     $this->installEntitySchema('user');
     $this->installEntitySchema('workspace');
 
-    $this->installConfig(['filter', 'node', 'system']);
+    $this->installConfig(['system']);
 
-    $this->installSchema('node', ['node_access']);
     $this->installSchema('workspaces', ['workspace_association']);
 
-    $this->createContentType(['type' => 'article']);
-
     $permissions = array_intersect([
       'administer nodes',
       'create workspace',
@@ -80,27 +72,33 @@ protected function setUp(): void {
   /**
    * Tests the revisions tracked by a workspace.
    *
+   * @param string $entity_type_id
+   *   The ID of the entity type to test.
+   * @param array $entity_values
+   *   An array of values for the entities created in this test.
+   *
    * @covers ::getTrackedEntities
    * @covers ::getAssociatedRevisions
+   *
+   * @dataProvider getEntityTypeIds
    */
-  public function testWorkspaceAssociation(): void {
-    $this->createNode(['title' => 'Test article 1 - live - unpublished', 'type' => 'article', 'status' => 0]);
-    $this->createNode(['title' => 'Test article 2 - live - published', 'type' => 'article']);
+  public function testWorkspaceAssociation(string $entity_type_id, array $entity_values): void {
+    $entity_1 = $this->createEntity($entity_type_id, $entity_values[1]);
+    $this->createEntity($entity_type_id, $entity_values[2]);
 
     // Edit one of the existing nodes in 'stage'.
     $this->switchToWorkspace('stage');
-    $node = $this->entityTypeManager->getStorage('node')->load(1);
-    $node->setTitle('Test article 1 - stage - published');
-    $node->setPublished();
+    $entity_1->set('name', 'Test entity 1 - stage - published');
+    $entity_1->setPublished();
     // This creates rev. 3.
-    $node->save();
+    $entity_1->save();
 
     // Generate content with the following structure:
     // Stage:
-    // - Test article 3 - stage - unpublished (rev. 4)
-    // - Test article 4 - stage - published (rev. 5 and 6)
-    $this->createNode(['title' => 'Test article 3 - stage - unpublished', 'type' => 'article', 'status' => 0]);
-    $this->createNode(['title' => 'Test article 4 - stage - published', 'type' => 'article']);
+    // - Test entity 3 - stage - unpublished (rev. 4)
+    // - Test entity 4 - stage - published (rev. 5 and 6)
+    $this->createEntity($entity_type_id, $entity_values[3]);
+    $this->createEntity($entity_type_id, $entity_values[4]);
 
     $expected_latest_revisions = [
       'stage' => [3, 4, 6],
@@ -111,17 +109,17 @@ public function testWorkspaceAssociation(): void {
     $expected_initial_revisions = [
       'stage' => [4, 5],
     ];
-    $this->assertWorkspaceAssociations('node', $expected_latest_revisions, $expected_all_revisions, $expected_initial_revisions);
+    $this->assertWorkspaceAssociations($entity_type_id, $expected_latest_revisions, $expected_all_revisions, $expected_initial_revisions);
 
     // Dev:
-    // - Test article 1 - stage - published (rev. 3)
-    // - Test article 3 - stage - unpublished (rev. 4)
-    // - Test article 4 - stage - published (rev. 5 and 6)
-    // - Test article 5 - dev - unpublished (rev. 7)
-    // - Test article 6 - dev - published (rev. 8 and 9)
+    // - Test entity 1 - stage - published (rev. 3)
+    // - Test entity 3 - stage - unpublished (rev. 4)
+    // - Test entity 4 - stage - published (rev. 5 and 6)
+    // - Test entity 5 - dev - unpublished (rev. 7)
+    // - Test entity 6 - dev - published (rev. 8 and 9)
     $this->switchToWorkspace('dev');
-    $this->createNode(['title' => 'Test article 5 - dev - unpublished', 'type' => 'article', 'status' => 0]);
-    $this->createNode(['title' => 'Test article 6 - dev - published', 'type' => 'article']);
+    $this->createEntity($entity_type_id, $entity_values[5]);
+    $this->createEntity($entity_type_id, $entity_values[6]);
 
     $expected_latest_revisions += [
       'dev' => [3, 4, 6, 7, 9],
@@ -134,7 +132,7 @@ public function testWorkspaceAssociation(): void {
     $expected_initial_revisions += [
       'dev' => [7, 8],
     ];
-    $this->assertWorkspaceAssociations('node', $expected_latest_revisions, $expected_all_revisions, $expected_initial_revisions);
+    $this->assertWorkspaceAssociations($entity_type_id, $expected_latest_revisions, $expected_all_revisions, $expected_initial_revisions);
 
     // Merge 'dev' into 'stage' and check the workspace associations.
     /** @var \Drupal\workspaces\WorkspaceMergerInterface $workspace_merger */
@@ -155,7 +153,7 @@ public function testWorkspaceAssociation(): void {
     // Which leaves revision 8 as the only remaining initial revision in 'dev'.
     $expected_initial_revisions['dev'] = [8];
 
-    $this->assertWorkspaceAssociations('node', $expected_latest_revisions, $expected_all_revisions, $expected_initial_revisions);
+    $this->assertWorkspaceAssociations($entity_type_id, $expected_latest_revisions, $expected_all_revisions, $expected_initial_revisions);
 
     // Publish 'stage' and check the workspace associations.
     /** @var \Drupal\workspaces\WorkspacePublisherInterface $workspace_publisher */
@@ -163,7 +161,37 @@ public function testWorkspaceAssociation(): void {
     $workspace_publisher->publish();
 
     $expected_revisions['stage'] = $expected_revisions['dev'] = [];
-    $this->assertWorkspaceAssociations('node', $expected_revisions, $expected_revisions, $expected_revisions);
+    $this->assertWorkspaceAssociations($entity_type_id, $expected_revisions, $expected_revisions, $expected_revisions);
+  }
+
+  /**
+   * The data provider for ::testWorkspaceAssociation().
+   */
+  public static function getEntityTypeIds(): array {
+    return [
+      [
+        'entity_type_id' => 'entity_test_mulrevpub',
+        'entity_values' => [
+          1 => ['name' => 'Test entity 1 - live - unpublished', 'status' => FALSE],
+          2 => ['name' => 'Test entity 2 - live - published', 'status' => TRUE],
+          3 => ['name' => 'Test entity 3 - stage - unpublished', 'status' => FALSE],
+          4 => ['name' => 'Test entity 4 - stage - published', 'status' => TRUE],
+          5 => ['name' => 'Test entity 5 - dev - unpublished', 'status' => FALSE],
+          6 => ['name' => 'Test entity 6 - dev - published', 'status' => TRUE],
+        ],
+      ],
+      [
+        'entity_type_id' => 'entity_test_mulrevpub_string_id',
+        'entity_values' => [
+          1 => ['id' => 'test_1', 'name' => 'Test entity 1 - live - unpublished', 'status' => FALSE],
+          2 => ['id' => 'test_2', 'name' => 'Test entity 2 - live - published', 'status' => TRUE],
+          3 => ['id' => 'test_3', 'name' => 'Test entity 3 - stage - unpublished', 'status' => FALSE],
+          4 => ['id' => 'test_4', 'name' => 'Test entity 4 - stage - published', 'status' => TRUE],
+          5 => ['id' => 'test_5', 'name' => 'Test entity 5 - dev - unpublished', 'status' => FALSE],
+          6 => ['id' => 'test_6', 'name' => 'Test entity 6 - dev - published', 'status' => TRUE],
+        ],
+      ],
+    ];
   }
 
   /**
diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php
index 504a1aa30fa5c7bf27ab7c54110b84012fb844aa..8391859f46bff01970e9d38ac44293cc202f0249 100644
--- a/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php
+++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php
@@ -4,6 +4,7 @@
 
 namespace Drupal\Tests\workspaces\Kernel;
 
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\workspaces\Entity\Handler\IgnoredWorkspaceHandler;
 use Drupal\workspaces\Entity\Workspace;
 
@@ -163,4 +164,24 @@ protected function ignoreEntityType(string $entity_type_id): void {
     \Drupal::entityTypeManager()->clearCachedDefinitions();
   }
 
+  /**
+   * Creates an entity.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param array $values
+   *   An array of values for the entity.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   The created entity.
+   */
+  protected function createEntity(string $entity_type_id, array $values = []): EntityInterface {
+    $storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);
+
+    $entity = $storage->create($values);
+    $entity->save();
+
+    return $entity;
+  }
+
 }
diff --git a/core/modules/workspaces/workspaces.install b/core/modules/workspaces/workspaces.install
index e4c0a6668b99eed052cdbbcb015924723a165ad9..1a1eb1f98fa2ce6b53b358d55d00791994400241 100644
--- a/core/modules/workspaces/workspaces.install
+++ b/core/modules/workspaces/workspaces.install
@@ -83,8 +83,16 @@ function workspaces_schema() {
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
+        'default' => 0,
         'description' => 'The ID of the associated entity.',
       ],
+      'target_entity_id_string' => [
+        'type' => 'varchar_ascii',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The string ID of the associated entity.',
+      ],
       'target_entity_revision_id' => [
         'type' => 'int',
         'unsigned' => TRUE,
@@ -92,10 +100,10 @@ function workspaces_schema() {
         'description' => 'The revision ID of the associated entity.',
       ],
     ],
+    'primary key' => ['workspace', 'target_entity_type_id', 'target_entity_id', 'target_entity_id_string'],
     'indexes' => [
       'target_entity_revision_id' => ['target_entity_revision_id'],
     ],
-    'primary key' => ['workspace', 'target_entity_type_id', 'target_entity_id'],
   ];
 
   return $schema;
@@ -107,3 +115,30 @@ function workspaces_schema() {
 function workspaces_update_last_removed(): int {
   return 8803;
 }
+
+/**
+ * Update workspace associations to support entity types with string IDs.
+ */
+function workspaces_update_11101(): void {
+  $schema = \Drupal::database()->schema();
+
+  $target_id_spec = [
+    'type' => 'int',
+    'unsigned' => TRUE,
+    'not null' => TRUE,
+    'default' => 0,
+    'description' => 'The ID of the associated entity.',
+  ];
+  $schema->changeField('workspace_association', 'target_entity_id', 'target_entity_id', $target_id_spec);
+
+  $target_id_string_spec = [
+    'type' => 'varchar_ascii',
+    'length' => 128,
+    'not null' => TRUE,
+    'default' => '',
+    'description' => 'The string ID of the associated entity.',
+  ];
+  $schema->addField('workspace_association', 'target_entity_id_string', $target_id_string_spec, [
+    'primary key' => ['workspace', 'target_entity_type_id', 'target_entity_id', 'target_entity_id_string'],
+  ]);
+}
diff --git a/core/tests/Drupal/Tests/UpdatePathTestTrait.php b/core/tests/Drupal/Tests/UpdatePathTestTrait.php
index ccd930eaa8fbbc3fddf3e6c7dcc1c6b1be091980..b90023ec88205922b1f3a59a8e2c3179ef61f377 100644
--- a/core/tests/Drupal/Tests/UpdatePathTestTrait.php
+++ b/core/tests/Drupal/Tests/UpdatePathTestTrait.php
@@ -23,6 +23,13 @@ trait UpdatePathTestTrait {
    */
   protected $checkFailedUpdates = TRUE;
 
+  /**
+   * Fail the test if there are entity field definition updates needed.
+   *
+   * @var bool
+   */
+  protected $checkEntityFieldDefinitionUpdates = TRUE;
+
   /**
    * Helper function to run pending database updates.
    *
@@ -145,17 +152,19 @@ protected function runUpdates($update_url = NULL) {
       }
 
       // Ensure that the update hooks updated all entity schema.
-      $needs_updates = \Drupal::entityDefinitionUpdateManager()->needsUpdates();
-      if ($needs_updates) {
-        foreach (\Drupal::entityDefinitionUpdateManager()->getChangeSummary() as $entity_type_id => $summary) {
-          $entity_type_label = \Drupal::entityTypeManager()->getDefinition($entity_type_id)->getLabel();
-          foreach ($summary as $message) {
-            $this->fail("$entity_type_label: $message");
+      if ($this->checkEntityFieldDefinitionUpdates) {
+        $needs_updates = \Drupal::entityDefinitionUpdateManager()->needsUpdates();
+        if ($needs_updates) {
+          foreach (\Drupal::entityDefinitionUpdateManager()->getChangeSummary() as $entity_type_id => $summary) {
+            $entity_type_label = \Drupal::entityTypeManager()->getDefinition($entity_type_id)->getLabel();
+            foreach ($summary as $message) {
+              $this->fail("$entity_type_label: $message");
+            }
           }
+          // The above calls to `fail()` should prevent this from ever being
+          // called, but it is here in case something goes really wrong.
+          $this->assertFalse($needs_updates, 'After all updates ran, entity schema is up to date.');
         }
-        // The above calls to `fail()` should prevent this from ever being
-        // called, but it is here in case something goes really wrong.
-        $this->assertFalse($needs_updates, 'After all updates ran, entity schema is up to date.');
       }
     }
   }