diff --git a/core/lib/Drupal/Core/Config/Entity/Display.php b/core/lib/Drupal/Core/Config/Entity/Display.php
new file mode 100644
index 0000000..5b65111
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Entity/Display.php
@@ -0,0 +1,233 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Entity\Display.
+ */
+
+namespace Drupal\Core\Config\Entity;
+
+use Drupal\layout\Plugin\LayoutInterface;
+
+/**
+ * Configuration encapsulator that provides all the data needed by block-driven
+ * controllers to render a page.
+ */
+class Display extends ConfigEntityBase implements DisplayInterface {
+
+  /**
+   * The ID (config name) identifying a specific display object.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The human-readable label of this display.
+   * @todo we probably don't need/want to have this.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The UUID identifying a specific display object.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The layout plugin instance being used to serve this page.
+   *
+   * @var \Drupal\layout\Plugin\LayoutInterface
+   */
+  protected $layoutPlugin;
+
+  /**
+   * The name of the layout plugin to use.
+   *
+   * @var string
+   */
+  public $layout;
+
+  /**
+   * An array of settings to be coupled with the layout plugin to create a
+   * layout plugin instance.
+   *
+   * @var array
+   */
+  public $layoutSettings = array();
+
+  /**
+   * Storage for data sources that have been statically assigned - that is, not
+   * derived in some way from the Request.
+   *
+   * The array is keyed on the name of a data source, and the values are getters
+   * that are able to retrieve the appropriate data.
+   *
+   * @todo define how the getters work.
+   *
+   * @var array
+   */
+  public $staticData = array();
+
+  /**
+   * Contains all block configuration.
+   *
+   * There are two levels to the configuration contained herein: display-level
+   * block configuration, and then block instance configuration.
+   *
+   * Block instance configuration is stored in a separate config object. This
+   * array is keyed by the config name that uniquely identifies each block
+   * instance. At runtime, various object methods will retrieve this additional
+   * config and return it to calling code.
+   *
+   * Display-level block configuration is data that determines the behavior of
+   * a block *in this display*. The most important examples of this are the
+   * region to which the block is assigned, and its weighting in that region.
+   *
+   * @code
+   *    array(
+   *      'block1-configkey' => array(
+   *        'region' => 'content',
+   *        // store the region role name here so that we can do role conversion w/out
+   *        // needing to have access to the original layout plugin
+   *        'region-role' => 'content',
+   *        // increment by 100 so there is ALWAYS plenty of space for manual insertion
+   *        'weight' => -100,
+   *      ),
+   *      'block2-configkey' => array(
+   *        'region' => 'sidebar_first',
+   *        'region-role' => 'sidebar',
+   *        'weight' => -100,
+   *      ),
+   *      'block2-configkey' => array(
+   *        'region' => 'sidebar_first',
+   *        'region-role' => 'sidebar',
+   *        'weight' => 0,
+   *      ),
+   *      'maincontent' => array(
+   *        'region' => 'content',
+   *        'region-role' => 'content',
+   *        'weight' => -200,
+   *      ),
+   *    );
+   * @endcode
+   *
+   * @var array
+   */
+  public $blockConfig = array();
+
+  protected $blocksInRegions;
+
+  public function __construct(array $values, $entity_type) {
+    $this->layout = $values['layout'];
+    // @todo oh so much, but really, blocks for now.
+    parent::__construct($values, 'display');
+  }
+
+  public function setMainContent($callback, array $args = array()) {
+    // @todo need to wrap the callback up in a passthru block
+    // @todo consider throwing an exception if this display instance doesn't appear to support main content
+  }
+
+  public function getAllBlockInfo() {
+    return $this->blockConfig;
+  }
+
+  public function getSortedBlocksByRegion($region = '') {
+    if ($this->blocksInRegions === NULL) {
+      $this->sortBlocks();
+    }
+
+    if (!isset($this->blocksInRegions[$region])) {
+      throw new \Exception(sprintf("Region %region does not exist in layout %layout", array('%region' => $region, '%layout' => $this->getLayoutPluginInstance()->name)), E_RECOVERABLE_ERROR);
+    }
+
+    return $this->blocksInRegions[$region];
+  }
+
+  public function getAllSortedBlocks() {
+    if ($this->blocksInRegions === NULL) {
+      $this->sortBlocks();
+    }
+
+    return $this->blocksInRegions;
+  }
+
+  /**
+   * Transform the stored blockConfig into a sorted, region-oriented array.
+   */
+  protected function sortBlocks() {
+    $layout_instance = $this->getLayoutPluginInstance();
+    if ($this->layout !== $layout_instance->name) {
+      $block_config = $this->remapBlocksToLayout($layout_instance);
+    }
+    else {
+      $block_config = $this->blockConfig;
+    }
+
+    $this->blocksInRegions = array_fill_keys(array_keys($layout_instance->getRegions()), array());
+    foreach ($block_config as $config_name => $info) {
+      $this->blocksInRegions[$info['weight']] = $config_name;
+    }
+
+    foreach ($this->blocksInRegions as &$region) {
+      krsort($region);
+    }
+  }
+
+  /**
+   * Remap the the saved blockConfig to work in a different layout.
+   *
+   * @todo this logic is a good default, but should be alterable.
+   *
+   * @param \Drupal\layout\Plugin\LayoutInterface $layout
+   *
+   * @return array
+   *   An array containing block configuration info, per the blockConfig property.
+   */
+  protected function remapBlocksToLayout(LayoutInterface $layout) {
+    $roles = array();
+
+    $layout_regions = $layout->getRegions();
+    $layout_regions_indexed = array_keys($layout_regions);
+    foreach ($layout_regions as $name => $info) {
+      $roles[$info['role']][] = $name;
+    }
+
+    $remapped_config = array();
+    foreach ($this->blockConfig as $name => $info) {
+      // First, if there's a direct region name match, use that.
+      if (!empty($layout_regions[$info['region']])) {
+        // No need to do anything.
+      }
+      // Then, try to remap using region roles.
+      else if (!empty($roles[$info['region-role']])) {
+        $info['region'] = $roles[$info['region-role']];
+      }
+      // Finally, fall back to dumping everything in the layout's first region.
+      else {
+        $info['region'] = reset($layout_regions_indexed);
+      }
+
+      $remapped_config[$name] = $info;
+    }
+
+    return $remapped_config;
+  }
+
+  public function getLayoutPluginInstance() {
+    if ($this->layoutPlugin === NULL) {
+      if (empty($this->layout)) {
+        throw new \Exception(sprintf('Display with id %id had no layout plugin attached.', array('%id' => $this->id)), E_RECOVERABLE_ERROR);
+      }
+
+      $this->layoutPlugin = layout_manager()->createInstance($this->layout);
+      // @todo add handling for remapping if the layout could not be found
+    }
+
+    return $this->layoutPlugin;
+  }
+}
diff --git a/core/lib/Drupal/Core/Config/Entity/DisplayInterface.php b/core/lib/Drupal/Core/Config/Entity/DisplayInterface.php
new file mode 100644
index 0000000..f114c1b
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Entity/DisplayInterface.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Entity\DisplayInterface
+ */
+interface DisplayInterface {
+
+  /**
+   * Introspects the contained config to determine whether or not this Display
+   * supports the 'main content' passthrough.
+   *
+   * @return bool
+   */
+  public function hasMainContent();
+
+  /**
+   * Sets the controller that should be used to serve the main page content.
+   *
+   * "Main page content" is analogous to the callback specified in
+   * 'page callback' in Drupal 6 and 7. In a blocks-driven world, such direct
+   * callbacks run a bit against the grain. However, the simplicity of
+   * implementing and understanding it is so beneficial to developer experience
+   * that we provide this 'main' content option, which simulates the old
+   * behavior by injecting a virtualized 'main' block that passes through
+   * to a specified callback.
+   *
+   * We normalize the main content block here in the configuration object so
+   * that various controllers consuming the display need not repeatedly
+   * implement this special handling for the main callback.
+   *
+   * @todo this is fairly specific to pages - maybe not best for the interface
+   *
+   * @param Callable $callback
+   *   Any form of callable.
+   * @param array $args
+   *   An array of arguments to pass to the callable.
+   */
+  public function setMainContent($callback, array $args = array());
+
+  /**
+   * Returns an indexed array of block config names, sorted by the order in
+   * which they should appear in the region.
+   *    *
+   * @param string $region
+   *   The region from which to return the set of blocks.
+   *
+   * @return array
+   */
+  public function getSortedBlocksByRegion($region = '');
+
+  /**
+   * Returns an array of arrays, keyed by region name and containing the
+   * same data as that which is returned by
+   * @see DisplayInterface::getSortedBlocksByRegion().
+   *
+   * @return mixed
+   */
+  public function getAllSortedBlocks();
+
+  /**
+   * Returns the config info about all blocks on this display.
+   *
+   * There are two levels of configuration that are being captured here: the
+   * configuration for the block itself (i.e., config generated by a user saving
+   * the block's edit form), and configuration for how the particular block
+   * instance behaves in *this* display. The former is typically its own config
+   * object, and only a reference to that config key is stored directly on this
+   * object. The most important examples of the latter are the region in which
+   * the block is placed, and its weighting within the region.
+   *
+   * @todo we need to determine how freestanding block config is.
+   *
+   * @return array
+   *   An array of block info, keyed on each block's config name.
+   */
+  public function getAllBlockInfo();
+
+  /**
+   * Returns the layout plugin instance to be used with this display.
+   *
+   * @return Drupal\layout\Plugin\LayoutInterface
+   */
+  public function getLayoutPluginInstance();
+}
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index ea3dadc..9d515fc 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -4118,6 +4118,29 @@ function system_archiver_info() {
 }
 
 /**
+ * Implements hook_entity_info().
+ *
+ * Provides the core Display object, which contains all the info necessary for
+ * block-driven pages.
+ */
+function system_entity_info() {
+  return array(
+    'display' => array(
+      'label' => t('Display'),
+      'entity class' => 'Drupal\Core\Config\Entity\Display',
+      'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController',
+      'config prefix' => 'display',
+      'fieldable' => FALSE,
+      'entity keys' => array(
+        'id' => 'id',
+        'label' => 'label', // @todo may not need this if internal-only (?)
+        'uuid' => 'uuid',
+      ),
+    )
+  );
+}
+
+/**
  * Returns HTML for a confirmation form.
  *
  * By default this does not alter the appearance of a form at all,
