diff --git a/core/composer.json b/core/composer.json
index ba488e5..f8bd203 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -140,7 +140,8 @@
         "drupal/update": "self.version",
         "drupal/user": "self.version",
         "drupal/views": "self.version",
-        "drupal/views_ui": "self.version"
+        "drupal/views_ui": "self.version",
+        "drupal/workspace": "self.version"
     },
     "minimum-stability": "dev",
     "prefer-stable": true,
diff --git a/core/modules/config/src/Tests/ConfigImportAllTest.php b/core/modules/config/src/Tests/ConfigImportAllTest.php
index baeacb9..004c5b5 100644
--- a/core/modules/config/src/Tests/ConfigImportAllTest.php
+++ b/core/modules/config/src/Tests/ConfigImportAllTest.php
@@ -7,6 +7,7 @@
 use Drupal\system\Tests\Module\ModuleTestBase;
 use Drupal\shortcut\Entity\Shortcut;
 use Drupal\taxonomy\Entity\Term;
+use Drupal\workspace\Entity\Workspace;
 
 /**
  * Tests the largest configuration import possible with all available modules.
@@ -92,6 +93,10 @@ public function testInstallUninstall() {
     $shortcuts = Shortcut::loadMultiple();
     entity_delete_multiple('shortcut', array_keys($shortcuts));
 
+    // Delete any workspaces so the workspace module can be uninstalled.
+    $workspaces = Workspace::loadMultiple();
+    \Drupal::entityTypeManager()->getStorage('workspace')->delete($workspaces);
+
     system_list_reset();
     $all_modules = system_rebuild_module_data();
 
diff --git a/core/modules/workspace/config/install/workspace.type.basic.yml b/core/modules/workspace/config/install/workspace.type.basic.yml
new file mode 100644
index 0000000..3d9c2cb
--- /dev/null
+++ b/core/modules/workspace/config/install/workspace.type.basic.yml
@@ -0,0 +1,5 @@
+langcode: en
+status: true
+dependencies: {  }
+id: basic
+label: Basic
diff --git a/core/modules/workspace/config/schema/workspace.schema.yml b/core/modules/workspace/config/schema/workspace.schema.yml
new file mode 100644
index 0000000..0049f5b
--- /dev/null
+++ b/core/modules/workspace/config/schema/workspace.schema.yml
@@ -0,0 +1,10 @@
+workspace.type.*:
+  type: config_entity
+  label: 'Workspace type'
+  mapping:
+    id:
+      type: string
+      label: 'Machine name'
+    label:
+      type: label
+      label: 'Label'
diff --git a/core/modules/workspace/css/workspace.admin.css b/core/modules/workspace/css/workspace.admin.css
new file mode 100644
index 0000000..31b757d
--- /dev/null
+++ b/core/modules/workspace/css/workspace.admin.css
@@ -0,0 +1,91 @@
+.item-list ul.workspace,
+.item-list ul.workspace ul {
+    padding-top: 20px;
+    position: relative;
+    margin: 0;
+}
+
+.item-list .workspace li {
+    float: left;
+    text-align: center;
+    list-style-type: none;
+    position: relative;
+    padding: 20px 5px 0;
+    margin: 0;
+}
+
+/* We will use ::before and ::after to draw the connectors */
+.item-list .workspace li::before,
+.item-list .workspace li::after {
+    content: '';
+    position: absolute;
+    top: 0;
+    right: 50%;
+    border-top: 1px solid #ccc;
+    width: 50%;
+    height: 20px;
+}
+.item-list .workspace li::after {
+    right: auto;
+    left: 50%;
+    border-left: 1px solid #ccc;
+}
+
+/* We need to remove left-right connectors from elements without any siblings */
+.item-list .workspace li:only-child::after,
+.item-list .workspace li:only-child::before {
+    display: none;
+}
+
+/* Remove space from the top of single children */
+.item-list .workspace li:only-child{
+    padding-top: 0;
+}
+
+/* Remove left connector from first child and right connector from last child*/
+.item-list .workspace li:first-child::before,
+.item-list .workspace li:last-child::after {
+    border: 0 none;
+}
+
+/* Adding back the vertical connector to the last nodes */
+.item-list .workspace li:last-child::before {
+    border-right: 1px solid #ccc;
+}
+
+/* Downward connector from parents */
+.item-list ul.workspace ul::before,
+.item-list ul.workspace ul ul::before {
+    content: '';
+    position: absolute; top: 0; left: 50%;
+    border-left: 1px solid #ccc;
+    width: 0;
+    height: 20px;
+}
+
+.item-list .workspace .rev,
+.item-list .workspace .rev__title {
+    display: inline-block;
+    margin: 0;
+}
+
+.item-list .workspace .panel__title {
+    padding: 0;
+}
+
+.item-list .workspace hr {
+    margin: 5px 0;
+}
+
+.rev {
+    margin: 0px 0px 20px;
+    padding: 9px;
+    background: #F8F8F8;
+    border: 1px solid #CCC
+}
+
+.rev__title {
+    font-size: 1em;
+    text-transform: uppercase;
+    margin: 0px;
+}
\ No newline at end of file
diff --git a/core/modules/workspace/css/workspace.switcher.css b/core/modules/workspace/css/workspace.switcher.css
new file mode 100644
index 0000000..a833a6b
--- /dev/null
+++ b/core/modules/workspace/css/workspace.switcher.css
@@ -0,0 +1,26 @@
+/**
+ * @file
+ * Styling for switcher block.
+ */
+
+#block-workspaceswitcher input[type="submit"] {
+  background: transparent;
+  border: 0;
+  border-radius: 0;
+  padding: 0.2em 0 0 0 ;
+  margin: 0;
+  color: #0071b3;
+  border-bottom: 1px dotted;
+  text-decoration: none;
+  font-family: Georgia, "Times New Roman", Times, serif;
+  font-size: 1em;
+}
+
+#block-workspaceswitcher input[type="submit"]:hover {
+  border-bottom-style: solid;
+  color: #018fe2;
+}
+
+#block-workspaceswitcher input[type="submit"].is-active {
+  color: #000000;
+}
diff --git a/core/modules/workspace/css/workspace.toolbar.css b/core/modules/workspace/css/workspace.toolbar.css
new file mode 100644
index 0000000..9b82323
--- /dev/null
+++ b/core/modules/workspace/css/workspace.toolbar.css
@@ -0,0 +1,67 @@
+/**
+ * @file
+ * Styling for Workspace toolbar.
+ */
+
+.toolbar .toolbar-icon-workspace:before {
+  background-image: url("../icons/bebebe/workspace.svg");
+}
+
+.toolbar .toolbar-icon-workspace.is-active:before {
+  background-image: url("../icons/ffffff/workspace.svg");
+}
+
+.toolbar .toolbar-icon-workspace-update:before {
+  background-image: url("../icons/bebebe/update.svg");
+}
+
+.toolbar .toolbar-icon-workspace-update.is-active:before {
+  background-image: url("../icons/ffffff/update.svg");
+}
+
+.toolbar .toolbar-bar .workspace-toolbar-tab.toolbar-tab,
+.toolbar .toolbar-bar .workspace-update-toolbar-tab.toolbar-tab{
+  float: right;
+}
+
+.toolbar #toolbar-item-workspace-switcher-tray input[type="submit"] {
+  display: block;
+  background: transparent;
+  border: 0;
+  border-radius: 0;
+  margin: 0;
+  padding: 1em 1.3333em;
+  font-weight: normal;
+  font-size: 1em;
+  line-height: 1;
+  color: #565656;
+}
+
+.toolbar #toolbar-item-workspace-switcher-tray input[type="submit"]:hover {
+  text-decoration: underline;
+  color: #000000;
+  box-shadow: none;
+}
+
+.toolbar #toolbar-item-workspace-switcher-tray input[type="submit"].is-active {
+  color: #000000;
+  font-weight: bold;
+  text-decoration: underline;
+}
+
+.toolbar #toolbar-item-workspace-switcher-tray.toolbar-tray-horizontal input[type="submit"] {
+  float: left;
+}
+
+.toolbar #toolbar-item-workspace-switcher-tray.toolbar-tray-vertical input[type="submit"] {
+  padding-left: 2.75em;
+  padding-right: 4em;
+}
+
+.toolbar #toolbar-item-workspace-switcher-tray.toolbar-tray-vertical form {
+  background-color: #ffffff;
+}
+
+.toolbar #toolbar-item-workspace-switcher-tray a.add-workspace {
+  float: right;
+}
diff --git a/core/modules/workspace/icons/bebebe/update.svg b/core/modules/workspace/icons/bebebe/update.svg
new file mode 100644
index 0000000..c114dea
--- /dev/null
+++ b/core/modules/workspace/icons/bebebe/update.svg
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE svg  PUBLIC '-//W3C//DTD SVG 1.1//EN'  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 32 32" height="32px" id="Layer_1" version="1.1" viewBox="0 0 32 32" width="32px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><path d="M25.444,4.291c0,0-1.325,1.293-2.243,2.201C18.514,3.068,11.909,3.456,7.676,7.689   c-2.47,2.47-3.623,5.747-3.484,8.983h4C8.051,14.46,8.81,12.205,10.5,10.514c2.663-2.663,6.735-3.043,9.812-1.162   c-1.042,1.032-2.245,2.238-2.245,2.238c-0.841,1.009,0.104,1.592,0.584,1.577l5.624-0.001c0.297,0,0.539,0.001,0.539,0.001   s0.245,0,0.543,0h1.092c0.298,0,0.54-0.243,0.54-0.541V4.895C27.023,4.188,26.247,3.502,25.444,4.291z" fill="#bebebe"/><path d="M6.555,27.709c0,0,1.326-1.293,2.243-2.201c4.688,3.424,11.292,3.036,15.526-1.197   c2.47-2.471,3.622-5.747,3.484-8.983h-4.001c0.142,2.211-0.617,4.467-2.308,6.159c-2.663,2.662-6.735,3.043-9.812,1.161   c1.042-1.032,2.245-2.238,2.245-2.238c0.841-1.01-0.104-1.592-0.584-1.577l-5.624,0.002c-0.297,0-0.54-0.002-0.54-0.002   s-0.245,0-0.543,0H5.551c-0.298,0-0.54,0.242-0.541,0.541v7.732C4.977,27.812,5.753,28.498,6.555,27.709z" fill="#bebebe"/></g></svg>
\ No newline at end of file
diff --git a/core/modules/workspace/icons/bebebe/workspace.svg b/core/modules/workspace/icons/bebebe/workspace.svg
new file mode 100644
index 0000000..0d893f3
--- /dev/null
+++ b/core/modules/workspace/icons/bebebe/workspace.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 3.6.1 (26313) - http://www.bohemiancoding.com/sketch -->
+    <title>Combined Shape</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Group-13" transform="translate(-28.000000, -12.000000)" fill="#BEBEBE">
+            <path d="M42,24 L44,24 L44,12 L32,12 L32,14 L42,14 L42,24 Z M28,16 L40,16 L40,28 L28,28 L28,16 Z" id="Combined-Shape"></path>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/core/modules/workspace/icons/ffffff/update.svg b/core/modules/workspace/icons/ffffff/update.svg
new file mode 100644
index 0000000..61bba58
--- /dev/null
+++ b/core/modules/workspace/icons/ffffff/update.svg
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE svg  PUBLIC '-//W3C//DTD SVG 1.1//EN'  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 32 32" height="32px" id="Layer_1" version="1.1" viewBox="0 0 32 32" width="32px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><path d="M25.444,4.291c0,0-1.325,1.293-2.243,2.201C18.514,3.068,11.909,3.456,7.676,7.689   c-2.47,2.47-3.623,5.747-3.484,8.983h4C8.051,14.46,8.81,12.205,10.5,10.514c2.663-2.663,6.735-3.043,9.812-1.162   c-1.042,1.032-2.245,2.238-2.245,2.238c-0.841,1.009,0.104,1.592,0.584,1.577l5.624-0.001c0.297,0,0.539,0.001,0.539,0.001   s0.245,0,0.543,0h1.092c0.298,0,0.54-0.243,0.54-0.541V4.895C27.023,4.188,26.247,3.502,25.444,4.291z" fill="#ffffff"/><path d="M6.555,27.709c0,0,1.326-1.293,2.243-2.201c4.688,3.424,11.292,3.036,15.526-1.197   c2.47-2.471,3.622-5.747,3.484-8.983h-4.001c0.142,2.211-0.617,4.467-2.308,6.159c-2.663,2.662-6.735,3.043-9.812,1.161   c1.042-1.032,2.245-2.238,2.245-2.238c0.841-1.01-0.104-1.592-0.584-1.577l-5.624,0.002c-0.297,0-0.54-0.002-0.54-0.002   s-0.245,0-0.543,0H5.551c-0.298,0-0.54,0.242-0.541,0.541v7.732C4.977,27.812,5.753,28.498,6.555,27.709z" fill="#ffffff"/></g></svg>
\ No newline at end of file
diff --git a/core/modules/workspace/icons/ffffff/workspace.svg b/core/modules/workspace/icons/ffffff/workspace.svg
new file mode 100644
index 0000000..584ec8c
--- /dev/null
+++ b/core/modules/workspace/icons/ffffff/workspace.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 3.6.1 (26313) - http://www.bohemiancoding.com/sketch -->
+    <title>Combined Shape</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Group-13" transform="translate(-28.000000, -12.000000)" fill="#FFFFFF">
+            <path d="M42,24 L44,24 L44,12 L32,12 L32,14 L42,14 L42,24 Z M28,16 L40,16 L40,28 L28,28 L28,16 Z" id="Combined-Shape"></path>
+        </g>
+    </g>
+</svg>
diff --git a/core/modules/workspace/src/Access/WorkspaceViewCheck.php b/core/modules/workspace/src/Access/WorkspaceViewCheck.php
new file mode 100644
index 0000000..8d45be1
--- /dev/null
+++ b/core/modules/workspace/src/Access/WorkspaceViewCheck.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\workspace\Access;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Access\AccessResultInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\workspace\Entity\WorkspaceInterface;
+
+class WorkspaceViewCheck implements AccessInterface {
+
+  /**
+   * Checks that the user should be able to view the specified workspace.
+   *
+   * "View" in practice implies "is allowed to make active".
+   *
+   * @param WorkspaceInterface $workspace
+   *   The workspace to view.
+   * @param AccountInterface $account
+   *   The user account to check.
+   *
+   * @return AccessResultInterface
+   *   The access result.
+   */
+  public function access(WorkspaceInterface $workspace, AccountInterface $account) {
+    return AccessResult::allowedIf($workspace->access('view', $account))->addCacheableDependency($workspace);
+  }
+}
diff --git a/core/modules/workspace/src/Controller/WorkspaceController.php b/core/modules/workspace/src/Controller/WorkspaceController.php
new file mode 100644
index 0000000..0b993c9
--- /dev/null
+++ b/core/modules/workspace/src/Controller/WorkspaceController.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\workspace\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Url;
+use Drupal\workspace\Entity\Workspace;
+use Drupal\workspace\Entity\WorkspaceType;
+use Drupal\workspace\Entity\WorkspaceTypeInterface;
+
+class WorkspaceController extends ControllerBase {
+
+  public function add() {
+    $types = WorkspaceType::loadMultiple();
+    if ($types && count($types) == 1) {
+      $type = reset($types);
+      return $this->addForm($type);
+    }
+    if (count($types) === 0) {
+      return array(
+        '#markup' => $this->t('You have not created any Workspace types yet. Go to the <a href=":url">Workspace type creation page</a> to add a new Workspace type.', [
+          ':url' => Url::fromRoute('entity.workspace_type.add_form')->toString(),
+        ]),
+      );
+    }
+
+    return array('#theme' => 'workspace_add_list', '#content' => $types);
+
+  }
+
+  public function addForm(WorkspaceTypeInterface $workspace_type) {
+    $workspace = Workspace::create([
+      'type' => $workspace_type->id()
+    ]);
+    return $this->entityFormBuilder()->getForm($workspace);
+  }
+
+  public function getAddFormTitle(WorkspaceTypeInterface $workspace_type) {
+    return $this->t('Add %type workspace', array('%type' => $workspace_type->label()));
+  }
+}
diff --git a/core/modules/workspace/src/DefaultWorkspaceNegotiator.php b/core/modules/workspace/src/DefaultWorkspaceNegotiator.php
new file mode 100644
index 0000000..bc65453
--- /dev/null
+++ b/core/modules/workspace/src/DefaultWorkspaceNegotiator.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\workspace;
+
+use Symfony\Component\HttpFoundation\Request;
+
+class DefaultWorkspaceNegotiator extends WorkspaceNegotiatorBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies(Request $request) {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWorkspaceId(Request $request) {
+    return $this->container->getParameter('workspace.default');
+  }
+
+}
diff --git a/core/modules/workspace/src/Entity/ContentWorkspace.php b/core/modules/workspace/src/Entity/ContentWorkspace.php
new file mode 100644
index 0000000..8db613f
--- /dev/null
+++ b/core/modules/workspace/src/Entity/ContentWorkspace.php
@@ -0,0 +1,173 @@
+<?php
+
+namespace Drupal\workspace\Entity;
+
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\TypedData\TranslatableInterface;
+use Drupal\user\UserInterface;
+
+/**
+ * Defines the Content workspace entity.
+ *
+ * @ContentEntityType(
+ *   id = "content_workspace",
+ *   label = @Translation("Content workspace"),
+ *   label_singular = @Translation("content workspace"),
+ *   label_plural = @Translation("content workspaces"),
+ *   label_count = @PluralTranslation(
+ *     singular = "@count content workspace",
+ *     plural = "@count content workspaces"
+ *   ),
+ *   handlers = {
+ *     "storage_schema" = "Drupal\content_moderation\ContentWorkspaceStorageSchema",
+ *     "views_data" = "\Drupal\views\EntityViewsData",
+ *   },
+ *   base_table = "content_workspace",
+ *   revision_table = "content_workspace_revision",
+ *   data_table = "content_workspace_field_data",
+ *   revision_data_table = "content_workspace_field_revision",
+ *   translatable = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "revision" = "revision_id",
+ *     "uuid" = "uuid",
+ *     "uid" = "uid",
+ *     "langcode" = "langcode",
+ *   }
+ * )
+ */
+class ContentWorkspace extends ContentEntityBase implements ContentWorkspaceInterface  {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields = parent::baseFieldDefinitions($entity_type);
+
+    $fields['uid'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('User'))
+      ->setDescription(t('The username of the entity creator.'))
+      ->setSetting('target_type', 'user')
+      ->setDefaultValueCallback('Drupal\workspace\Entity\ContentWorkspace::getCurrentUserId')
+      ->setTranslatable(TRUE)
+      ->setRevisionable(TRUE);
+
+    $fields['workspace'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('workspace'))
+      ->setDescription(t('The workspace of the referenced content.'))
+      ->setSetting('target_type', 'workspace')
+      ->setRequired(TRUE)
+      ->setTranslatable(TRUE)
+      ->setRevisionable(TRUE)
+      ->addConstraint('workspace', []);
+
+    $fields['content_entity_type_id'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Content entity type ID'))
+      ->setDescription(t('The ID of the content entity type this workspace is for.'))
+      ->setRequired(TRUE)
+      ->setRevisionable(TRUE);
+
+    $fields['content_entity_id'] = BaseFieldDefinition::create('integer')
+      ->setLabel(t('Content entity ID'))
+      ->setDescription(t('The ID of the content entity this workspace is for.'))
+      ->setRequired(TRUE)
+      ->setRevisionable(TRUE);
+
+    $fields['content_entity_revision_id'] = BaseFieldDefinition::create('integer')
+      ->setLabel(t('Content entity revision ID'))
+      ->setDescription(t('The revision ID of the content entity this workspace is for.'))
+      ->setRequired(TRUE)
+      ->setRevisionable(TRUE);
+
+    return $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwner() {
+    return $this->get('uid')->entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwnerId() {
+    return $this->getEntityKey('uid');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwnerId($uid) {
+    $this->set('uid', $uid);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwner(UserInterface $account) {
+    $this->set('uid', $account->id());
+    return $this;
+  }
+
+  /**
+   * Creates or updates an entity's workspace whilst saving that entity.
+   *
+   * @param \Drupal\content_moderation\Entity\ContentWorkspace $content_workspace
+   *   The content moderation entity content entity to create or save.
+   *
+   * @internal
+   *   This method should only be called as a result of saving the related
+   *   content entity.
+   */
+  public static function updateOrCreateFromEntity(ContentWorkspace $content_workspace) {
+    $content_workspace->realSave();
+  }
+
+  /**
+   * Default value callback for the 'uid' base field definition.
+   *
+   * @see \Drupal\content_moderation\Entity\ContentWorkspace::baseFieldDefinitions()
+   *
+   * @return array
+   *   An array of default values.
+   */
+  public static function getCurrentUserId() {
+    return array(\Drupal::currentUser()->id());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save() {
+    $related_entity = \Drupal::entityTypeManager()
+      ->getStorage($this->content_entity_type_id->value)
+      ->loadRevision($this->content_entity_revision_id->value);
+    if ($related_entity instanceof TranslatableInterface) {
+      $related_entity = $related_entity->getTranslation($this->activeLangcode);
+    }
+    $related_entity->workspace->target_id = $this->workspace->target_id;
+    return $related_entity->save();
+  }
+
+  /**
+   * Saves an entity permanently.
+   *
+   * When saving existing entities, the entity is assumed to be complete,
+   * partial updates of entities are not supported.
+   *
+   * @return int
+   *   Either SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   *   In case of failures an exception is thrown.
+   */
+  protected function realSave() {
+    return parent::save();
+  }
+
+}
diff --git a/core/modules/workspace/src/Entity/ContentWorkspaceInterface.php b/core/modules/workspace/src/Entity/ContentWorkspaceInterface.php
new file mode 100644
index 0000000..4527a08
--- /dev/null
+++ b/core/modules/workspace/src/Entity/ContentWorkspaceInterface.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Drupal\workspace\Entity;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\user\EntityOwnerInterface;
+
+/**
+ * An interface for Content workspace entity.
+ *
+ * Content workspace entities track the workspace of other content entities.
+ */
+interface ContentWorkspaceInterface extends ContentEntityInterface, EntityOwnerInterface {
+
+}
diff --git a/core/modules/workspace/src/Entity/Form/WorkspaceForm.php b/core/modules/workspace/src/Entity/Form/WorkspaceForm.php
new file mode 100644
index 0000000..1a4fee5
--- /dev/null
+++ b/core/modules/workspace/src/Entity/Form/WorkspaceForm.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace Drupal\workspace\Entity\Form;
+
+use Drupal\Core\Entity\ContentEntityForm;
+use Drupal\Core\Entity\EntityConstraintViolationListInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Form controller for the workspace edit forms.
+ */
+class WorkspaceForm extends ContentEntityForm {
+
+  /**
+   * The workspace content entity.
+   *
+   * @var \Drupal\workspace\Entity\WorkspaceInterface
+   */
+  protected $entity;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $workspace = $this->entity;
+
+    if ($this->operation == 'edit') {
+      $form['#title'] = $this->t('Edit workspace %label', array('%label' => $workspace->label()));
+    }
+    $form['label'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#maxlength' => 255,
+      '#default_value' => $workspace->label(),
+      '#description' => $this->t("Label for the Workspace."),
+      '#required' => TRUE,
+    );
+
+    $form['machine_name'] = array(
+      '#type' => 'machine_name',
+      '#title' => $this->t('Workspace ID'),
+      '#maxlength' => 255,
+      '#default_value' => $workspace->get('machine_name')->value,
+      '#machine_name' => array(
+        'exists' => '\Drupal\workspace\Entity\Workspace::load',
+      ),
+      '#element_validate' => array(),
+    );
+
+    return parent::form($form, $form_state, $workspace);;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditedFieldNames(FormStateInterface $form_state) {
+    return array_merge(array(
+      'label',
+      'machine_name',
+    ), parent::getEditedFieldNames($form_state));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
+    // Manually flag violations of fields not handled by the form display. This
+    // is necessary as entity form displays only flag violations for fields
+    // contained in the display.
+    $field_names = array(
+      'label',
+      'machine_name'
+    );
+    foreach ($violations->getByFields($field_names) as $violation) {
+      list($field_name) = explode('.', $violation->getPropertyPath(), 2);
+      $form_state->setErrorByName($field_name, $violation->getMessage());
+    }
+    parent::flagViolations($violations, $form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    $workspace = $this->entity;
+    $insert = $workspace->isNew();
+    $workspace->save();
+    $info = ['%info' => $workspace->label()];
+    $context = array('@type' => $workspace->bundle(), '%info' => $workspace->label());
+    $logger = $this->logger('workspace');
+
+    if ($insert) {
+      $logger->notice('@type: added %info.', $context);
+      drupal_set_message($this->t('Workspace %info has been created.', $info));
+    }
+    else {
+      $logger->notice('@type: updated %info.', $context);
+      drupal_set_message($this->t('Workspace %info has been updated.', $info));
+    }
+
+    if ($workspace->id()) {
+      $form_state->setValue('id', $workspace->id());
+      $form_state->set('id', $workspace->id());
+      $redirect = $this->currentUser()->hasPermission('administer workspaces') ? $workspace->toUrl('collection') : $workspace->toUrl('canonical');
+      $form_state->setRedirectUrl($redirect);
+    }
+    else {
+      drupal_set_message($this->t('The workspace could not be saved.'), 'error');
+      $form_state->setRebuild();
+    }
+  }
+
+}
diff --git a/core/modules/workspace/src/Entity/Form/WorkspaceTypeDeleteForm.php b/core/modules/workspace/src/Entity/Form/WorkspaceTypeDeleteForm.php
new file mode 100644
index 0000000..74b0080
--- /dev/null
+++ b/core/modules/workspace/src/Entity/Form/WorkspaceTypeDeleteForm.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\workspace\Entity\Form;
+
+use Drupal\Core\Entity\EntityDeleteForm;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\Core\Form\FormStateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a confirmation form for deleting a custom workspace type entity.
+ */
+class WorkspaceTypeDeleteForm extends EntityDeleteForm {
+
+  /**
+   * The query factory to create entity queries.
+   *
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  public $queryFactory;
+
+  /**
+   * Constructs a query factory object.
+   *
+   * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
+   *   The entity query object.
+   */
+  public function __construct(QueryFactory $query_factory) {
+    $this->queryFactory = $query_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.query')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $workspaces = $this->queryFactory->get('workspace')->condition('type', $this->entity->id())->execute();
+    if (!empty($workspaces)) {
+      $caption = '<p>' . $this->formatPlural(count($workspaces), '%label is used by 1 workspace on your site. You can not remove this workspace type until you have removed all of the %label workspaces.', '%label is used by @count workspaces on your site. You may not remove %label until you have removed all of the %label custom workspaces.', array('%label' => $this->entity->label())) . '</p>';
+      $form['description'] = array('#markup' => $caption);
+      return $form;
+    }
+    else {
+      return parent::buildForm($form, $form_state);
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/core/modules/workspace/src/Entity/Form/WorkspaceTypeForm.php b/core/modules/workspace/src/Entity/Form/WorkspaceTypeForm.php
new file mode 100644
index 0000000..dc8a49c
--- /dev/null
+++ b/core/modules/workspace/src/Entity/Form/WorkspaceTypeForm.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\workspace\Entity\Form;
+
+use Drupal\Core\Entity\BundleEntityFormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class WorkspaceTypeForm.
+ *
+ * @package Drupal\workspace\Entity\Form
+ */
+class WorkspaceTypeForm extends BundleEntityFormBase {
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $form = parent::form($form, $form_state);
+
+    $workspace_type = $this->entity;
+    $form['label'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#maxlength' => 255,
+      '#default_value' => $workspace_type->label(),
+      '#description' => $this->t("Label for the Workspace type."),
+      '#required' => TRUE,
+    );
+
+    $form['id'] = array(
+      '#type' => 'machine_name',
+      '#default_value' => $workspace_type->id(),
+      '#machine_name' => array(
+        'exists' => '\Drupal\workspace\Entity\WorkspaceType::load',
+      ),
+      '#disabled' => !$workspace_type->isNew(),
+    );
+
+    return $this->protectBundleIdElement($form);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    $workspace_type = $this->entity;
+    $status = $workspace_type->save();
+
+    switch ($status) {
+      case SAVED_NEW:
+        drupal_set_message($this->t('Created the %label Workspace type.', [
+          '%label' => $workspace_type->label(),
+        ]));
+        break;
+
+      default:
+        drupal_set_message($this->t('Saved the %label Workspace type.', [
+          '%label' => $workspace_type->label(),
+        ]));
+    }
+    $form_state->setRedirectUrl($workspace_type->urlInfo('collection'));
+  }
+
+}
\ No newline at end of file
diff --git a/core/modules/workspace/src/Entity/Workspace.php b/core/modules/workspace/src/Entity/Workspace.php
new file mode 100644
index 0000000..c0bbd4f
--- /dev/null
+++ b/core/modules/workspace/src/Entity/Workspace.php
@@ -0,0 +1,205 @@
+<?php
+
+namespace Drupal\workspace\Entity;
+
+use Drupal\Core\Entity\Entity;
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\user\UserInterface;
+
+/**
+ * The workspace entity class.
+ *
+ * @ContentEntityType(
+ *   id = "workspace",
+ *   label = @Translation("Workspace"),
+ *   bundle_label = @Translation("Workspace type"),
+ *   handlers = {
+ *     "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage",
+ *     "list_builder" = "\Drupal\workspace\WorkspaceListBuilder",
+ *     "route_provider" = {
+ *       "html" = "\Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
+ *     },
+ *     "form" = {
+ *       "default" = "\Drupal\workspace\Entity\Form\WorkspaceForm",
+ *       "add" = "\Drupal\workspace\Entity\Form\WorkspaceForm",
+ *       "edit" = "\Drupal\workspace\Entity\Form\WorkspaceForm",
+ *     },
+ *   },
+ *   admin_permission = "administer workspaces",
+ *   base_table = "workspace",
+ *   revision_table = "workspace_revision",
+ *   data_table = "workspace_field_data",
+ *   revision_data_table = "workspace_field_revision",
+ *   bundle_entity_type = "workspace_type",
+ *   field_ui_base_route = "entity.workspace_type.edit_form",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "revision" = "revision_id",
+ *     "bundle" = "type",
+ *     "uuid" = "uuid",
+ *     "label" = "label",
+ *     "machine_name" = "machine_name",
+ *     "uid" = "uid",
+ *     "created" = "created"
+ *   },
+ *   links = {
+ *     "canonical" = "/admin/structure/workspace/{workspace}",
+ *     "edit-form" = "/admin/structure/workspace/{workspace}/edit",
+ *     "activate-form" = "/admin/structure/workspace/{workspace}/activate",
+ *     "collection" = "/admin/structure/workspace",
+ *   },
+ * )
+ */
+class Workspace extends ContentEntityBase implements WorkspaceInterface {
+
+  use EntityChangedTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields['id'] = BaseFieldDefinition::create('integer')
+      ->setLabel(t('Workspace ID'))
+      ->setDescription(t('The workspace ID.'))
+      ->setReadOnly(TRUE)
+      ->setSetting('unsigned', TRUE);
+
+    $fields['uuid'] = BaseFieldDefinition::create('uuid')
+      ->setLabel(t('UUID'))
+      ->setDescription(t('The workspace UUID.'))
+      ->setReadOnly(TRUE);
+
+    $fields['revision_id'] = BaseFieldDefinition::create('integer')
+      ->setLabel(t('Revision ID'))
+      ->setDescription(t('The revision ID.'))
+      ->setReadOnly(TRUE)
+      ->setSetting('unsigned', TRUE);
+
+    $fields['label'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Workaspace name'))
+      ->setDescription(t('The workspace name.'))
+      ->setRevisionable(TRUE)
+      ->setSetting('max_length', 128)
+      ->setRequired(TRUE);
+
+    $fields['machine_name'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Workaspace ID'))
+      ->setDescription(t('The workspace machine name.'))
+      ->setRevisionable(TRUE)
+      ->setSetting('max_length', 128)
+      ->setRequired(TRUE)
+      ->addPropertyConstraints('value', ['Regex' => ['pattern' => '/^[\da-z_$()+-\/]*$/']]);
+
+    $fields['uid'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('Owner'))
+      ->setDescription(t('The workspace owner.'))
+      ->setRevisionable(TRUE)
+      ->setSetting('target_type', 'user')
+      ->setDefaultValueCallback('Drupal\workspace\Entity\Workspace::getCurrentUserId');
+
+    $fields['type'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('Type'))
+      ->setDescription(t('The workspace type.'))
+      ->setSetting('target_type', 'workspace_type')
+      ->setReadOnly(TRUE);
+
+    $fields['changed'] = BaseFieldDefinition::create('changed')
+      ->setLabel(t('Changed'))
+      ->setDescription(t('The time that the workspace was last edited.'))
+      ->setRevisionable(TRUE);
+
+    $fields['created'] = BaseFieldDefinition::create('created')
+      ->setLabel(t('Created'))
+      ->setDescription(t('The UNIX timestamp of when the workspace has been created.'));
+
+    $fields['upstream'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('Assign default target workspace'))
+      ->setDescription(t('The workspace to push to and pull from.'))
+      ->setRevisionable(TRUE)
+      ->setRequired(TRUE)
+      ->setSetting('target_type', 'workspace')
+      ->setDefaultValueCallback('workspace_active_id')
+      ->setDisplayOptions('form', [
+        'type' => 'options_buttons',
+        'weight' => 0
+      ])
+      ->setDisplayConfigurable('form', TRUE);
+
+    return $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUpdateSeq() {
+    return \Drupal::service('workspace.entity_index.sequence')->useWorkspace($this->id())->getLastSequenceId();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCreatedTime($created) {
+    $this->set('created', (int) $created);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getStartTime() {
+    return $this->get('created')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMachineName() {
+    return $this->get('machine_name')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwner() {
+    return $this->get('uid')->entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwner(UserInterface $account) {
+    $this->set('uid', $account->id());
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwnerId() {
+    return $this->get('uid')->target_id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwnerId($uid) {
+    $this->set('uid', $uid);
+    return $this;
+  }
+
+  /**
+   * Default value callback for 'uid' base field definition.
+   *
+   * @see ::baseFieldDefinitions()
+   *
+   * @return array
+   *   An array of default values.
+   */
+  public static function getCurrentUserId() {
+    return [\Drupal::currentUser()->id()];
+  }
+
+}
diff --git a/core/modules/workspace/src/Entity/WorkspaceInterface.php b/core/modules/workspace/src/Entity/WorkspaceInterface.php
new file mode 100644
index 0000000..16a211a
--- /dev/null
+++ b/core/modules/workspace/src/Entity/WorkspaceInterface.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\workspace\Entity;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\user\EntityOwnerInterface;
+
+interface WorkspaceInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface {
+
+  /**
+   * Returns the last sequence ID in the workspace's sequence index.
+   *
+   * @return float
+   */
+  public function getUpdateSeq();
+
+  /**
+   * Sets the workspace creation timestamp.
+   *
+   * @param int $timestamp
+   *   The workspace creation timestamp.
+   *
+   * @return \Drupal\workspace\Entity\WorkspaceInterface
+   *   The called workspace entity.
+   */
+  public function setCreatedTime($timestamp);
+
+  /**
+   * Returns the workspace creation timestamp.
+   *
+   * @return int
+   *   Creation timestamp of the workspace.
+   */
+  public function getStartTime();
+
+  /**
+   * Returns the workspace machine name.
+   *
+   * @return string
+   *   Machine name of the workspace.
+   */
+  public function getMachineName();
+
+}
diff --git a/core/modules/workspace/src/Entity/WorkspaceType.php b/core/modules/workspace/src/Entity/WorkspaceType.php
new file mode 100644
index 0000000..99dc9f7
--- /dev/null
+++ b/core/modules/workspace/src/Entity/WorkspaceType.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\workspace\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
+
+/**
+ * Defines the Workspace type entity.
+ *
+ * @ConfigEntityType(
+ *   id = "workspace_type",
+ *   label = @Translation("Workspace type"),
+ *   handlers = {
+ *     "list_builder" = "\Drupal\workspace\WorkspaceTypeListBuilder",
+ *     "route_provider" = {
+ *       "html" = "\Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
+ *     },
+ *     "form" = {
+ *       "default" = "\Drupal\workspace\Entity\Form\WorkspaceTypeForm",
+ *       "add" = "\Drupal\workspace\Entity\Form\WorkspaceTypeForm",
+ *       "edit" = "\Drupal\workspace\Entity\Form\WorkspaceTypeForm",
+ *       "delete" = "\Drupal\workspace\Entity\Form\WorkspaceTypeDeleteForm",
+ *     },
+ *   },
+ *   config_prefix = "type",
+ *   bundle_of = "workspace",
+ *   admin_permission = "administer site configuration",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid"
+ *   },
+ *    links = {
+ *     "edit-form" = "/admin/structure/workspace/types/{workspace_type}/edit",
+ *     "delete-form" = "/admin/structure/workspace/types/{workspace_type}/delete",
+ *     "collection" = "/admin/structure/workspace/types",
+ *   },
+ *   config_export = {
+ *     "id",
+ *     "label",
+ *   }
+ * )
+ */
+class WorkspaceType extends ConfigEntityBundleBase implements WorkspaceTypeInterface {
+  /**
+   * The Workspace type ID.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The Workspace type label.
+   *
+   * @var string
+   */
+  protected $label;
+
+}
diff --git a/core/modules/workspace/src/Entity/WorkspaceTypeInterface.php b/core/modules/workspace/src/Entity/WorkspaceTypeInterface.php
new file mode 100644
index 0000000..7bf2049
--- /dev/null
+++ b/core/modules/workspace/src/Entity/WorkspaceTypeInterface.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\workspace\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+
+/**
+ * Provides an interface for defining Workspace type entities.
+ */
+interface WorkspaceTypeInterface extends ConfigEntityInterface {
+
+}
diff --git a/core/modules/workspace/src/EntityAccess.php b/core/modules/workspace/src/EntityAccess.php
new file mode 100644
index 0000000..5c0cf01
--- /dev/null
+++ b/core/modules/workspace/src/EntityAccess.php
@@ -0,0 +1,262 @@
+<?php
+
+namespace Drupal\workspace;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\workspace\Entity\WorkspaceInterface;
+use Drupal\workspace\WorkspaceManagerInterface;
+
+/**
+ * Service wrapper for hooks relating to entity access control.
+ */
+class EntityAccess {
+  use StringTranslationTrait;
+
+  /**
+   * @var int
+   *
+   * The ID of the default workspace, which has special permission handling.
+   */
+  protected $defaultWorkspaceId;
+
+  /**
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * @var \Drupal\workspace\WorkspaceManagerInterface
+   */
+  protected $workspaceManager;
+
+  /**
+   * Constructs a new EntityAccess.
+   *
+   * @param EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager service.
+   * @param WorkspaceManagerInterface $workspace_manager
+   *   The workspace manager service.
+   * @param int $default_workspace
+   *   The ID of the default workspace.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, $default_workspace) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->workspaceManager = $workspace_manager;
+    $this->defaultWorkspaceId = $default_workspace;
+  }
+
+  /**
+   * Hook bridge;
+   *
+   * @see hook_entity_access()
+   *
+   * @param EntityInterface $entity
+   * @param string $operation
+   * @param AccountInterface $account
+   *
+   * @return AccessResult
+   */
+  public function entityAccess(EntityInterface $entity, $operation, AccountInterface $account) {
+
+    // Workspaces themselves are handled by another hook. Ignore them here.
+    if ($entity->getEntityTypeId() == 'workspace') {
+      return AccessResult::neutral();
+    }
+
+    return $this->bypassAccessResult($account);
+  }
+
+  /**
+   * Hook bridge;
+   *
+   * @see hook_entity_create_access()
+   *
+   * @param AccountInterface $account
+   * @param array $context
+   * @param $entity_bundle
+   *
+   * @return \Drupal\Core\Access\AccessResult
+   */
+  public function entityCreateAccess(AccountInterface $account, array $context, $entity_bundle) {
+
+    // Workspaces themselves are handled by another hook. Ignore them here.
+    if ($entity_bundle == 'workspace') {
+      return AccessResult::neutral();
+    }
+
+    return $this->bypassAccessResult($account);
+  }
+
+  /**
+   * @param AccountInterface $account
+   * @return AccessResult
+   */
+  protected function bypassAccessResult(AccountInterface $account) {
+    // This approach assumes that the current "global" active workspace is
+    // correct, ie, if you're "in" a given workspace then you get ALL THE PERMS
+    // to ALL THE THINGS! That's why this is a dangerous permission.
+    $active_workspace = $this->workspaceManager->getActiveWorkspace();
+
+    return AccessResult::allowedIfHasPermission($account, 'bypass_entity_access_workspace_' . $active_workspace->id())
+      ->orIf(
+        AccessResult::allowedIf($active_workspace->getOwnerId() == $account->id())
+          ->andIf(AccessResult::allowedIfHasPermission($account, 'bypass_entity_access_own_workspace'))
+      );
+  }
+
+  /**
+   * Hook bridge;
+   *
+   * @see hook_entity_access()
+   * @see hook_ENTITY_TYPE_access()
+   *
+   * @param WorkspaceInterface $workspace
+   * @param string $operation
+   * @param AccountInterface $account
+   *
+   * @return AccessResult
+   */
+  public function workspaceAccess(WorkspaceInterface $workspace, $operation, AccountInterface $account) {
+
+    $operations = [
+      'view' => ['any' => 'view_any_workspace', 'own' => 'view_own_workspace'],
+      'update' => ['any' => 'edit_any_workspace', 'own' => 'edit_own_workspace'],
+      'delete' => ['any' => 'delete_any_workspace', 'own' => 'delete_own_workspace'],
+    ];
+
+    // The default workspace is always viewable, no matter what.
+    $result = AccessResult::allowedIf($operation == 'view' && $workspace->id() == $this->defaultWorkspaceId)
+      // Or if the user has permission to access any workspace at all.
+      ->orIf(AccessResult::allowedIfHasPermission($account, $operations[$operation]['any']))
+      // Or if it's their own workspace, and they have permission to access their own workspace.
+      ->orIf(
+        AccessResult::allowedIf($workspace->getOwnerId() == $account->id())
+          ->andIf(AccessResult::allowedIfHasPermission($account, $operations[$operation]['own']))
+      )
+      ->orIf(AccessResult::allowedIfHasPermission($account, $operation . '_workspace_' . $workspace->id()));
+
+    return $result;
+  }
+
+  /**
+   * Hook bridge;
+   *
+   * @see hook_create_access();
+   * @see hook_ENTITY_TYPE_create_access().
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   * @param array $context
+   * @param $entity_bundle
+   *
+   * @return AccessResult
+   */
+  public function workspaceCreateAccess(AccountInterface $account, array $context, $entity_bundle) {
+    return AccessResult::allowedIfHasPermission($account, 'create_workspace');
+  }
+
+  /**
+   * Returns an array of workspace-specific permissions.
+   *
+   * Note: This approach assumes that a site will have only a small number
+   * of workspace entities, under a dozen. If there are many dozens of
+   * workspaces defined then this approach will have scaling issues.
+   *
+   * @return array
+   *   The workspace permissions.
+   */
+  public function workspacePermissions() {
+    $perms = [];
+
+    foreach ($this->getAllWorkspaces() as $workspace) {
+      $perms += $this->createWorkspaceViewPermission($workspace)
+      + $this->createWorkspaceEditPermission($workspace)
+      + $this->createWorkspaceDeletePermission($workspace)
+      + $this->createWorkspaceBypassPermission($workspace);
+    }
+
+    return $perms;
+  }
+
+  /**
+   * Returns a list of all workspace entities in the system.
+   *
+   * @return WorkspaceInterface[]
+   */
+  protected function getAllWorkspaces() {
+    return $this->entityTypeManager->getStorage('workspace')->loadMultiple();
+  }
+
+  /**
+   * Derives the view permission for a specific workspace.
+   *
+   * @param \Drupal\workspace\Entity\WorkspaceInterface $workspace
+   *   The workspace from which to derive the permission.
+   * @return array
+   *   A single-item array with the permission to define.
+   */
+  protected function createWorkspaceViewPermission(WorkspaceInterface $workspace) {
+    $perms['view_workspace_' . $workspace->id()] = [
+      'title' => $this->t('View the %workspace workspace', ['%workspace' => $workspace->label()]),
+      'description' => $this->t('View the %workspace workspace and content within it', ['%workspace' => $workspace->label()]),
+    ];
+
+    return $perms;
+  }
+
+  /**
+   * Derives the edit permission for a specific workspace.
+   *
+   * @param \Drupal\workspace\Entity\WorkspaceInterface $workspace
+   *   The workspace from which to derive the permission.
+   * @return array
+   *   A single-item array with the permission to define.
+   */
+  protected function createWorkspaceEditPermission(WorkspaceInterface $workspace) {
+    $perms['update_workspace_' . $workspace->id()] = [
+      'title' => $this->t('Edit the %workspace workspace', ['%workspace' => $workspace->label()]),
+      'description' => $this->t('Edit the %workspace workspace itself', ['%workspace' => $workspace->label()]),
+    ];
+
+    return $perms;
+  }
+
+  /**
+   * Derives the delete permission for a specific workspace.
+   *
+   * @param \Drupal\workspace\Entity\WorkspaceInterface $workspace
+   *   The workspace from which to derive the permission.
+   * @return array
+   *   A single-item array with the permission to define.
+   */
+  protected function createWorkspaceDeletePermission(WorkspaceInterface $workspace) {
+    $perms['delete_workspace_' . $workspace->id()] = [
+      'title' => $this->t('Delete the %workspace workspace', ['%workspace' => $workspace->label()]),
+      'description' => $this->t('View the %workspace workspace and all content within it', ['%workspace' => $workspace->label()]),
+    ];
+
+    return $perms;
+  }
+
+  /**
+   * Derives the delete permission for a specific workspace.
+   *
+   * @param \Drupal\workspace\Entity\WorkspaceInterface $workspace
+   *   The workspace from which to derive the permission.
+   * @return array
+   *   A single-item array with the permission to define.
+   */
+  protected function createWorkspaceBypassPermission(WorkspaceInterface $workspace) {
+    $perms['bypass_entity_access_workspace_' . $workspace->id()] = [
+      'title' => $this->t('Bypass content entity access in %workspace workspace', ['%workspace' => $workspace->label()]),
+      'description' => $this->t('Allow all Edit/Update/Delete permissions for all content in the %workspace workspace', ['%workspace' => $workspace->label()]),
+      'restrict access' => TRUE,
+    ];
+
+    return $perms;
+  }
+
+}
diff --git a/core/modules/workspace/src/Form/WorkspaceActivateForm.php b/core/modules/workspace/src/Form/WorkspaceActivateForm.php
new file mode 100644
index 0000000..c2234c2
--- /dev/null
+++ b/core/modules/workspace/src/Form/WorkspaceActivateForm.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\workspace\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\workspace\Entity\WorkspaceInterface;
+
+/**
+ * Handle activation of a workspace on administrative pages.
+ */
+class WorkspaceActivateForm extends WorkspaceActivateFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'activate';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, WorkspaceInterface $workspace = NULL) {
+    $form['workspace_id'] = [
+      '#type' => 'hidden',
+      '#value' => $workspace->id(),
+    ];
+
+    $form['instruction'] = [
+      '#type' => 'markup',
+      '#prefix' => '<p>',
+      '#markup' => $this->t('Would you like to activate the %workspace workspace?', ['%workspace' => $workspace->label()]),
+      '#suffix' => '</p>',
+    ];
+
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => 'Activate',
+    ];
+
+    $form['#title'] = $this->t('Activate workspace %label', array('%label' => $workspace->label()));
+
+    return $form;
+  }
+
+}
diff --git a/core/modules/workspace/src/Form/WorkspaceActivateFormBase.php b/core/modules/workspace/src/Form/WorkspaceActivateFormBase.php
new file mode 100644
index 0000000..da242cb
--- /dev/null
+++ b/core/modules/workspace/src/Form/WorkspaceActivateFormBase.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Drupal\workspace\Form;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\workspace\Entity\WorkspaceInterface;
+use Drupal\workspace\WorkspaceManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * The base class for forms that activate a workspace.
+ *
+ * Use this class as the base for any form that switches the active workspace.
+ * This abstraction handles form validation and submission.
+ */
+abstract class WorkspaceActivateFormBase extends FormBase {
+
+  /**
+   * @var \Drupal\workspace\WorkspaceManagerInterface
+   */
+  protected $workspaceManager;
+
+  /**
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('workspace.manager'),
+      $container->get('entity_type.manager')
+    );
+  }
+
+  public function __construct(WorkspaceManagerInterface $workspace_manager, EntityTypeManagerInterface $entity_type_manager) {
+    $this->workspaceManager = $workspace_manager;
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    $id = $form_state->getValue('workspace_id');
+
+    // Ensure we are given an ID.
+    if (!$id) {
+      $form_state->setErrorByName('workspace_id', 'The workspace ID is required.');
+    }
+
+    // Ensure the workspace by that id exists.
+    /** @var WorkspaceInterface $workspace */
+    $workspace = $this->entityTypeManager->getStorage('workspace')->load($id);
+    if (!$workspace) {
+      $form_state->setErrorByName('workspace_id', 'This workspace no longer exists.');
+    }
+  }
+
+  /**
+   * @inheritDoc
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $id = $form_state->getValue('workspace_id');
+    /** @var WorkspaceInterface $workspace */
+    $workspace = $this->entityTypeManager->getStorage('workspace')->load($id);
+
+    try {
+      $this->workspaceManager->setActiveWorkspace($workspace);
+      $form_state->setRedirect('<front>');
+    }
+    catch(\Exception $e) {
+      watchdog_exception('Workspace', $e);
+      drupal_set_message($e->getMessage(), 'error');
+    }
+  }
+
+}
diff --git a/core/modules/workspace/src/Form/WorkspaceSwitcherForm.php b/core/modules/workspace/src/Form/WorkspaceSwitcherForm.php
new file mode 100644
index 0000000..047c16b
--- /dev/null
+++ b/core/modules/workspace/src/Form/WorkspaceSwitcherForm.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Drupal\workspace\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\workspace\Entity\WorkspaceInterface;
+
+/**
+ * Switcher for to activate a different workspace.
+ *
+ * This is a separate form for each workspace rather than one big form with
+ * many buttons for scaling reasons. For example, this form may show up in a
+ * toolbar. We may want to show just a subset of workspaces to switch to, maybe
+ * access control, etc. This approach keeps that logic out of the switching
+ * process itself.
+ */
+class WorkspaceSwitcherForm extends WorkspaceActivateFormBase {
+
+  /**
+   * Hack to allow us to show this form multiple times on a page.
+   *
+   * @see https://www.drupal.org/node/766146
+   *
+   * @var int
+   */
+  protected static $formCounter = 1;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'workspace_switcher_form_' . static::$formCounter++;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, WorkspaceInterface $workspace = NULL) {
+    // @todo this form is identical to WorkspaceActivateForm except for this method; can we consolidate forms?
+    $form['workspace_id'] = [
+      '#type' => 'hidden',
+      '#value' => $workspace->id(),
+    ];
+
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => $workspace->label(),
+    ];
+
+    $active_workspace = $this->workspaceManager->getActiveWorkspace();
+    if ($active_workspace->id() === $workspace->id()) {
+      $form['submit']['#attributes']['class'] = ['is-active'];
+    }
+
+    return $form;
+  }
+
+}
diff --git a/core/modules/workspace/src/ParamConverter/EntityRevisionConverter.php b/core/modules/workspace/src/ParamConverter/EntityRevisionConverter.php
new file mode 100644
index 0000000..b118a0f
--- /dev/null
+++ b/core/modules/workspace/src/ParamConverter/EntityRevisionConverter.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Drupal\workspace\ParamConverter;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\ParamConverter\ParamConverterInterface;
+use Drupal\Core\ParamConverter\ParamNotConvertedException;
+use Drupal\Core\TypedData\TranslatableInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Parameter converter for upcasting entity revision IDs to full objects.
+ */
+class EntityRevisionConverter implements ParamConverterInterface {
+
+  /**
+   * Entity manager which performs the upcasting in the end.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a new EntityRevisionConverter.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(EntityManagerInterface $entity_manager) {
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert($value, $definition, $name, array $defaults) {
+    $entity_type_id = $this->getEntityTypeFromDefaults($definition, $name, $defaults);
+    if ($storage = $this->entityManager->getStorage($entity_type_id)) {
+      $entity = $storage->loadRevision($value);
+      // If the entity type is translatable, ensure we return the proper
+      // translation object for the current context.
+      if ($entity instanceof EntityInterface && $entity instanceof TranslatableInterface) {
+        $entity = $this->entityManager->getTranslationFromContext($entity, NULL, array('operation' => 'entity_upcast'));
+      }
+      return $entity;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies($definition, $name, Route $route) {
+    if (!empty($definition['type']) && strpos($definition['type'], 'entity_revision:') === 0) {
+      $entity_type_id = substr($definition['type'], strlen('entity:'));
+      if (strpos($definition['type'], '{') !== FALSE) {
+        $entity_type_slug = substr($entity_type_id, 1, -1);
+        return $name != $entity_type_slug && in_array($entity_type_slug, $route->compile()->getVariables(), TRUE);
+      }
+      return $this->entityManager->hasDefinition($entity_type_id);
+    }
+    return FALSE;
+  }
+
+  /**
+   * Determines the entity type ID given a route definition and route defaults.
+   *
+   * @param mixed $definition
+   *   The parameter definition provided in the route options.
+   * @param string $name
+   *   The name of the parameter.
+   * @param array $defaults
+   *   The route defaults array.
+   *
+   * @throws \Drupal\Core\ParamConverter\ParamNotConvertedException
+   *   Thrown when the dynamic entity type is not found in the route defaults.
+   *
+   * @return string
+   *   The entity type ID.
+   */
+  protected function getEntityTypeFromDefaults($definition, $name, array $defaults) {
+    $entity_type_id = substr($definition['type'], strlen('entity_revision:'));
+
+    // If the entity type is dynamic, it will be pulled from the route defaults.
+    if (strpos($entity_type_id, '{') === 0) {
+      $entity_type_slug = substr($entity_type_id, 1, -1);
+      if (!isset($defaults[$entity_type_slug])) {
+        throw new ParamNotConvertedException(sprintf('The "%s" parameter was not converted because the "%s" parameter is missing', $name, $entity_type_slug));
+      }
+      $entity_type_id = $defaults[$entity_type_slug];
+    }
+    return $entity_type_id;
+  }
+
+}
\ No newline at end of file
diff --git a/core/modules/workspace/src/Plugin/Block/WorkspaceBlock.php b/core/modules/workspace/src/Plugin/Block/WorkspaceBlock.php
new file mode 100644
index 0000000..47b7f6e
--- /dev/null
+++ b/core/modules/workspace/src/Plugin/Block/WorkspaceBlock.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Drupal\workspace\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\workspace\WorkspaceManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @Block(
+ *   id = "workspace_switcher_block",
+ *   admin_label = @Translation("Workspace switcher"),
+ *   category = @Translation("Workspace"),
+ * )
+ */
+class WorkspaceBlock extends BlockBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * @var \Drupal\workspace\WorkspaceManagerInterface
+   */
+  protected $workspaceManager;
+
+  /**
+   * @param array $configuration
+   * @param string $plugin_id
+   * @param mixed $plugin_definition
+   * @param \Drupal\workspace\WorkspaceManagerInterface $workspace_manager
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, WorkspaceManagerInterface $workspace_manager, EntityTypeManagerInterface $entity_type_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->workspaceManager = $workspace_manager;
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('workspace.manager'),
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    $build = [
+      // @todo the block depending on the toolbar is obscure; find a better way to generate this form
+      '#pre_render' => ['workspace.toolbar:preRenderWorkspaceSwitcherForms'],
+      // This wil get filled in via pre-render.
+      'workspace_forms' => [],
+      '#attached' => [
+        'library' => [
+          'workspace/drupal.workspace.switcher',
+        ],
+      ],
+      '#cache' => [
+        'contexts' => $this->entityTypeManager->getDefinition('workspace')->getListCacheContexts(),
+        'tags' => $this->entityTypeManager->getDefinition('workspace')->getListCacheTags(),
+      ],
+    ];
+    return $build;
+  }
+
+}
diff --git a/core/modules/workspace/src/Plugin/Field/WorkspaceFieldItemList.php b/core/modules/workspace/src/Plugin/Field/WorkspaceFieldItemList.php
new file mode 100644
index 0000000..d8862a3
--- /dev/null
+++ b/core/modules/workspace/src/Plugin/Field/WorkspaceFieldItemList.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Drupal\workspace\Plugin\Field;
+
+use Drupal\Core\Field\EntityReferenceFieldItemList;
+
+/**
+ * A computed field that provides a content entity's workspace.
+ *
+ * It links content entities to a workspace configuration entity via a
+ * workspace content entity.
+ */
+class WorkspaceFieldItemList extends EntityReferenceFieldItemList {
+
+  /**
+   * Gets the workspace entity linked to a content entity revision.
+   *
+   * @return \Drupal\workspace\Entity\WorkspaceInterface|null
+   *   The workspace configuration entity linked to a content entity
+   *   revision.
+   */
+  protected function getWorkspace() {
+    $entity = $this->getEntity();
+
+    if (!$entity->getEntityType()->isRevisionable()) {
+      return NULL;
+    }
+
+    if ($entity->id() && $entity->getRevisionId()) {
+      $revisions = \Drupal::service('entity.query')->get('content_workspace')
+        ->condition('content_entity_type_id', $entity->getEntityTypeId())
+        ->condition('content_entity_id', $entity->id())
+        ->condition('content_entity_revision_id', $entity->getRevisionId())
+        ->allRevisions()
+        ->sort('revision_id', 'DESC')
+        ->execute();
+
+      if ($revision_to_load = key($revisions)) {
+        /** @var \Drupal\workspace\Entity\ContentWorkspaceInterface $content_workspace */
+        $content_workspace = \Drupal::entityTypeManager()
+          ->getStorage('content_workspace')
+          ->loadRevision($revision_to_load);
+
+        // Return the correct translation.
+        $langcode = $entity->language()->getId();
+        if (!$content_workspace->hasTranslation($langcode)) {
+          $content_workspace->addTranslation($langcode);
+        }
+        if ($content_workspace->language()->getId() !== $langcode) {
+          $content_workspace = $content_workspace->getTranslation($langcode);
+        }
+
+        return $content_workspace->get('workspace')->entity;
+      }
+    }
+    return \Drupal::getContainer()->getParameter('workspace.default');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($index) {
+    if ($index !== 0) {
+      throw new \InvalidArgumentException('An entity can not have multiple workspaces at the same time.');
+    }
+    $this->computeWorkspaceFieldItemList();
+    return isset($this->list[$index]) ? $this->list[$index] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIterator() {
+    $this->computeWorkspaceFieldItemList();
+    return parent::getIterator();
+  }
+
+  /**
+   * Recalculate the workspace field item list.
+   */
+  protected function computeWorkspaceFieldItemList() {
+    // Compute the value of the workspace.
+    $index = 0;
+    if (!isset($this->list[$index]) || $this->list[$index]->isEmpty()) {
+      $workspace = $this->getWorkspace();
+      // Do not store NULL values in the static cache.
+      if ($workspace) {
+        $this->list[$index] = $this->createItem($index, ['entity' => $workspace]);
+      }
+    }
+  }
+
+}
diff --git a/core/modules/workspace/src/SessionWorkspaceNegotiator.php b/core/modules/workspace/src/SessionWorkspaceNegotiator.php
new file mode 100644
index 0000000..5a150c5
--- /dev/null
+++ b/core/modules/workspace/src/SessionWorkspaceNegotiator.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\workspace;
+
+use Drupal\workspace\Entity\WorkspaceInterface;
+use Drupal\user\PrivateTempStoreFactory;
+use Symfony\Component\HttpFoundation\Request;
+
+class SessionWorkspaceNegotiator extends WorkspaceNegotiatorBase {
+
+  /**
+   * @var \Drupal\user\PrivateTempStore
+   */
+  protected $tempstore;
+
+  /**
+   * Constructor.
+   *
+   * @param \Drupal\user\PrivateTempStoreFactory $tempstore_factory
+   */
+  public function __construct(PrivateTempStoreFactory $tempstore_factory) {
+    $this->tempstore = $tempstore_factory->get('workspace.negotiator.session');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies(Request $request) {
+    // This negotiator only applies if the current user is authenticated,
+    // i.e. a session exists.
+    return $this->currentUser->isAuthenticated();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWorkspaceId(Request $request) {
+    $workspace_id = $this->tempstore->get('active_workspace_id');
+    return $workspace_id ?: $this->container->getParameter('workspace.default');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function persist(WorkspaceInterface $workspace) {
+    $this->tempstore->set('active_workspace_id', $workspace->id());
+    return TRUE;
+  }
+
+}
diff --git a/core/modules/workspace/src/Toolbar.php b/core/modules/workspace/src/Toolbar.php
new file mode 100644
index 0000000..86197da
--- /dev/null
+++ b/core/modules/workspace/src/Toolbar.php
@@ -0,0 +1,148 @@
+<?php
+
+
+namespace Drupal\workspace;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\FormBuilderInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Url;
+use Drupal\workspace\Entity\WorkspaceInterface;
+use Drupal\workspace\Form\WorkspaceSwitcherForm;
+
+/**
+ * Service for hooks and utilities related to Toolbar integration.
+ */
+class Toolbar {
+  use StringTranslationTrait;
+
+  /**
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * @var \Drupal\workspace\\WorkspaceManagerInterface
+   */
+  protected $workspaceManager;
+
+  /**
+   * @var \Drupal\Core\Form\FormBuilderInterface
+   */
+  protected $formBuilder;
+
+  /**
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * Constructs a new Toolbar.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager service.
+   * @param \Drupal\workspace\WorkspaceManagerInterface $workspace_manager
+   *   The workspace manager service.
+   * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
+   *   The form builder service.
+   * @param AccountInterface $current_user
+   *   The current user service.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, FormBuilderInterface $form_builder, AccountInterface $current_user) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->workspaceManager = $workspace_manager;
+    $this->formBuilder = $form_builder;
+    $this->currentUser = $current_user;
+  }
+
+  /**
+   * Hook bridge;  Responds to hook_toolbar().
+   *
+   * @see hook_toolbar().
+   */
+  public function toolbar() {
+    $items = [];
+
+    $active = $this->workspaceManager->getActiveWorkspace();
+
+    $items['workspace_switcher'] = [
+      // Include the toolbar_tab_wrapper to style the link like a toolbar tab.
+      // Exclude the theme wrapper if custom styling is desired.
+      '#type' => 'toolbar_item',
+      '#weight' => 125,
+      '#wrapper_attributes' => [
+        'class' => ['workspace-toolbar-tab'],
+      ],
+      '#attached' => [
+        'library' => [
+          'workspace/drupal.workspace.toolbar',
+        ],
+      ],
+    ];
+
+    $items['workspace_switcher']['tab'] = [
+      '#type' => 'link',
+      '#title' => $this->t('@active', ['@active' => $active->label()]),
+      '#url' => Url::fromRoute('entity.workspace.collection'),
+      '#attributes' => [
+        'title' => $this->t('Switch workspaces'),
+        'class' => ['toolbar-icon', 'toolbar-icon-workspace'],
+      ],
+    ];
+
+    $create_link = [
+      '#type' => 'link',
+      '#title' => t('Add workspace'),
+      '#url' => Url::fromRoute('entity.workspace.add'),
+      '#options' => array('attributes' => array('class' => array('add-workspace'))),
+    ];
+
+    $items['workspace_switcher']['tray'] = [
+      '#heading' => $this->t('Switch to workspace'),
+      '#pre_render' => ['workspace.toolbar:preRenderWorkspaceSwitcherForms'],
+      // This wil get filled in via pre-render.
+      'workspace_forms' => [],
+      'create_link' => $create_link,
+      '#cache' => [
+        'contexts' => $this->entityTypeManager->getDefinition('workspace')->getListCacheContexts(),
+        'tags' => $this->entityTypeManager->getDefinition('workspace')->getListCacheTags(),
+      ],
+      '#attributes' => [
+        'class' => ['toolbar-menu'],
+      ],
+    ];
+
+    return $items;
+  }
+
+  /**
+   * Prerender callback; Adds the workspace switcher forms to the render array.
+   *
+   * @param array $element
+   *
+   * @return array
+   *   The modified $element.
+   */
+  public function preRenderWorkspaceSwitcherForms(array $element) {
+    foreach ($this->allWorkspaces() as $workspace) {
+      $element['workspace_forms']['workspace_' . $workspace->getMachineName()] = $this->formBuilder->getForm(WorkspaceSwitcherForm::class, $workspace);
+    }
+
+    return $element;
+  }
+
+  /**
+   * Returns a list of all defined and accessible workspaces.
+   *
+   * Note: This assumes that the total number of workspaces on the site is
+   * very small.  If it's actually large this method will have memory issues.
+   *
+   * @return WorkspaceInterface[]
+   */
+  protected function allWorkspaces() {
+    return array_filter($this->entityTypeManager->getStorage('workspace')->loadMultiple(), function(WorkspaceInterface $workspace) {
+      return $workspace->access('view', $this->currentUser);
+    });
+  }
+}
diff --git a/core/modules/workspace/src/WorkspaceAccessException.php b/core/modules/workspace/src/WorkspaceAccessException.php
new file mode 100644
index 0000000..55b58e6
--- /dev/null
+++ b/core/modules/workspace/src/WorkspaceAccessException.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Drupal\workspace;
+
+
+use Drupal\Core\Access\AccessException;
+
+/**
+ * Exception thrown when trying to switch to an inaccessible workspace.
+ */
+class WorkspaceAccessException extends AccessException {
+
+}
diff --git a/core/modules/workspace/src/WorkspaceListBuilder.php b/core/modules/workspace/src/WorkspaceListBuilder.php
new file mode 100644
index 0000000..d1b31fb
--- /dev/null
+++ b/core/modules/workspace/src/WorkspaceListBuilder.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Drupal\workspace;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityListBuilder;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\workspace\Entity\WorkspaceInterface;
+use Drupal\workspace\Entity\WorkspaceTypeInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a class to build a listing of workspace entities.
+ *
+ * @see \Drupal\workspace\Entity\Workspace
+ */
+class WorkspaceListBuilder extends EntityListBuilder {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('entity.manager')->getStorage($entity_type->id()),
+      $container->get('workspace.manager')
+    );
+  }
+
+  /**
+   * Constructs a new EntityListBuilder object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type definition.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
+   *   The entity storage class.
+   * @param \Drupal\workspace\Workspace\WorkspaceManagerInterface $workspace_manager
+   */
+  public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, WorkspaceManagerInterface $workspace_manager) {
+    parent::__construct($entity_type, $storage);
+    $this->workspaceManager = $workspace_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildHeader() {
+    $header['label'] = t('Workspace');
+    $header['uid'] = t('Owner');
+    $header['type'] = t('Type');
+    $header['status'] = t('Status');
+
+    return $header + parent::buildHeader();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRow(EntityInterface $entity) {
+    /** @var WorkspaceInterface $entity */
+    $row['label'] = $entity->label() . ' (' . $entity->getMachineName() . ')';
+    $row['owner'] = $entity->getOwner()->getDisplayname();
+    /** @var WorkspaceTypeInterface $type */
+    $type = $entity->get('type')->first()->entity;
+    $row['type'] = $type ? $type->label() : '';
+    $active_workspace = $this->workspaceManager->getActiveWorkspace()->id();
+    $row['status'] = $active_workspace == $entity->id() ? 'Active' : 'Inactive';
+    return $row + parent::buildRow($entity);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultOperations(EntityInterface $entity) {
+    /** @var WorkspaceInterface $entity */
+    $operations = parent::getDefaultOperations($entity);
+    if (isset($operations['edit'])) {
+      $operations['edit']['query']['destination'] = $entity->url('collection');
+    }
+
+    $active_workspace = $this->workspaceManager->getActiveWorkspace()->id();
+    if ($entity->id() != $active_workspace) {
+      $operations['activate'] = array(
+        'title' => $this->t('Set Active'),
+        'weight' => 20,
+        'url' => $entity->urlInfo('activate-form', ['query' => ['destination' => $entity->url('collection')]]),
+      );
+    }
+
+    return $operations;
+  }
+
+}
\ No newline at end of file
diff --git a/core/modules/workspace/src/WorkspaceManager.php b/core/modules/workspace/src/WorkspaceManager.php
new file mode 100644
index 0000000..3e51c8e
--- /dev/null
+++ b/core/modules/workspace/src/WorkspaceManager.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Drupal\workspace;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\workspace\Entity\ContentWorkspace;
+use Drupal\workspace\Entity\ContentWorkspaceInterface;
+use Drupal\workspace\Entity\WorkspaceInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+class WorkspaceManager implements WorkspaceManagerInterface {
+  use StringTranslationTrait;
+
+  /**
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * @var \Drupal\Core\Session\AccountProxyInterface
+   */
+  protected $currentUser;
+
+  /**
+   * @var array
+   */
+  protected $negotiators = array();
+
+  /**
+   * @var array
+   */
+  protected $sortedNegotiators;
+
+  /**
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
+  /**
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
+   * @param \Psr\Log\LoggerInterface $logger
+   */
+  public function __construct(RequestStack $request_stack, EntityManagerInterface $entity_manager, AccountProxyInterface $current_user, LoggerInterface $logger = NULL) {
+    $this->requestStack = $request_stack;
+    $this->entityManager = $entity_manager;
+    $this->currentUser = $current_user;
+    $this->logger = $logger ?: new NullLogger();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addNegotiator(WorkspaceNegotiatorInterface $negotiator, $priority) {
+    $this->negotiators[$priority][] = $negotiator;
+    $this->sortedNegotiators = NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load($workspace_id) {
+    return $this->entityManager->getStorage('workspace')->load($workspace_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadMultiple(array $workspace_ids = NULL) {
+    return $this->entityManager->getStorage('workspace')->loadMultiple($workspace_ids);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadByMachineName($machine_name) {
+    $workspaces = $this->entityManager->getStorage('workspace')->loadByProperties(['machine_name' => $machine_name]);
+    return current($workspaces);
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @todo {@link https://www.drupal.org/node/2600382 Access check.}
+   */
+  public function getActiveWorkspace() {
+    $request = $this->requestStack->getCurrentRequest();
+    foreach ($this->getSortedNegotiators() as $negotiator) {
+      if ($negotiator->applies($request)) {
+        if ($workspace_id = $negotiator->getWorkspaceId($request)) {
+          if ($workspace = $this->load($workspace_id)) {
+            return $workspace;
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setActiveWorkspace(WorkspaceInterface $workspace) {
+    $default_workspace_id = \Drupal::getContainer()->getParameter('workspace.default');
+    // If the current user doesn't have access to view the workspace, they
+    // shouldn't be allowed to switch to it.
+    // @todo Could this be handled better?
+    if (!$workspace->access('view') && ($workspace->id() != $default_workspace_id)) {
+      $this->logger->error('Denied access to view workspace {workspace}', ['workspace' => $workspace->label()]);
+      throw new WorkspaceAccessException('The user does not have permission to view that workspace.');
+    }
+
+    // Set the workspace on the proper negotiator.
+    $request = $this->requestStack->getCurrentRequest();
+    foreach ($this->getSortedNegotiators() as $negotiator) {
+      if ($negotiator->applies($request)) {
+        $negotiator->persist($workspace);
+        break;
+      }
+    }
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function updateOrCreateFromEntity(EntityInterface $entity) {
+    if (!$entity->getEntityType()->isRevisionable() || in_array($entity->getEntityTypeId(), ['content_workspace', 'workspace'])) {
+      return;
+    }
+
+    $content_workspaces = $this->entityManager
+      ->getStorage('content_workspace')
+      ->loadByProperties([
+      'content_entity_type_id' => $entity->getEntityTypeId(),
+      'content_entity_id' => $entity->id(),
+    ]);
+
+    $content_workspace = reset($content_workspaces);
+    if (!$content_workspace instanceof ContentWorkspaceInterface) {
+      $content_workspace = ContentWorkspace::create([
+        'content_entity_type_id' => $entity->getEntityTypeId(),
+        'content_entity_id' => $entity->id()
+      ]);
+    }
+    else {
+      $content_workspace->setNewRevision(TRUE);
+    }
+
+    $content_workspace->set('content_entity_revision_id', $entity->getRevisionId());
+    $content_workspace->set('workspace', $this->getActiveWorkspace());
+    ContentWorkspace::updateOrCreateFromEntity($content_workspace);
+
+  }
+
+  /**
+   * @return \Drupal\workspace\WorkspaceNegotiatorInterface[]
+   */
+  protected function getSortedNegotiators() {
+    if (!isset($this->sortedNegotiators)) {
+      // Sort the negotiators according to priority.
+      krsort($this->negotiators);
+      // Merge nested negotiators from $this->negotiators into
+      // $this->sortedNegotiators.
+      $this->sortedNegotiators = array();
+      foreach ($this->negotiators as $builders) {
+        $this->sortedNegotiators = array_merge($this->sortedNegotiators, $builders);
+      }
+    }
+    return $this->sortedNegotiators;
+  }
+
+}
diff --git a/core/modules/workspace/src/WorkspaceManagerInterface.php b/core/modules/workspace/src/WorkspaceManagerInterface.php
new file mode 100644
index 0000000..56d4da5
--- /dev/null
+++ b/core/modules/workspace/src/WorkspaceManagerInterface.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\workspace;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\workspace\Entity\WorkspaceInterface;
+
+interface WorkspaceManagerInterface {
+
+  /**
+   * @param \Drupal\workspace\WorkspaceNegotiatorInterface $negotiator
+   * @param int $priority
+   */
+  public function addNegotiator(WorkspaceNegotiatorInterface $negotiator, $priority);
+
+  /**
+   * @param string $workspace_id
+   */
+  public function load($workspace_id);
+
+  /**
+   * @param array|null $workspace_ids
+   */
+  public function loadMultiple(array $workspace_ids = NULL);
+
+  /**
+   * @param string $machine_name
+   */
+  public function loadByMachineName($machine_name);
+
+  /**
+   * @return \Drupal\workspace\Entity\WorkspaceInterface
+   */
+  public function getActiveWorkspace();
+
+  /**
+   * Sets the active workspace for the site/session.
+   *
+   * @param \Drupal\workspace\Entity\WorkspaceInterface $workspace
+   *   The workspace to set as active.
+   *
+   * @return \Drupal\workspace\WorkspaceManagerInterface
+   *
+   * @throws WorkspaceAccessException
+   */
+  public function setActiveWorkspace(WorkspaceInterface $workspace);
+
+  /**
+   * Update or create a ContentWorkspace entity from another entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to update or create from.
+   */
+  public function updateOrCreateFromEntity(EntityInterface $entity);
+}
diff --git a/core/modules/workspace/src/WorkspaceNegotiatorBase.php b/core/modules/workspace/src/WorkspaceNegotiatorBase.php
new file mode 100644
index 0000000..9ab65bb
--- /dev/null
+++ b/core/modules/workspace/src/WorkspaceNegotiatorBase.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\workspace;
+
+use Drupal\Core\Session\AccountInterface;
+use Drupal\workspace\Entity\WorkspaceInterface;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerAwareTrait;
+
+abstract class WorkspaceNegotiatorBase implements WorkspaceNegotiatorInterface, ContainerAwareInterface {
+
+  use ContainerAwareTrait;
+
+  /**
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * @var \Drupal\workspace\WorkspaceManagerInterface
+   */
+  protected $workspaceManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCurrentUser(AccountInterface $current_user) {
+    $this->currentUser = $current_user;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setWorkspaceManager(WorkspaceManagerInterface $entity_manager) {
+    $this->workspaceManager = $entity_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function persist(WorkspaceInterface $workspace) {
+    return TRUE;
+  }
+
+}
diff --git a/core/modules/workspace/src/WorkspaceNegotiatorInterface.php b/core/modules/workspace/src/WorkspaceNegotiatorInterface.php
new file mode 100644
index 0000000..f9ba1fb
--- /dev/null
+++ b/core/modules/workspace/src/WorkspaceNegotiatorInterface.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\workspace;
+
+use Drupal\Core\Session\AccountInterface;
+use Drupal\workspace\Entity\WorkspaceInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+interface WorkspaceNegotiatorInterface {
+
+  /**
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   */
+  public function setCurrentUser(AccountInterface $current_user);
+
+  /**
+   * @param \Drupal\workspace\WorkspaceManagerInterface $entity_manager
+   */
+  public function setWorkspaceManager(WorkspaceManagerInterface $entity_manager);
+
+  /**
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   * @return boolean
+   */
+  public function applies(Request $request);
+
+  /**
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   * @return string
+   */
+  public function getWorkspaceId(Request $request);
+
+  /**
+   * @param \Drupal\workspace\Entity\WorkspaceInterface $workspace
+   * @return boolean
+   */
+  public function persist(WorkspaceInterface $workspace);
+
+}
diff --git a/core/modules/workspace/src/WorkspacePointerInterface.php b/core/modules/workspace/src/WorkspacePointerInterface.php
new file mode 100644
index 0000000..5b95d74
--- /dev/null
+++ b/core/modules/workspace/src/WorkspacePointerInterface.php
@@ -0,0 +1,98 @@
+<?php
+
+
+namespace Drupal\workspace;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\workspace\Entity\WorkspaceInterface;
+
+/**
+ * Provides an interface for defining Workspace pointer entities.
+ *
+ * @ingroup workspace
+ */
+interface WorkspacePointerInterface extends ContentEntityInterface, EntityChangedInterface {
+  // Add get/set methods for your configuration properties here.
+
+  /**
+   * Gets the Workspace pointer name.
+   *
+   * @return string
+   *   Name of the Workspace pointer.
+   */
+  public function getName();
+
+  /**
+   * Sets the Workspace pointer name.
+   *
+   * @param string $name
+   *   The Workspace pointer name.
+   *
+   * @return \Drupal\workspace\WorkspacePointerInterface
+   *   The called Workspace pointer entity.
+   */
+  public function setName($name);
+
+  /**
+   * Gets the Workspace pointer creation timestamp.
+   *
+   * @return int
+   *   Creation timestamp of the Workspace pointer.
+   */
+  public function getCreatedTime();
+
+  /**
+   * Sets the Workspace pointer creation timestamp.
+   *
+   * @param int $timestamp
+   *   The Workspace pointer creation timestamp.
+   *
+   * @return \Drupal\workspace\WorkspacePointerInterface
+   *   The called Workspace pointer entity.
+   */
+  public function setCreatedTime($timestamp);
+
+  /**
+   * Sets the Workspace this pointer references.
+   *
+   * @param \Drupal\workspace\Entity\WorkspaceInterface $workspace
+   *
+   * @return $this
+   *   The called Workspace pointer entity.
+   */
+  public function setWorkspace(WorkspaceInterface $workspace);
+
+  /**
+   * Returns the referenced workspace entity.
+   *
+   * @return \Drupal\workspace\Entity\WorkspaceInterface
+   *   The workspace entity.
+   */
+  public function getWorkspace();
+
+  /**
+   * Returns the referenced workspace ID.
+   *
+   * @return int|null
+   *   The workspace ID, or NULL in case the workspace ID field has not been
+   *   set on the entity.
+   */
+  public function getWorkspaceId();
+
+  /**
+   * Sets the referenced workspace ID.
+   *
+   * @param int $workspace_id
+   *   The workspace id.
+   *
+   * @return $this
+   */
+  public function setWorkspaceId($uid);
+
+  /**
+   * @param \Drupal\workspace\WorkspacePointerInterface $target
+   * @return string
+   */
+  public function generateReplicationId(WorkspacePointerInterface $target);
+}
diff --git a/core/modules/workspace/src/WorkspaceTypeListBuilder.php b/core/modules/workspace/src/WorkspaceTypeListBuilder.php
new file mode 100644
index 0000000..5da8dd9
--- /dev/null
+++ b/core/modules/workspace/src/WorkspaceTypeListBuilder.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\workspace;
+
+use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
+use Drupal\Core\Entity\EntityInterface;
+/**
+ * Provides a listing of Workspace type entities.
+ */
+class WorkspaceTypeListBuilder extends ConfigEntityListBuilder {
+  /**
+   * {@inheritdoc}
+   */
+  public function buildHeader() {
+    $header['label'] = $this->t('Workspace type');
+    $header['id'] = $this->t('Machine name');
+    return $header + parent::buildHeader();
+  }
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRow(EntityInterface $entity) {
+    $row['label'] = $entity->label();
+    $row['id'] = $entity->id();
+
+    return $row + parent::buildRow($entity);
+  }
+}
\ No newline at end of file
diff --git a/core/modules/workspace/templates/workspace-add-list.html.twig b/core/modules/workspace/templates/workspace-add-list.html.twig
new file mode 100644
index 0000000..6a0ff29
--- /dev/null
+++ b/core/modules/workspace/templates/workspace-add-list.html.twig
@@ -0,0 +1,20 @@
+{#
+/**
+ * @file
+ * Default theme implementation to present a list of custom Workspace types.
+ *
+ * Available variables:
+ * - types: A collection of all the available custom Workspace types.
+ *   Each Workspace type contains the following:
+ *   - link: A link to add a Workspace of this type.
+ *
+ * @ingroup themeable
+ */
+#}
+<ul class="admin-list">
+    {% for type in types %}
+        <li class="clearfix">
+            <a href="{{ type.url }}"><span class="label">{{ type.label }}</span></a>
+        </li>
+    {% endfor %}
+</ul>
diff --git a/core/modules/workspace/templates/workspace-rev.html.twig b/core/modules/workspace/templates/workspace-rev.html.twig
new file mode 100644
index 0000000..c273518
--- /dev/null
+++ b/core/modules/workspace/templates/workspace-rev.html.twig
@@ -0,0 +1,29 @@
+{#
+/**
+ * @file
+ * Default theme implementation for revisions.
+ *
+ * Available variables:
+ * - entity_type_id: The entity type ID.
+ * - entity_id: The entity ID.
+ * - revision_id: The entity revision ID.
+ * - uuid: The entity UUID.
+ * - rev: The revision token.
+ * - status: The status of the revision.
+ * - open_rev: Whether the revision is open or not.
+ * - conflict: Whether the revision is a conflict or not.
+ * - default: Whether the revision is the defaukt one or not.
+ *
+ * - title: The revision token linked to a view of the revision.
+ *
+ * @see template_preprocess_workspace_rev()
+ *
+ * @ingroup themeable
+ */
+#}
+
+<div class="rev{{ default ? ' color-success' }}{{ conflict ? ' color-error' }}">
+    <h3 class="rev__title">{{ title }}</h3>
+    <hr>
+    {% trans %}Status: {{ status }}{% endtrans %}
+</div>
\ No newline at end of file
diff --git a/core/modules/workspace/tests/src/Functional/WorkspaceBypassTest.php b/core/modules/workspace/tests/src/Functional/WorkspaceBypassTest.php
new file mode 100644
index 0000000..781aa32
--- /dev/null
+++ b/core/modules/workspace/tests/src/Functional/WorkspaceBypassTest.php
@@ -0,0 +1,155 @@
+<?php
+
+namespace Drupal\Tests\workspace\Functional;
+
+use Drupal\simpletest\BlockCreationTrait;
+use \Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests access bypass permission controls on workspaces.
+ *
+ * @group workspace
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ *
+ */
+class WorkspaceBypassTest extends BrowserTestBase {
+  use WorkspaceTestUtilities;
+  use BlockCreationTrait {
+    placeBlock as drupalPlaceBlock;
+  }
+
+  public static $modules = ['node', 'user', 'block', 'workspace', 'workspace'];
+
+  /**
+   * Verifies that a user can edit anything in a workspace with a specific perm.
+   */
+  public function testBypassSpecificWorkspace() {
+    $permissions = [
+      'create_workspace',
+      'edit_own_workspace',
+      'view_own_workspace',
+    ];
+
+    $this->createNodeType('Test', 'test');
+    $this->setupWorkspaceSwitcherBlock();
+
+    $ditka = $this->drupalCreateUser(array_merge($permissions, ['create test content']));
+
+    // Login as a limited-access user and create a workspace.
+    $this->drupalLogin($ditka);
+
+    $vanilla_node = $this->createNodeThroughUI('Vanilla node', 'test');
+    $this->assertEquals(1, $vanilla_node->workspace->target_id);
+
+    $bears = $this->createWorkspaceThroughUI('Bears', 'bears');
+    $this->switchToWorkspace($bears);
+
+    // Now create a node in the Bears workspace, as the owner of that workspace.
+    $ditka_bears_node = $this->createNodeThroughUI('Ditka Bears node', 'test');
+    $this->assertEquals($bears->id(), $ditka_bears_node->workspace->entity->id());
+    $ditka_bears_node_id = $ditka_bears_node->id();
+
+    // Create a new user that should be able to edit anything in the Bears workspace.
+    $lombardi = $this->drupalCreateUser(array_merge($permissions, ['view_workspace_' . $bears->id(), 'bypass_entity_access_workspace_' . $bears->id()]));
+    $this->drupalLogin($lombardi);
+    $this->switchToWorkspace($bears);
+
+    // Because Lombardi has the bypass permission, he should be able to
+    // create and edit any node.
+
+    $this->drupalGet('/node/' . $ditka_bears_node_id . '/edit');
+    $session = $this->getSession();
+    $this->assertEquals(200, $session->getStatusCode());
+
+    $bears_vanilla_node = $this->getOneEntityByLabel('node', 'Vanilla node');
+    $this->drupalGet('/node/' . $bears_vanilla_node->id() . '/edit');
+    $session = $this->getSession();
+    $this->assertEquals(200, $session->getStatusCode());
+
+    $lombardi_bears_node = $this->createNodeThroughUI('Lombardi Bears node', 'test');
+    $this->assertEquals($bears->id(), $lombardi_bears_node->workspace->entity->id());
+    $lombardi_bears_node_id = $lombardi_bears_node->id();
+
+    $this->drupalLogin($ditka);
+    $this->switchToWorkspace($bears);
+
+    $this->drupalGet('/node/' . $lombardi_bears_node_id . '/edit');
+    $session = $this->getSession();
+    $this->assertEquals(403, $session->getStatusCode());
+
+    // Create a new user that should NOT be able to edit anything in the Bears workspace.
+    $belichick = $this->drupalCreateUser(array_merge($permissions, ['view_workspace_' . $bears->id()]));
+    $this->drupalLogin($belichick);
+    $this->switchToWorkspace($bears);
+
+    $this->drupalGet('/node/' . $ditka_bears_node_id . '/edit');
+    $session = $this->getSession();
+    $this->assertEquals(403, $session->getStatusCode());
+
+    $this->drupalGet('/node/' . $bears_vanilla_node->id() . '/edit');
+    $session = $this->getSession();
+    $this->assertEquals(403, $session->getStatusCode());
+
+  }
+
+  /**
+   * Verifies that a user can edit anything in a workspace they own.
+   */
+  public function testBypassOwnWorkspace() {
+    $permissions = [
+      'create_workspace',
+      'edit_own_workspace',
+      'view_own_workspace',
+      'bypass_entity_access_own_workspace',
+    ];
+
+    $this->createNodeType('Test', 'test');
+    $this->setupWorkspaceSwitcherBlock();
+
+    $ditka = $this->drupalCreateUser(array_merge($permissions, ['create test content']));
+
+    // Login as a limited-access user and create a workspace.
+    $this->drupalLogin($ditka);
+
+    $vanilla_node = $this->createNodeThroughUI('Vanilla node', 'test');
+    $this->assertEquals(1, $vanilla_node->workspace->target_id);
+
+    $bears = $this->createWorkspaceThroughUI('Bears', 'bears');
+    $this->switchToWorkspace($bears);
+
+    // Now create a node in the Bears workspace, as the owner of that workspace.
+    $ditka_bears_node = $this->createNodeThroughUI('Ditka Bears node', 'test');
+    $this->assertEquals($bears->id(), $ditka_bears_node->workspace->entity->id());
+    $ditka_bears_node_id = $ditka_bears_node->id();
+
+    // Editing both nodes should be possible.
+
+    $this->drupalGet('/node/' . $ditka_bears_node_id . '/edit');
+    $session = $this->getSession();
+    $this->assertEquals(200, $session->getStatusCode());
+
+    $bears_vanilla_node = $this->getOneEntityByLabel('node', 'Vanilla node');
+    $this->drupalGet('/node/' . $bears_vanilla_node->id() . '/edit');
+    $session = $this->getSession();
+    $this->assertEquals(200, $session->getStatusCode());
+
+    // Create a new user that should be able to edit anything in the Bears workspace.
+    $lombardi = $this->drupalCreateUser(array_merge($permissions, ['view_workspace_' . $bears->id()]));
+    $this->drupalLogin($lombardi);
+    $this->switchToWorkspace($bears);
+
+    // Because editor 2 has the bypass permission, he should be able to
+    // create and edit any node.
+
+    $this->drupalGet('/node/' . $ditka_bears_node_id . '/edit');
+    $session = $this->getSession();
+    $this->assertEquals(403, $session->getStatusCode());
+
+    $this->drupalGet('/node/' . $bears_vanilla_node->id() . '/edit');
+    $session = $this->getSession();
+    $this->assertEquals(403, $session->getStatusCode());
+  }
+
+
+}
diff --git a/core/modules/workspace/tests/src/Functional/WorkspaceIndividualPermissionsTest.php b/core/modules/workspace/tests/src/Functional/WorkspaceIndividualPermissionsTest.php
new file mode 100644
index 0000000..3704a2a
--- /dev/null
+++ b/core/modules/workspace/tests/src/Functional/WorkspaceIndividualPermissionsTest.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\Tests\workspace\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests permission controls on workspaces.
+ *
+ * @group workspace
+ *
+ * @runTestsInSeparateProcesses
+ *
+ * @preserveGlobalState disabled
+ */
+class WorkspaceIndividualPermissionsTest extends BrowserTestBase  {
+
+  use WorkspaceTestUtilities;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['workspace', 'workspace'];
+
+  /**
+   * Verifies that a user can create and edit only their own workspace.
+   */
+  public function testEditIndividualWorkspace() {
+    $permissions = [
+      'access administration pages',
+      'administer site configuration',
+      'create_workspace',
+      'edit_own_workspace',
+      'view_own_workspace',
+    ];
+
+    $editor1 = $this->drupalCreateUser($permissions);
+
+    // Login as a limited-access user and create a workspace.
+    $this->drupalLogin($editor1);
+
+    $this->createWorkspaceThroughUI('Bears', 'bears');
+    $bears = $this->getOneWorkspaceByLabel('Bears');
+
+    // Now login as a different user with permission to edit that workspace,
+    // specifically.
+
+    $editor2 = $this->drupalCreateUser(array_merge($permissions, ['update_workspace_' . $bears->id()]));
+
+    $this->drupalLogin($editor2);
+    $session = $this->getSession();
+
+    $this->drupalGet("/admin/structure/workspace/{$bears->id()}/edit");
+    $this->assertEquals(200, $session->getStatusCode());
+  }
+
+  /**
+   * Verifies that a user can view a specific workspace.
+   */
+  public function testViewIndividualWorkspace() {
+    $permissions = [
+      'access administration pages',
+      'administer site configuration',
+      'create_workspace',
+      'edit_own_workspace',
+    ];
+
+    $editor1 = $this->drupalCreateUser($permissions);
+
+    // Login as a limited-access user and create a workspace.
+    $this->drupalLogin($editor1);
+
+    $this->createWorkspaceThroughUI('Bears', 'bears');
+    $bears = $this->getOneWorkspaceByLabel('Bears');
+
+    // Now login as a different user and create a workspace.
+
+    $editor2 = $this->drupalCreateUser(array_merge($permissions, ['view_workspace_' . $bears->id()]));
+
+    $this->drupalLogin($editor2);
+    $session = $this->getSession();
+
+    $this->createWorkspaceThroughUI('Packers', 'packers');
+
+    $packers = $this->getOneWorkspaceByLabel('Packers');
+
+    // Load the activate form for the Bears workspace. It should work, because
+    // the user has the permission specific to that workspace.
+    $this->drupalGet("admin/structure/workspace/{$bears->id()}/activate");
+    $this->assertEquals(200, $session->getStatusCode());
+
+    // But editor 1 cannot view the Packers workspace.
+
+    $this->drupalLogin($editor1);
+    $this->drupalGet("admin/structure/workspace/{$packers->id()}/activate");
+    $this->assertEquals(403, $session->getStatusCode());
+  }
+
+}
diff --git a/core/modules/workspace/tests/src/Functional/WorkspacePermissionsTest.php b/core/modules/workspace/tests/src/Functional/WorkspacePermissionsTest.php
new file mode 100644
index 0000000..0b52aca
--- /dev/null
+++ b/core/modules/workspace/tests/src/Functional/WorkspacePermissionsTest.php
@@ -0,0 +1,168 @@
+<?php
+
+namespace Drupal\Tests\workspace\Functional;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\workspace\Entity\WorkspaceInterface;
+use Drupal\simpletest\BrowserTestBase;
+
+
+/**
+ * Tests permission controls on workspaces.
+ *
+ * @group workspace
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ *
+ */
+class WorkspacePermissionsTest extends BrowserTestBase {
+  use WorkspaceTestUtilities;
+
+  public static $modules = ['workspace', 'workspace'];
+
+  /**
+   * Verifies that a user can create but not edit a workspace.
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   */
+  public function testCreateWorkspace() {
+    $editor = $this->drupalCreateUser([
+      'access administration pages',
+      'administer site configuration',
+      'create_workspace',
+    ]);
+
+    // Login as a limited-access user and create a workspace.
+    $this->drupalLogin($editor);
+    $session = $this->getSession();
+
+    $this->drupalGet('/admin/structure/workspace/add');
+
+    $this->assertEquals(200, $session->getStatusCode());
+
+    $page = $session->getPage();
+    $page->fillField('label', 'Bears');
+    $page->fillField('machine_name', 'bears');
+    $page->findButton(t('Save'))->click();
+
+    $session->getPage()->hasContent('Bears (bears)');
+
+    // Now edit that same workspace; We shouldn't be able to do so, since
+    // we don't have edit permissions.
+
+    /** @var EntityTypeManagerInterface $etm */
+    $etm = \Drupal::service('entity_type.manager');
+    /** @var WorkspaceInterface $bears */
+    $entity_list = $etm->getStorage('workspace')->loadByProperties(['label' => 'Bears']);
+    $bears = current($entity_list);
+
+    $this->drupalGet("/admin/structure/workspace/{$bears->id()}/edit");
+    $this->assertEquals(403, $session->getStatusCode());
+
+    // @todo add Deletion checks once there's a UI for deletion.
+  }
+
+  /**
+   * Verifies that a user can create and edit only their own workspace.
+   */
+  public function testEditOwnWorkspace() {
+    $permissions = [
+      'access administration pages',
+      'administer site configuration',
+      'create_workspace',
+      'edit_own_workspace',
+    ];
+
+    $editor1 = $this->drupalCreateUser($permissions);
+
+    // Login as a limited-access user and create a workspace.
+    $this->drupalLogin($editor1);
+
+    $this->createWorkspaceThroughUI('Bears', 'bears');
+
+    // Now edit that same workspace; We should be able to do so.
+
+    $bears = $this->getOneWorkspaceByLabel('Bears');
+
+    $session = $this->getSession();
+
+    $this->drupalGet("/admin/structure/workspace/{$bears->id()}/edit");
+    $this->assertEquals(200, $session->getStatusCode());
+
+    $page = $session->getPage();
+    $page->fillField('label', 'Bears again');
+    $page->fillField('machine_name', 'bears');
+    $page->findButton(t('Save'))->click();
+    $session->getPage()->hasContent('Bears again (bears)');
+
+    // Now login as a different user and ensure they don't have edit access,
+    // and vice versa.
+
+    $editor2 = $this->drupalCreateUser($permissions);
+
+    $this->drupalLogin($editor2);
+    $session = $this->getSession();
+
+    $this->createWorkspaceThroughUI('Packers', 'packers');
+
+    $packers = $this->getOneWorkspaceByLabel('Packers');
+
+    $this->drupalGet("/admin/structure/workspace/{$packers->id()}/edit");
+    $this->assertEquals(200, $session->getStatusCode());
+
+    $this->drupalGet("/admin/structure/workspace/{$bears->id()}/edit");
+    $this->assertEquals(403, $session->getStatusCode());
+  }
+
+  /**
+   * Verifies that a user can edit any workspace.
+   */
+  public function testEditAnyWorkspace() {
+    $permissions = [
+      'access administration pages',
+      'administer site configuration',
+      'create_workspace',
+      'edit_own_workspace',
+    ];
+
+    $editor1 = $this->drupalCreateUser($permissions);
+
+    // Login as a limited-access user and create a workspace.
+    $this->drupalLogin($editor1);
+
+    $this->createWorkspaceThroughUI('Bears', 'bears');
+
+    // Now edit that same workspace; We should be able to do so.
+
+    $bears = $this->getOneWorkspaceByLabel('Bears');
+
+    $session = $this->getSession();
+
+    $this->drupalGet("/admin/structure/workspace/{$bears->id()}/edit");
+    $this->assertEquals(200, $session->getStatusCode());
+
+    $page = $session->getPage();
+    $page->fillField('label', 'Bears again');
+    $page->fillField('machine_name', 'bears');
+    $page->findButton(t('Save'))->click();
+    $session->getPage()->hasContent('Bears again (bears)');
+
+    // Now login as a different user and ensure they don't have edit access,
+    // and vice versa.
+
+    $admin = $this->drupalCreateUser(array_merge($permissions, ['edit_any_workspace']));
+
+    $this->drupalLogin($admin);
+    $session = $this->getSession();
+
+    $this->createWorkspaceThroughUI('Packers', 'packers');
+
+    $packers = $this->getOneWorkspaceByLabel('Packers');
+
+    $this->drupalGet("/admin/structure/workspace/{$packers->id()}/edit");
+    $this->assertEquals(200, $session->getStatusCode());
+
+    $this->drupalGet("/admin/structure/workspace/{$bears->id()}/edit");
+    $this->assertEquals(200, $session->getStatusCode());
+  }
+}
diff --git a/core/modules/workspace/tests/src/Functional/WorkspaceSwitcherTest.php b/core/modules/workspace/tests/src/Functional/WorkspaceSwitcherTest.php
new file mode 100644
index 0000000..0132452
--- /dev/null
+++ b/core/modules/workspace/tests/src/Functional/WorkspaceSwitcherTest.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\Tests\workspace\Functional;
+
+use Drupal\workspace\Entity\Workspace;
+use Drupal\simpletest\BlockCreationTrait;
+use Drupal\simpletest\BrowserTestBase;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests workspace switching functionality.
+ *
+ * @group workspace
+ */
+class WorkspaceSwitcherTest extends BrowserTestBase {
+  use WorkspaceTestUtilities;
+  use BlockCreationTrait {
+    placeBlock as drupalPlaceBlock;
+  }
+
+  public static $modules = ['block', 'workspace'];
+
+  /**
+   * Test that the block displays and switches workspaces.
+   * Then test the admin page displays workspaces and allows switching.
+   */
+  public function testSwitchingWorkspaces() {
+    $permissions = [
+      'create_workspace',
+      'edit_own_workspace',
+      'view_own_workspace',
+      'bypass_entity_access_own_workspace',
+    ];
+
+    $this->setupWorkspaceSwitcherBlock();
+
+    $mayer = $this->drupalCreateUser($permissions);
+    $this->drupalLogin($mayer);
+
+    $vultures = $this->createWorkspaceThroughUI('Vultures', 'vultures');
+    $this->switchToWorkspace($vultures);
+
+    $gravity = $this->createWorkspaceThroughUI('Gravity', 'gravity');
+
+    $this->drupalGet('/admin/structure/workspace/' . $gravity->id() . '/activate');
+
+    $session = $this->getSession();
+    $this->assertEquals(200, $session->getStatusCode());
+    $page = $session->getPage();
+    $page->findButton(t('Activate'))->click();
+
+    $session->getPage()->findLink($gravity->label());
+
+  }
+}
diff --git a/core/modules/workspace/tests/src/Functional/WorkspaceTest.php b/core/modules/workspace/tests/src/Functional/WorkspaceTest.php
new file mode 100644
index 0000000..d34f31e
--- /dev/null
+++ b/core/modules/workspace/tests/src/Functional/WorkspaceTest.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Drupal\Tests\workspace\Functional;
+
+use Drupal\simpletest\BrowserTestBase;
+use Drupal\Tests\workspace\Functional\WorkspaceTestUtilities;
+
+/**
+ * Test the workspace entity.
+ *
+ * @group workspace
+ */
+class WorkspaceTest extends BrowserTestBase {
+  use WorkspaceTestUtilities;
+
+  public static $modules = ['workspace'];
+
+  public function testSpecialCharacters() {
+    $permissions = [
+      'access administration pages',
+      'administer site configuration',
+      'create_workspace',
+      'edit_own_workspace',
+    ];
+
+    $editor1 = $this->drupalCreateUser($permissions);
+    $this->drupalLogin($editor1);
+
+    // Test a valid workspace name
+    $this->createWorkspaceThroughUI('Workspace 1', 'a0_$()+-/');
+
+    // Test and invaid workspace name
+    $this->drupalGet('/admin/structure/workspace/add');
+    $session = $this->getSession();
+    $this->assertEquals(200, $session->getStatusCode());
+    $page = $session->getPage();
+    $page->fillField('label', 'workspace2');
+    $page->fillField('machine_name', 'A!"£%^&*{}#~@?');
+    $page->findButton(t('Save'))->click();
+    $session->getPage()->hasContent("This value is not valid");
+  }
+}
\ No newline at end of file
diff --git a/core/modules/workspace/tests/src/Functional/WorkspaceTestUtilities.php b/core/modules/workspace/tests/src/Functional/WorkspaceTestUtilities.php
new file mode 100644
index 0000000..e427fa6
--- /dev/null
+++ b/core/modules/workspace/tests/src/Functional/WorkspaceTestUtilities.php
@@ -0,0 +1,207 @@
+<?php
+
+namespace Drupal\Tests\workspace\Functional;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\workspace\Entity\WorkspaceInterface;
+use Drupal\node\Entity\NodeType;
+use Drupal\workspace\WorkspacePointerInterface;
+
+/**
+ * Utility methods for use in BrowserTestBase tests.
+ *
+ * This trait will not work if not used in a child of BrowserTestBase.
+ */
+trait WorkspaceTestUtilities {
+
+  /**
+   * Loads a single workspace by its label.
+   *
+   * The UI approach to creating a workspace doesn't make it easy to know what
+   * the ID is, so this lets us make paths for a workspace after it's created.
+   *
+   * @param $label
+   *   The label of the workspace to load.
+   * @return WorkspaceInterface
+   */
+  protected function getOneWorkspaceByLabel($label) {
+    return $this->getOneEntityByLabel('workspace', $label);
+  }
+
+  /**
+   * Loads a single entity by its label.
+   *
+   * The UI approach to creating an entity doesn't make it easy to know what
+   * the ID is, so this lets us make paths for an entity after it's created.
+   *
+   * @param string $type
+   *   The type of entity to load.
+   * @param $label
+   *   The label of the entity to load.
+   * @return WorkspaceInterface
+   */
+  protected function getOneEntityByLabel($type, $label) {
+    /** @var EntityTypeManagerInterface $etm */
+    $etm = \Drupal::service('entity_type.manager');
+
+    $property = $etm->getDefinition($type)->getKey('label');
+
+    /** @var WorkspaceInterface $bears */
+    $entity_list = $etm->getStorage($type)->loadByProperties([$property => $label]);
+
+    $entity = current($entity_list);
+
+    if (!$entity) {
+      $this->fail("No {$type} entity named {$label} found.");
+    }
+
+    return $entity;
+  }
+
+  /**
+   * Creates a new Workspace through the UI.
+   *
+   * @param string $label
+   *   The label of the workspace to create.
+   * @param string $machine_name
+   *   The machine name of the workspace to create.
+   *
+   * @return WorkspaceInterface
+   *   The workspace that was just created.
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   */
+  protected function createWorkspaceThroughUI($label, $machine_name) {
+    $this->drupalGet('/admin/structure/workspace/add');
+
+    $session = $this->getSession();
+    $this->assertSession()->statusCodeEquals(200);
+
+    $page = $session->getPage();
+    $page->fillField('label', $label);
+    $page->fillField('machine_name', $machine_name);
+    $page->findButton(t('Save'))->click();
+
+    $session->getPage()->hasContent("$label ($machine_name)");
+
+    return $this->getOneWorkspaceByLabel($label);
+  }
+
+  /**
+   * Adds the workspace switcher block to the site.
+   *
+   * This is necessary for switchToWorkspace() to function correctly.
+   */
+  protected function setupWorkspaceSwitcherBlock() {
+    // Add the block to the sidebar.
+    $this->drupalPlaceBlock('workspace_switcher_block', [
+      'id' => 'workspaceswitcher',
+      'region' => 'sidebar_first',
+      'label' => 'Workspace switcher',
+    ]);
+
+    // Confirm the block shows on the front page.
+    $this->drupalGet('<front>');
+    $page = $this->getSession()->getPage();
+
+    $this->assertTrue($page->hasContent('Workspace switcher'));
+  }
+
+  /**
+   * Sets a given workspace as "active" for subsequent requests.
+   *
+   * This assumes that the switcher block has already been setup by calling
+   * setupWorkspaceSwitcherBlock().
+   *
+   * @param WorkspaceInterface $workspace
+   *   The workspace to set active.
+   */
+  protected function switchToWorkspace(WorkspaceInterface $workspace) {
+    // Switch the test runner's context to the specified workspace.
+    \Drupal::service('workspace.manager')->setActiveWorkspace($workspace);
+
+    // Switch the system under test to the specified workspace.
+    $this->getSession()->getPage()->findButton($workspace->label())->click();
+
+    // If we don't do both of those, test runner utility methods will not be
+    // run in the same workspace as the system under test, and you'll be left
+    // wondering why your test runner cannot find content you just created.
+  }
+
+  /**
+   * Creates a new node type.
+   *
+   * @param string $label
+   *   The human-readable label of the type to create.
+   * @param string $machine_name
+   *   The machine name of the type to create.
+   */
+  protected function createNodeType($label, $machine_name) {
+    $node_type = NodeType::create([
+      'type' => $machine_name,
+      'label' => $label,
+    ]);
+    $node_type->save();
+  }
+
+
+  /**
+   * Creates a node by "clicking" buttons.
+   *
+   * @param string $label
+   * @param string $bundle
+   *
+   * @return \Drupal\workspace\Entity\WorkspaceInterface
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   */
+  protected function createNodeThroughUI($label, $bundle) {
+    $this->drupalGet('/node/add/' . $bundle);
+
+    $session = $this->getSession();
+    $this->assertSession()->statusCodeEquals(200);
+
+    $page = $session->getPage();
+    $page->fillField('Title', $label);
+    $page->findButton(t('Save'))->click();
+
+    $session->getPage()->hasContent("{$label} has been created");
+
+    return $this->getOneEntityByLabel('node', $label);
+  }
+
+  /**
+   * Returns a pointer to the specified workspace.
+   *
+   * @todo Replace this with a common method in the module somewhere.
+   *
+   * @param \Drupal\workspace\Entity\WorkspaceInterface $workspace
+   *   The workspace for which we want a pointer.
+   * @return WorkspacePointerInterface
+   *   The pointer to the provided workspace.
+   */
+  protected function getPointerToWorkspace(WorkspaceInterface $workspace) {
+    /** @var EntityTypeManagerInterface $etm */
+    $etm = \Drupal::service('entity_type.manager');
+
+    $pointers = $etm->getStorage('workspace_pointer')
+      ->loadByProperties(['workspace_pointer' => $workspace->id()]);
+    $pointer = reset($pointers);
+    return $pointer;
+  }
+
+  /**
+   * Determine if the content list has an entity's label.
+   *
+   * This assertion can be used to validate a particular entity exists in the
+   * current workspace.
+   */
+  protected function isLabelInContentOverview($label) {
+    $this->drupalGet('/admin/content');
+    $session = $this->getSession();
+    $this->assertSession()->statusCodeEquals(200);
+    $page = $session->getPage();
+    return $page->hasContent($label);
+  }
+
+}
diff --git a/core/modules/workspace/tests/src/Functional/WorkspaceViewTest.php b/core/modules/workspace/tests/src/Functional/WorkspaceViewTest.php
new file mode 100644
index 0000000..1eaecb1
--- /dev/null
+++ b/core/modules/workspace/tests/src/Functional/WorkspaceViewTest.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace Drupal\Tests\workspace\Functional;
+
+use Drupal\simpletest\BrowserTestBase;
+
+
+/**
+ * Tests permission controls on workspaces.
+ *
+ * @group workspace
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ *
+ */
+class WorkspaceViewTest extends BrowserTestBase {
+  use WorkspaceTestUtilities;
+
+  public static $modules = ['workspace', 'workspace'];
+
+  /**
+   * Verifies that a user can view their own workspace.
+   */
+  public function testViewOwnWorkspace() {
+    $permissions = [
+      'access administration pages',
+      'administer site configuration',
+      'create_workspace',
+      'edit_own_workspace',
+      'view_own_workspace',
+    ];
+
+    $editor1 = $this->drupalCreateUser($permissions);
+
+    // Login as a limited-access user and create a workspace.
+    $this->drupalLogin($editor1);
+
+    $this->createWorkspaceThroughUI('Bears', 'bears');
+
+    $bears = $this->getOneWorkspaceByLabel('Bears');
+
+    // Now login as a different user and create a workspace.
+
+    $editor2 = $this->drupalCreateUser($permissions);
+
+    $this->drupalLogin($editor2);
+    $session = $this->getSession();
+
+    $this->createWorkspaceThroughUI('Packers', 'packers');
+
+    $packers = $this->getOneWorkspaceByLabel('Packers');
+
+    // Load the activate form for the Bears workspace. It should fail because
+    // the workspace belongs to someone else.
+    $this->drupalGet("admin/structure/workspace/{$bears->id()}/activate");
+    $this->assertEquals(403, $session->getStatusCode());
+
+    // But editor 2 should be able to activate the Packers workspace.
+    $this->drupalGet("admin/structure/workspace/{$packers->id()}/activate");
+    $this->assertEquals(200, $session->getStatusCode());
+  }
+
+  /**
+   * Verifies that a user can view any workspace.
+   */
+  public function testViewAnyWorkspace() {
+    $permissions = [
+      'access administration pages',
+      'administer site configuration',
+      'create_workspace',
+      'edit_own_workspace',
+      'view_any_workspace',
+    ];
+
+    $editor1 = $this->drupalCreateUser($permissions);
+
+    // Login as a limited-access user and create a workspace.
+    $this->drupalLogin($editor1);
+
+    $this->createWorkspaceThroughUI('Bears', 'bears');
+
+    $bears = $this->getOneWorkspaceByLabel('Bears');
+
+    // Now login as a different user and create a workspace.
+
+    $editor2 = $this->drupalCreateUser($permissions);
+
+    $this->drupalLogin($editor2);
+    $session = $this->getSession();
+
+    $this->createWorkspaceThroughUI('Packers', 'packers');
+
+    $packers = $this->getOneWorkspaceByLabel('Packers');
+
+    // Load the activate form for the Bears workspace. This user should be
+    // able to see both workspaces because of the "view any" permission.
+    $this->drupalGet("admin/structure/workspace/{$bears->id()}/activate");
+    $this->assertEquals(200, $session->getStatusCode());
+
+    // But editor 2 should be able to activate the Packers workspace.
+    $this->drupalGet("admin/structure/workspace/{$packers->id()}/activate");
+    $this->assertEquals(200, $session->getStatusCode());
+  }
+}
diff --git a/core/modules/workspace/workspace.info.yml b/core/modules/workspace/workspace.info.yml
new file mode 100644
index 0000000..27e086e
--- /dev/null
+++ b/core/modules/workspace/workspace.info.yml
@@ -0,0 +1,8 @@
+name: Workspace
+type: module
+description: 'Provides the ability to have multiple workspaces on a single site to facilitate things like full-site preview and content staging.'
+version: VERSION
+core: 8.x
+package: Core (Experimental)
+dependencies:
+ - user
diff --git a/core/modules/workspace/workspace.install b/core/modules/workspace/workspace.install
new file mode 100644
index 0000000..e8ad30d
--- /dev/null
+++ b/core/modules/workspace/workspace.install
@@ -0,0 +1,22 @@
+<?php
+
+use Drupal\workspace\Entity\Workspace;
+
+/**
+ * Implementation of hook_install().
+ */
+function workspace_install() {
+  /** @var \Drupal\workspace\Entity\WorkspaceInterface $live */
+  $live = Workspace::create(['machine_name' => 'live', 'label' => 'Live', 'type' => 'basic']);
+  $live->save();
+
+  $default_workspace_id = \Drupal::getContainer()->getParameter('workspace.default');
+  /** @var \Drupal\workspace\Entity\WorkspaceInterface $stage */
+  $stage = Workspace::create(['machine_name' => 'stage', 'label' => 'Stage', 'type' => 'basic']);
+  $stage->set('upstream', $default_workspace_id);
+  $stage->save();
+
+  // allow workspace entity route alterations
+  \Drupal::service('entity_type.manager')->clearCachedDefinitions();
+  \Drupal::service('router.builder')->rebuild();
+}
diff --git a/core/modules/workspace/workspace.libraries.yml b/core/modules/workspace/workspace.libraries.yml
new file mode 100644
index 0000000..e1edc2a
--- /dev/null
+++ b/core/modules/workspace/workspace.libraries.yml
@@ -0,0 +1,17 @@
+drupal.workspace.toolbar:
+  version: VERSION
+  css:
+    theme:
+      css/workspace.toolbar.css: {}
+
+drupal.workspace.admin:
+  version: VERSION
+  css:
+    theme:
+      css/workspace.admin.css: {}
+
+drupal.workspace.switcher:
+  version: VERSION
+  css:
+    theme:
+      css/workspace.switcher.css: {}
\ No newline at end of file
diff --git a/core/modules/workspace/workspace.links.action.yml b/core/modules/workspace/workspace.links.action.yml
new file mode 100644
index 0000000..4bd599b
--- /dev/null
+++ b/core/modules/workspace/workspace.links.action.yml
@@ -0,0 +1,10 @@
+entity.workspace.add:
+  route_name: entity.workspace.add
+  title: 'Add workspace'
+  appears_on:
+    - entity.workspace.collection
+entity.workspace_type.add_form:
+  route_name: 'entity.workspace_type.add_form'
+  title: 'Add Workspace type'
+  appears_on:
+    - entity.workspace_type.collection
diff --git a/core/modules/workspace/workspace.links.menu.yml b/core/modules/workspace/workspace.links.menu.yml
new file mode 100644
index 0000000..fc925cf
--- /dev/null
+++ b/core/modules/workspace/workspace.links.menu.yml
@@ -0,0 +1,5 @@
+entity.workspace.collection:
+  title: 'Workspaces'
+  parent: system.admin_structure
+  description: 'Create and manage workspaces.'
+  route_name: entity.workspace.collection
diff --git a/core/modules/workspace/workspace.links.task.yml b/core/modules/workspace/workspace.links.task.yml
new file mode 100644
index 0000000..084b0a3
--- /dev/null
+++ b/core/modules/workspace/workspace.links.task.yml
@@ -0,0 +1,17 @@
+entity.workspace.collection:
+  title: 'Workspaces'
+  route_name: entity.workspace.collection
+  base_route: entity.workspace.collection
+entity.workspace_type.collection:
+  title: 'Types'
+  route_name: entity.workspace_type.collection
+  base_route: entity.workspace.collection
+
+entity.workspace.edit_form:
+  route_name: entity.workspace.edit_form
+  base_route: entity.workspace.canonical
+  title: Edit
+entity.workspace_type.edit_form:
+  title: 'Edit'
+  route_name: entity.workspace_type.edit_form
+  base_route: entity.workspace_type.edit_form
diff --git a/core/modules/workspace/workspace.module b/core/modules/workspace/workspace.module
new file mode 100644
index 0000000..bd387e2
--- /dev/null
+++ b/core/modules/workspace/workspace.module
@@ -0,0 +1,164 @@
+<?php
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Link;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
+use Drupal\workspace\Entity\WorkspaceInterface;
+use Drupal\workspace\Plugin\Field\WorkspaceFieldItemList;
+
+/**
+ * Implements hook_help().
+ */
+function workspace_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    // Main module help for the workspace module.
+    case 'help.page.workspace':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('The Workspace module allows workspaces to be defined and switched between. Content is then assigned to the active workspace when created. For more information, see the <a href=":workspace">online documentation for the Workspace module</a>.', [':workspace' => 'https://www.drupal.org/node/2824024']) . '</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implements hook_entity_base_field_info().
+ */
+function workspace_entity_base_field_info(EntityTypeInterface $entity_type) {
+  if ($entity_type->isRevisionable() && !in_array($entity_type->id(), ['content_workspace', 'workspace'])) {
+    return ['workspace' => BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('Workspace'))
+      ->setDescription(t('The Workspace of this piece of content.'))
+      ->setComputed(TRUE)
+      ->setClass(WorkspaceFieldItemList::class)
+      ->setSetting('target_type', 'workspace')
+      ->setTranslatable(TRUE)];
+  }
+}
+
+/**
+ * Implements hook_entity_insert().
+ */
+function workspace_entity_insert(EntityInterface $entity) {
+  \Drupal::service('workspace.manager')->updateOrCreateFromEntity($entity);
+}
+
+/**
+ * Implements hook_entity_update().
+ */
+function workspace_entity_update(EntityInterface $entity) {
+  \Drupal::service('workspace.manager')->updateOrCreateFromEntity($entity);
+}
+
+/**
+ * Default value callback for 'upstream' base field definition.
+ *
+ * @return array
+ */
+function workspace_active_id() {
+  /** @var \Drupal\workspace\Entity\Workspace $active_workspace */
+  $active_workspace = \Drupal::service('workspace.manager')->getActiveWorkspace();
+  if ($active_workspace instanceof WorkspaceInterface) {
+    return [$active_workspace->id()];
+  }
+}
+
+/**
+ * Implements hook_toolbar().
+ */
+function workspace_toolbar() {
+  return \Drupal::service('workspace.toolbar')->toolbar();
+}
+
+/**
+ * Implements hook_entity_access().
+ */
+function workspace_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
+  return \Drupal::service('workspace.entity_access')->entityAccess($entity, $operation, $account);
+}
+
+/**
+ * Implements hook_entity_create_access().
+ */
+function workspace_entity_create_access(AccountInterface $account, array $context, $entity_bundle) {
+  return \Drupal::service('workspace.entity_access')->entityCreateAccess($account, $context, $entity_bundle);
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_access().
+ */
+function workspace_workspace_access(EntityInterface $entity, $operation, AccountInterface $account) {
+  return \Drupal::service('workspace.entity_access')->workspaceAccess($entity, $operation, $account);
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_create_access().
+ */
+function workspace_workspace_create_access(AccountInterface $account, array $context, $entity_bundle) {
+  return \Drupal::service('workspace.entity_access')->workspaceCreateAccess($account, $context, $entity_bundle);
+}
+
+
+/**
+ * Implements hook_theme().
+ *
+ * @param $existing
+ * @param $type
+ * @param $theme
+ * @param $path
+ * @return array
+ */
+function workspace_theme($existing, $type, $theme, $path) {
+  return [
+    'workspace_add_list' => [
+      'variables' => ['content' => NULL],
+    ],
+    'workspace_rev' => [
+      'render element' => 'elements',
+    ],
+  ];
+}
+
+/**
+ * Implements hook_preprocess_HOOK
+ */
+function workspace_preprocess_workspace_add_list(&$variables) {
+  if (!empty($variables['content'])) {
+    foreach ($variables['content'] as $type) {
+      $variables['types'][$type->id()]['label'] = $type->label();
+      $options = array('query' => \Drupal::request()->query->all());
+      $variables['types'][$type->id()]['url'] = Url::fromRoute('entity.workspace.add_form', array('workspace_type' => $type->id()), $options);
+    }
+  }
+}
+
+/**
+ * Prepares variables for revision templates.
+ */
+function workspace_preprocess_workspace_rev(&$variables) {
+  $uuid = $variables['elements']['#uuid'];
+  $rev = $variables['elements']['#rev'];
+  $rev_info = array_merge(
+    \Drupal::service('workspace.entity_index.rev')->get("$uuid:$rev"),
+    $variables['elements']['#rev_info']
+  );
+
+  $variables = array_merge($variables, $rev_info);
+
+  list($i) = explode('-', $rev);
+  // Apart from the index length, we want 7 characters plus dash and ellipsis.
+  $length = strlen($i) + 9;
+  $title = Unicode::truncate($rev, $length, FALSE, TRUE);
+
+  if (!empty($rev_info['revision_id'])) {
+    $entity_revision = \Drupal::entityTypeManager()->getStorage($rev_info['entity_type_id'])->loadRevision($rev_info['revision_id']);
+    $variables['title'] = Link::fromTextAndUrl($title, $entity_revision->toUrl('revision'));
+  }
+  else {
+    $variables['title'] = $title;
+  }
+}
diff --git a/core/modules/workspace/workspace.permissions.yml b/core/modules/workspace/workspace.permissions.yml
new file mode 100644
index 0000000..c8bcf15
--- /dev/null
+++ b/core/modules/workspace/workspace.permissions.yml
@@ -0,0 +1,42 @@
+create_workspace:
+  title: Create a new workspace
+
+view_own_workspace:
+  title: View own workspace
+  description: View a workspace owned by the user.
+
+view_any_workspace:
+  title: View any workspace
+  description: View any workspace, regardless of ownership.
+
+edit_own_workspace:
+  title: Edit own workspace
+  description: Make changes to workspaces owned by the user.
+
+edit_any_workspace:
+  title: Edit any workspace
+  description: Make changes to any workspace, regardless of ownership.
+
+delete_own_workspace:
+  title: Delete own workspace
+  description: Delete a workspace owned by the user and all content revisions within it.
+
+delete_any_workspace:
+  title: Delete any workspace
+  description: Delete a workspace and all content revisions within it, regardless of ownership.
+
+bypass_entity_access_own_workspace:
+  title: Bypass content entity access in own workspace
+  description: Allow all Edit/Update/Delete permissions for all content entities in a workspace owned by the user.
+  restrict access: TRUE
+
+view_revision_trees:
+  title: View revision trees
+  description: View the revision tree for any entities.
+
+update any workspace from upstream:
+  title: Update any workspace from upstream
+  description: Update any workspace with the latest changes from its upstream workspace.
+
+permission_callbacks:
+  - workspace.entity_access::workspacePermissions
diff --git a/core/modules/workspace/workspace.routing.yml b/core/modules/workspace/workspace.routing.yml
new file mode 100644
index 0000000..1c54ab9
--- /dev/null
+++ b/core/modules/workspace/workspace.routing.yml
@@ -0,0 +1,59 @@
+# Workspace routing definition
+entity.workspace.add:
+  path: '/admin/structure/workspace/add'
+  defaults:
+    _controller: '\Drupal\workspace\Controller\WorkspaceController::add'
+    _title: 'Add workspace'
+  options:
+    _admin_route: TRUE
+  requirements:
+    _permission: 'administer workspaces+create_workspace'
+
+entity.workspace.add_form:
+  path: '/admin/structure/workspace/add/{workspace_type}'
+  defaults:
+    _controller: '\Drupal\workspace\Controller\WorkspaceController::addForm'
+    _title_callback: '\Drupal\workspace\Controller\WorkspaceController::getAddFormTitle'
+  options:
+    _admin_route: TRUE
+  requirements:
+    _permission: 'administer workspaces'
+
+entity.workspace.collection:
+  path: '/admin/structure/workspace'
+  defaults:
+    _title: 'Workspaces'
+    _entity_list: 'workspace'
+  requirements:
+    _permission: 'administer workspaces+edit_any_workspace'
+
+entity.workspace.activate_form:
+  path: '/admin/structure/workspace/{workspace}/activate'
+  defaults:
+    _title: 'Activate Workspace'
+    _form: '\Drupal\workspace\Form\WorkspaceActivateForm'
+  options:
+    _admin_route: TRUE
+  requirements:
+    _workspace_view: 'TRUE'
+
+# WorkspaceType routing definition
+entity.workspace_type.collection:
+  path: '/admin/structure/workspace/types'
+  defaults:
+    _entity_list: 'workspace_type'
+    _title: 'Workspace types'
+  requirements:
+    _permission: 'administer site configuration'
+  options:
+    _admin_route: TRUE
+
+entity.workspace_type.add_form:
+  path: '/admin/structure/workspace/types/add'
+  defaults:
+    _entity_form: 'workspace_type.add'
+    _title: 'Add Workspace type'
+  requirements:
+    _permission: 'administer site configuration'
+  options:
+    _admin_route: TRUE
diff --git a/core/modules/workspace/workspace.services.yml b/core/modules/workspace/workspace.services.yml
new file mode 100644
index 0000000..cd56b5f
--- /dev/null
+++ b/core/modules/workspace/workspace.services.yml
@@ -0,0 +1,52 @@
+parameters:
+  workspace.default: 1
+
+services:
+  workspace.toolbar:
+    class: Drupal\workspace\Toolbar
+    arguments: ['@entity_type.manager', '@workspace.manager', '@form_builder', '@current_user']
+  workspace.paramconverter.entity_revision:
+    class: Drupal\workspace\ParamConverter\EntityRevisionConverter
+    arguments: ['@entity.manager']
+    tags:
+      - { name: paramconverter, priority: 30 }
+  workspace.entity_access:
+    class: Drupal\workspace\EntityAccess
+    arguments: ['@entity_type.manager', '@workspace.manager', '%workspace.default%']
+  access_check.workspace_view:
+    class: Drupal\workspace\Access\WorkspaceViewCheck
+    tags:
+      - { name: access_check, applies_to: _workspace_view }
+  workspace.manager:
+    class: Drupal\workspace\WorkspaceManager
+    arguments: ['@request_stack', '@entity.manager', '@current_user', '@logger.channel.workspace']
+    tags:
+      - { name: service_collector, tag: workspace_negotiator, call: addNegotiator }
+  cache_context.workspace:
+    class: Drupal\workspace\WorkspaceCacheContext
+    arguments: ['@workspace.manager']
+    tags:
+      - { name: cache.context }
+  logger.channel.workspace:
+    parent: logger.channel_base
+    arguments: ['cron']
+
+  # @todo: {@link https://www.drupal.org/node/2597414 Simplify the container
+  # definition for negotiators.}
+  workspace.negotiator.default:
+    class: Drupal\workspace\DefaultWorkspaceNegotiator
+    calls:
+      - [setContainer, ['@service_container']]
+      - [setCurrentUser, ['@current_user']]
+      - [setWorkspaceManager, ['@workspace.manager']]
+    tags:
+      - { name: workspace_negotiator, priority: 0 }
+  workspace.negotiator.session:
+    class: Drupal\workspace\SessionWorkspaceNegotiator
+    arguments: ['@user.private_tempstore']
+    calls:
+      - [setContainer, ['@service_container']]
+      - [setCurrentUser, ['@current_user']]
+      - [setWorkspaceManager, ['@workspace.manager']]
+    tags:
+      - { name: workspace_negotiator, priority: 100 }
