diff --git a/core/lib/Drupal/Core/TempStore/TempStore.php b/core/lib/Drupal/Core/TempStore/TempStore.php
new file mode 100644
index 0000000..be5248a
--- /dev/null
+++ b/core/lib/Drupal/Core/TempStore/TempStore.php
@@ -0,0 +1,249 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\TempStore\TempStore.
+ */
+
+namespace Drupal\Core\TempStore;
+
+/**
+ * Handles reading and writing to a non-volatile temporary storage area.
+ *
+ * A TempStore is not a true cache, because it is non-volatile. While a cache
+ * can be reconstructed if the data disappears (i.e, a backend goes away
+ * or a cache is cleared), TempStore cannot tolerate the data disappearing.
+ *
+ * It is primarily used to handle in-progress edits on complicated objects
+ * in order to provide state to an ordinarily stateless HTTP transaction.
+ */
+class TempStore {
+
+  /**
+   * The subsystem or module that owns this TempStore.
+   *
+   * @var string
+   */
+  protected $subsystem;
+
+  /**
+   * The unique identifier for the owner of the temporary data.
+   *
+   * In order to ensure that users do not accidentally acquire each other's
+   * changes, session IDs can be used to differentiate them. However, there
+   * are cases where session IDs are not ideal. In these cases, an
+   * alternative ID can be set (such as a user ID or the number 0) which
+   * would indicate no special session handling is required.
+   *
+   * @var string
+   */
+  protected $ownerID;
+
+  /**
+   * Constructs a temporary storage object.
+   *
+   * @param string $subsystem
+   *   The module or subsystem. Possible values might include 'entity',
+   *   'form', 'views', etc.
+   * @param string $owner_id
+   *   A unique identifier for the owner of the temporary storage data.
+   */
+  function __construct($subsystem, $owner_id) {
+    $this->subsystem = $subsystem;
+    $this->ownerID = $owner_id;
+  }
+
+  /**
+   * Fetches the data from the store.
+   *
+   * @param string $key
+   *   The key to the stored object. See TempStore::set() for details.
+   *
+   * @return object|null
+   *   The stored data object, or NULL if none exist.
+   */
+  function get($key) {
+    $data = db_query(
+      'SELECT data FROM {temp_store} WHERE owner_id = :owner_id AND subsystem = :subsystem AND temp_key = :temp_key',
+      array(
+        ':owner_id' => $this->ownerID,
+        ':subsystem' => $this->subsystem,
+        ':temp_key' => $key,
+      )
+    )
+    ->fetchObject();
+    if ($data) {
+      return unserialize($data->data);
+    }
+  }
+
+  /**
+   * Writes the data to the store.
+   *
+   * @param string $key
+   *   The key to the object being stored. For objects that already exist in
+   *   the database somewhere else, this is typically the primary key of that
+   *   object. For objects that do not already exist, this is typically 'new'
+   *   or some special key that indicates that the object does not yet exist.
+   * @param mixed $data
+   *   The data to be cached. It will be serialized.
+   *
+   * @todo
+   *   Using 'new' as a key might result in collisions if the same user tries
+   *   to create multiple new objects simultaneously. Document a workaround?
+   */
+  function set($key, $data) {
+    // Store the new data.
+    db_merge('temp_store')
+      ->key(array('temp_key' => $key))
+      ->fields(array(
+        'owner_id' => $this->ownerID,
+        'subsystem' => $this->subsystem,
+        'temp_key' => $key,
+        'data' => serialize($data),
+        'updated' => REQUEST_TIME,
+      ))
+      ->execute();
+  }
+
+  /**
+   * Removes one or more objects from this store for this owner.
+   *
+   * @param string|array $key
+   *   The key to the stored object, or an array of keys. See
+   *   TempStore::set() for details.
+   */
+  function delete($key) {
+    $this->deleteRecords($key);
+  }
+
+  /**
+   * Removes one or more objects from this store for all owners.
+   *
+   * @param string|array $key
+   *   The key to the stored object, or an array of keys. See
+   *   TempStore::set() for details.
+   */
+  function deleteAll($key) {
+    $this->deleteRecords($key, TRUE);
+  }
+
+  /**
+   * Deletes database records for objects.
+   *
+   * @param string|array $key
+   *   The key to the stored object, or an array of keys. See
+   *   TempStore::set() for details.
+   * @param bool $all
+   *   Whether to delete all records for this key (TRUE) or just the current
+   *   owner's (FALSE). Defaults to FALSE.
+   */
+  protected function deleteRecords($key, $all = FALSE) {
+    // The query builder will automatically use an IN condition when an array
+    // is passed.
+    $query = db_delete('temp_store')
+      ->condition('temp_key', $key)
+      ->condition('subsystem', $this->subsystem);
+
+    if (!$all) {
+      $query->condition('owner_id', $this->ownerID);
+    }
+
+    $query->execute();
+  }
+
+  /**
+   * Determines if the object is in use by another store for locking purposes.
+   *
+   * @param string $key
+   *   The key to the stored object. See TempStore::set() for details.
+   * @param bool $exclude_owner
+   *   (optional) Whether or not to disregard the current user when determining
+   *   the lock owner. Defaults to FALSE.
+   *
+   * @return stdClass|null
+   *   An object with the user ID and updated date if found, otherwise NULL.
+   */
+  public function getLockOwner($key) {
+    return db_query(
+      'SELECT owner_id AS ownerID, updated FROM {temp_store} WHERE subsystem = :subsystem AND temp_key = :temp_key ORDER BY updated ASC',
+      array(
+        ':subsystem' => $this->subsystem,
+        ':temp_key' => $key,
+      )
+    )->fetchObject();
+  }
+
+  /**
+   * Checks to see if another owner has locked the object.
+   *
+   * @param string $key
+   *   The key to the stored object. See TempStore::set() for details.
+   *
+   * @return stdClass|null
+   *   An object with the owner ID and updated date, or NULL if there is no
+   *   lock on the object belonging to a different owner.
+   */
+  public function isLocked($key) {
+    $lock_owner = $this->getLockOwner($key);
+    if ((isset($lock_owner->ownerID) && $this->ownerID != $lock_owner->ownerID)) {
+      return $lock_owner;
+    }
+  }
+
+  /**
+   * Fetches the last updated time for multiple objects in a given subsystem.
+   *
+   * @param string $subsystem
+   *   The module or subsystem. Possible values might include 'entity',
+   *   'form', 'views', etc.
+   * @param array $keys
+   *   An array of keys of stored objects. See TempStore::set() for details.
+   *
+   * @return
+   *   An associative array of objects and their last updated time, keyed by
+   *   object key.
+   */
+  public static function testStoredObjects($subsystem, $keys) {
+    return db_query(
+      "SELECT t.temp_key, t.updated FROM {temp_store} t WHERE t.subsystem = :subsystem AND t.temp_key IN (:keys) ORDER BY t.updated ASC",
+      array(':subsystem' => $subsystem, ':temp_keys' => $keys)
+    )
+    ->fetchAllAssoc('temp_key');
+  }
+
+  /**
+   * Truncates all objects in all stores for a given key and subsystem.
+   *
+   * @param string $subsystem
+   *   The module or subsystem. Possible values might include 'entity',
+   *   'form', 'views', etc.
+   * @param array $key
+   *   The key to the stored object. See TempStore::set() for details.
+   */
+  public static function clearAll($subsystem, $key) {
+    $query = db_delete('temp_store')
+      ->condition('temp_key', $key)
+      ->condition('subsystem', $subsystem);
+
+    $query->execute();
+  }
+
+  /**
+   * Truncates all objects older than a certain age, for all stores.
+   *
+   * @param int $age
+   *   The minimum age of objects to remove, in seconds. For example, 86400 is
+   *   one day. Defaults to 7 days.
+   */
+  public static function clearOldObjects($age = NULL) {
+    if (!isset($age)) {
+      // 7 days.
+      $age = 86400 * 7;
+    }
+    db_delete('temp_store')
+      ->condition('updated', REQUEST_TIME - $age, '<')
+      ->execute();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/TempStore/UserTempStore.php b/core/lib/Drupal/Core/TempStore/UserTempStore.php
new file mode 100644
index 0000000..fbaddd9
--- /dev/null
+++ b/core/lib/Drupal/Core/TempStore/UserTempStore.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\TempStore\UserTempStore.
+ */
+
+namespace Drupal\Core\TempStore;
+
+/**
+ * Defines a TempStore using either the user or the session as the owner ID.
+ */
+class UserTempStore extends TempStore {
+
+  /**
+   * Overrides TempStore::__construct().
+   *
+   * The $owner_id is given a default value of NULL.
+   */
+  function __construct($subsystem, $owner_id = NULL) {
+    if (!isset($owner_id)) {
+      // If the user is anonymous, fall back to the session ID.
+      $owner_id = user_is_logged_in() ? $GLOBALS['user']->uid : session_id();
+    }
+
+    parent::__construct($subsystem, $owner_id);
+  }
+
+  /**
+   * Overrides TempStore::set().
+   */
+  function set($key, $data) {
+    // Ensure that a session cookie is set for anonymous users.
+    if (!user_is_logged_in()) {
+      // A session is written so long as $_SESSION is not empty. Force this.
+      // @todo This feels really hacky. Is there a better way?
+      // @see http://drupalcode.org/project/ctools.git/blob/refs/heads/8.x-1.x:/includes/object-cache.inc#l69
+      $_SESSION['temp_store_use_session'] = TRUE;
+    }
+
+    parent::set($key, $data);
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/TempStore/TempStoreTest.php b/core/modules/system/lib/Drupal/system/Tests/TempStore/TempStoreTest.php
new file mode 100644
index 0000000..f4f35fd
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/TempStore/TempStoreTest.php
@@ -0,0 +1,199 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\TempStore\TempStoreTest.
+ */
+
+namespace Drupal\system\Tests\TempStore;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\Core\TempStore\UserTempStore;
+use stdClass;
+
+/**
+ * Tests the TempStore subsystem.
+ *
+ * @see Drupal\Core\TempStore\TempStore.
+ */
+class TempStoreTest extends WebTestBase {
+
+  /**
+   * One acting user.
+   *
+   * @var Drupal\user\User
+   */
+  protected $firstUser;
+
+  /**
+   * Another user.
+   *
+   * @var Drupal\user\User
+   */
+  protected $secondUser;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'TempStore',
+      'description' => 'Tests the temporary object storage system.',
+      'group' => 'TempStore',
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+    // Create two users for testing.
+    $this->firstUser = $this->drupalCreateUser();
+    $this->secondUser = $this->drupalCreateUser();
+  }
+
+  /**
+   * Generates a random PHP object.
+   *
+   * @param int $size
+   *   The number of random keys to add to the object.
+   *
+   * @return stdClass
+   *   The generated object, with the specified number of random keys. Each key
+   *   has a random string value.
+   */
+  public function randomObject($size = 4) {
+    $object = new stdClass();
+    for ($i = 0; $i < $size; $i++) {
+      $random_key = $this->randomName();
+      $random_value = $this->randomString();
+      $object->{$random_key} = $random_value;
+    }
+    return $object;
+  }
+
+  /**
+   * Tests the UserTempStore API.
+   */
+  public function testUserTempStore() {
+    $this->drupalLogin($this->firstUser);
+    $subsystem = $this->randomName();
+    $temp_store = new UserTempStore($subsystem, $this->loggedInUser->uid);
+
+    // An empty storage should return nothing.
+    $this->assertFalse($temp_store->get($this->randomName()));
+
+    // Add an additional key to the store.
+    $random_key = $this->randomName();
+    $random_object = $this->randomObject();
+    $temp_store->set($random_key, $random_object);
+
+    // Ensure the new key is returned when fetched.
+    $result_data = $temp_store->get($random_key);
+    $this->assertEqual(
+      $random_object,
+      $result_data,
+      format_string(
+        'Key %key is properly added to the store.',
+        array('%key' => $random_key)
+      )
+    );
+
+    // Delete the key and ensure that it is properly removed.
+    $temp_store->delete($random_key);
+    $this->assertFalse(
+      $temp_store->get($random_key),
+      format_string(
+        'Key %key is properly removed from the store.',
+        array('%key' => $random_key)
+      )
+    );
+
+    // Create multiple keys to test the deleteAll() functionality.
+    $keys = array();
+    for ($i = 0; $i < 4; $i++) {
+      $key = $this->randomName();
+      $keys[] = $key;
+      $temp_store->set($key, $this->randomObject());
+      $stored_data = $temp_store->get($key);
+      $this->assertTrue(
+        !empty($stored_data),
+        format_string(
+          'Key %key is properly added to the store.',
+          array('%key' => $random_key)
+        )
+      );
+    }
+    $temp_store->deleteAll($keys);
+    foreach ($keys as $key) {
+      $this->assertFalse(
+        $temp_store->get($key),
+        format_string(
+          'Key %key is properly removed from the store.',
+          array('%key' => $random_key)
+        )
+      );
+    }
+
+    // Create two entries, one 10 days old and one 4 days old.
+    // 1 day = 86400 s.
+    $old_key = $this->randomName();
+    $new_key = $this->randomName();
+    $temp_store->set($old_key, $this->randomObject());
+    $temp_store->set($new_key, $this->randomObject());
+    // Manually change the updated time as this is not possible in the API.
+    foreach (array($old_key, $new_key) as $key) {
+      db_update('temp_store')
+        ->fields(array(
+          'updated' => $key == $old_key ? REQUEST_TIME - (10 * 86400) : REQUEST_TIME - (4 * 86400),
+        ))
+        ->condition('temp_key', $key)
+        ->execute();
+    }
+
+    // Clear objects more than 7 days old.
+    UserTempStore::clearOldObjects(7 * 86400);
+
+    // Confirm that the older entry is deleted and the newer is not.
+    $this->assertFalse(
+      $temp_store->get($old_key),
+      'An entry more than 7 days old is cleaned.'
+    );
+    $this->assertTrue(
+      $temp_store->get($new_key),
+      'An entry less than 7 days old is not cleaned.'
+    );
+
+    // Test the getLockOwner() method.
+    $temp_store_one = new UserTempStore($subsystem, $this->loggedInUser->uid);
+
+    // Allow two users to be logged in at once.
+    $this->curlClose();
+    $this->loggedInUser = FALSE;
+
+    $this->drupalLogin($this->secondUser);
+
+    // Add another stored object to the original user's data store.
+    $key_first_user = $this->randomName();
+    $temp_store_one->set($key_first_user, $this->randomObject());
+
+    // Add a data store for the second user and store an object to it.
+    $temp_store_two = new UserTempStore($subsystem, $this->loggedInUser->uid);
+    $key_second_user = $this->randomName();
+    $temp_store_two->set($key_second_user, $this->randomObject());
+
+    // Confirm that the data is correctly saved for both users.
+    $this->assertEqual(
+      $temp_store_one->getLockOwner($key_first_user)->ownerID,
+      $this->firstUser->uid,
+      format_string(
+        'Object %key is locked by user %user',
+        array('%key' => $key_first_user, '%user' => $this->firstUser->uid)
+      )
+    );
+    $this->assertEqual(
+      $temp_store_two->getLockOwner($key_second_user)->ownerID,
+      $this->secondUser->uid,
+      format_string(
+        'Object %key is locked by user %user',
+        array('%key' => $key_first_user, '%user' => $this->firstUser->uid)
+      )
+    );
+  }
+
+}
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 0e42214..3efb710 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -1373,6 +1373,45 @@ function system_schema() {
     'primary key' => array('mlid'),
   );
 
+  $schema['temp_store'] = array(
+    'description' => t('A temporary data store for objects that are being edited. Allows state to be saved in a stateless environment.'),
+    'fields' => array(
+      'owner_id' => array(
+        'type' => 'varchar',
+        'length' => '64',
+        'not null' => TRUE,
+        'description' => 'The session ID this object belongs to.',
+      ),
+      'subsystem' => array(
+        'type' => 'varchar',
+        'length' => '128',
+        'not null' => TRUE,
+        'description' => 'The owner (type of the object) for this data store. Allows multiple subsystems to use this data store.',
+      ),
+      'temp_key' => array(
+        'type' => 'varchar',
+        'length' => '128',
+        'not null' => TRUE,
+        'description' => 'The key of the object this data store is attached to.',
+      ),
+      'updated' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'The time this data store was created or updated.',
+      ),
+      'data' => array(
+        'type' => 'text',
+        'size' => 'big',
+        'description' => 'Serialized data being stored.',
+        'serialize' => TRUE,
+      ),
+    ),
+    'primary key' => array('owner_id', 'subsystem', 'temp_key'),
+    'indexes' => array('updated' => array('updated')),
+  );
+
   $schema['queue'] = array(
     'description' => 'Stores items in queues.',
     'fields' => array(
@@ -2047,6 +2086,52 @@ function system_update_8016() {
 }
 
 /**
+ * Create the 'temp_store' table.
+ */
+function system_update_8017() {
+  $table = array(
+    'description' => t('A temporary data store for objects that are being edited. Allows state to be saved in a stateless environment.'),
+    'fields' => array(
+      'owner_id' => array(
+        'type' => 'varchar',
+        'length' => '64',
+        'not null' => TRUE,
+        'description' => 'The session ID this object belongs to.',
+      ),
+      'subsystem' => array(
+        'type' => 'varchar',
+        'length' => '128',
+        'not null' => TRUE,
+        'description' => 'The owner (type of the object) for this data store. Allows multiple subsystems to use this data store.',
+      ),
+      'temp_key' => array(
+        'type' => 'varchar',
+        'length' => '128',
+        'not null' => TRUE,
+        'description' => 'The key of the object this data store is attached to.',
+      ),
+      'updated' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'The time this data store was created or updated.',
+      ),
+      'data' => array(
+        'type' => 'text',
+        'size' => 'big',
+        'description' => 'Serialized data being stored.',
+        'serialize' => TRUE,
+      ),
+    ),
+    'primary key' => array('owner_id', 'subsystem', 'temp_key'),
+    'indexes' => array('updated' => array('updated')),
+  );
+
+  db_create_table('temp_store', $table);
+}
+
+/**
  * @} End of "defgroup updates-7.x-to-8.x".
  * The next series of updates should start at 9000.
  */
