diff --git a/core/modules/page/config/page.front_page.yml b/core/modules/page/config/page.front_page.yml
new file mode 100644
index 0000000000..7562328db4
--- /dev/null
+++ b/core/modules/page/config/page.front_page.yml
@@ -0,0 +1,8 @@
+id: front_page
+uuid: 5da81acf-3264-4d91-9b00-b2461c86e974
+label: 'Front page'
+visibility: '1'
+paths: '<front>'
+layout: 'static_layout:page__complex'
+langcode: und
+weight: 1
diff --git a/core/modules/page/config/page.not_admin_page.yml b/core/modules/page/config/page.not_admin_page.yml
new file mode 100644
index 0000000000..c7d1032332
--- /dev/null
+++ b/core/modules/page/config/page.not_admin_page.yml
@@ -0,0 +1,8 @@
+id: not_admin_page
+uuid: 45e82840-d11b-4726-8d09-9be84a69457d
+label: 'Not admin page'
+visibility: '0'
+paths: "admin\r\nadmin/*"
+layout: 'static_layout:page__simple'
+langcode: und
+weight: 10
diff --git a/core/modules/page/config/page.user_page.yml b/core/modules/page/config/page.user_page.yml
new file mode 100644
index 0000000000..ce86333cae
--- /dev/null
+++ b/core/modules/page/config/page.user_page.yml
@@ -0,0 +1,8 @@
+id: user_page
+uuid: ccd213ff-54d4-4f3a-9a00-f8406842dff0
+label: 'User page'
+visibility: '1'
+paths: "user\r\nuser/*"
+layout: 'static_layout:page__complex'
+langcode: und
+weight: 5
diff --git a/core/modules/page/layouts/static/complex/complex.yml b/core/modules/page/layouts/static/complex/complex.yml
new file mode 100644
index 0000000000..ec6491aff8
--- /dev/null
+++ b/core/modules/page/layouts/static/complex/complex.yml
@@ -0,0 +1,2 @@
+title: Complex layout
+template: complex
diff --git a/core/modules/page/layouts/static/simple/simple.yml b/core/modules/page/layouts/static/simple/simple.yml
new file mode 100644
index 0000000000..3c395fa197
--- /dev/null
+++ b/core/modules/page/layouts/static/simple/simple.yml
@@ -0,0 +1,2 @@
+title: Simple layout
+template: simple
diff --git a/core/modules/page/lib/Drupal/page/PageFormController.php b/core/modules/page/lib/Drupal/page/PageFormController.php
new file mode 100644
index 0000000000..d86404a01a
--- /dev/null
+++ b/core/modules/page/lib/Drupal/page/PageFormController.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\page\PageFormController.
+ */
+
+namespace Drupal\page;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityFormController;
+use Drupal\page\Plugin\Core\Entity\Page;
+
+/**
+ * Form controller for the page edit/add forms.
+ */
+class PageFormController extends EntityFormController {
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::form().
+   */
+  public function form(array $form, array &$form_state, EntityInterface $page) {
+    $form['label'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Label'),
+      '#maxlength' => 255,
+      '#default_value' => $page->label(),
+      '#description' => t("Example: 'Front page' or 'Section page'."),
+      '#required' => TRUE,
+    );
+    $form['id'] = array(
+      '#type' => 'machine_name',
+      '#default_value' => $page->id(),
+      '#machine_name' => array(
+        'exists' => 'page_load',
+        'source' => array('label'),
+      ),
+      '#disabled' => !$page->isNew(),
+    );
+
+    // Get list of layouts and expose that for page layout selection.
+    $layouts = drupal_container()->get('plugin.manager.layout')->getDefinitions();
+    $layout_options = array();
+    foreach ($layouts as $key => $layout) {
+      $layout_options[$key] = $layout['title'];
+    }
+    $form['layout'] = array(
+      '#type' => 'select',
+      '#title' => t('Layout for this page'),
+      '#default_value' => isset($page->layout) ? $page->layout : '',
+      '#options' => $layout_options,
+    );
+
+    // @todo this would ideally be pluggable and depend on general conditions
+    // and all, however, these are not yet abstracted.
+    $options = array(
+      0 => t('All paths except those listed'),
+      1 => t('Only the listed paths'),
+    );
+    $description = t("Enter one path per line. The '*' character is a wildcard. Example paths are %user for the current user's page and %user-wildcard for every user page. %front is the front page.", array('%user' => 'user', '%user-wildcard' => 'user/*', '%front' => '<front>'));
+    $form['visibility'] = array(
+      '#type' => 'radios',
+      '#title' => t('Apply to specific paths'),
+      '#options' => $options,
+      '#default_value' => isset($page->visibility) ? $page->visibility : 0,
+    );
+    $form['paths'] = array(
+      '#type' => 'textarea',
+      '#title' => '<span class="element-invisible">' . t('Paths') . '</span>',
+      '#default_value' => isset($page->paths) ? $page->paths : '',
+      '#description' => $description,
+    );
+
+    return parent::form($form, $form_state, $page);
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::actions().
+   */
+  protected function actions(array $form, array &$form_state) {
+    // Only includes a Save action for the entity, no direct Delete button.
+    return array(
+      'submit' => array(
+        '#value' => t('Save'),
+        '#validate' => array(
+          array($this, 'validate'),
+        ),
+        '#submit' => array(
+          array($this, 'submit'),
+          array($this, 'save'),
+        ),
+      ),
+    );
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::save().
+   */
+  public function save(array $form, array &$form_state) {
+    $page = $this->getEntity($form_state);
+    $page->save();
+
+    watchdog('page', 'Page @label saved.', array('@label' => $page->label()), WATCHDOG_NOTICE);
+    drupal_set_message(t('Page %label saved.', array('%label' => $page->label())));
+
+    $form_state['redirect'] = 'admin/structure/pages';
+  }
+
+}
+
diff --git a/core/modules/page/lib/Drupal/page/PageListController.php b/core/modules/page/lib/Drupal/page/PageListController.php
new file mode 100644
index 0000000000..97c0955436
--- /dev/null
+++ b/core/modules/page/lib/Drupal/page/PageListController.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\page\PageListController.
+ */
+
+namespace Drupal\page;
+
+use Drupal\Core\Config\Entity\ConfigEntityListController;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\page\Plugin\Core\Entity\Page;
+
+/**
+ * Provides a listing of pages.
+ */
+class PageListController extends ConfigEntityListController {
+
+  /**
+   * Get form for listing page to display for weights.
+   */
+  public function getForm($form, &$form_state) {
+    $form['items'] = array(
+      '#tree' => TRUE,
+    );
+    foreach ($this->load() as $entity) {
+      $form['items'][$entity->id()]['label'] = array(
+        '#markup' => check_plain($entity->label()),
+      );
+
+      $paths = nl2br(check_plain($entity->paths));
+      if (empty($entity->visibility)) {
+        $paths = t('Except: !paths', array('!paths' => '<br />' . $paths));
+      }
+      $form['items'][$entity->id()]['paths'] = array(
+        '#markup' => $paths,
+      );
+
+      $form['items'][$entity->id()]['weight'] = array(
+        '#type' => 'weight',
+        '#title' => t('Weight'),
+        '#default_value' => $entity->weight,
+        '#delta' => 100,
+        '#attributes' => array('class' => array('entity-weight')),
+      );
+
+      $form['items'][$entity->id()]['operations'] = array(
+        $this->buildOperations($entity),
+      );
+    }
+
+    $form['actions'] = array();
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save changes')
+    );
+    return $form;
+  }
+
+  /**
+   * Save weight changes to configuration entities.
+   */
+  public function submitForm($form, &$form_state) {
+    foreach ($form_state['values']['items'] as $id => $item) {
+      $page = entity_load('page', $id);
+      $page->weight = $item['weight'];
+      $page->save();
+    }
+  }
+
+}
diff --git a/core/modules/page/lib/Drupal/page/Plugin/Core/Entity/Page.php b/core/modules/page/lib/Drupal/page/Plugin/Core/Entity/Page.php
new file mode 100644
index 0000000000..9627354368
--- /dev/null
+++ b/core/modules/page/lib/Drupal/page/Plugin/Core/Entity/Page.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\page\Plugin\Core\Entity\Page.
+ */
+
+namespace Drupal\page\Plugin\Core\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+
+/**
+ * Defines the page entity class.
+ *
+ * @Plugin(
+ *   id = "page",
+ *   label = @Translation("Page"),
+ *   module = "page",
+ *   controller_class =  "Drupal\Core\Config\Entity\ConfigStorageController",
+ *   form_controller_class = {
+ *     "default" = "Drupal\page\PageFormController",
+ *   },
+ *   list_controller_class = "Drupal\page\PageListController",
+ *   list_path = "admin/structure/pages",
+ *   uri_callback = "page_uri",
+ *   config_prefix = "page",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid",
+ *   }
+ * )
+ */
+class Page extends ConfigEntityBase {
+
+  /**
+   * The page ID (machine name).
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The page UUID.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The page label.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * Whether to apply the path patterns or exclude them.
+   *
+   * @var int
+   */
+  public $visibility;
+
+  /**
+   * Path pattern associated with this page.
+   *
+   * @var string
+   */
+  public $paths;
+
+  /**
+   * Layout machine name associated with this page.
+   *
+   * @var string
+   */
+  public $layout;
+
+  /**
+   * Page weight to help figure out priority ordering.
+   *
+   * @var int
+   */
+  public $weight;
+
+}
diff --git a/core/modules/page/page.admin.inc b/core/modules/page/page.admin.inc
new file mode 100644
index 0000000000..1cb4b6103c
--- /dev/null
+++ b/core/modules/page/page.admin.inc
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @file
+ * Administration functions to maintain a set of pages using layouts.
+ */
+
+use Drupal\page\Plugin\Core\Entity\Page;
+
+/**
+ * Page callback: Presents list of pages.
+ *
+ * @see page_menu()
+ */
+function page_page_list() {
+  return drupal_get_form('page_page_list_form');
+}
+
+/**
+ * Form callback to present a tabledrag list.
+ */
+function page_page_list_form($form, &$form_state) {
+  $controller = entity_list_controller('page');
+  return $controller->getForm($form, $form_state);
+}
+
+
+/**
+ * Form callback to save a tabledrag list.
+ */
+function page_page_list_form_submit($form, &$form_state) {
+  $controller = entity_list_controller('page');
+  return $controller->submitForm($form, $form_state);
+}
+
+/**
+ * Page callback: Presents the page editing form.
+ *
+ * @see page_menu()
+ */
+function page_page_edit(Page $page) {
+  drupal_set_title(t('<em>Edit page</em> @label', array('@label' => $page->label())), PASS_THROUGH);
+  return entity_get_form($page);
+}
+
+/**
+ * Page callback: Provides the new page addition form.
+ *
+ * @see page_menu()
+ */
+function page_page_add() {
+  $page = entity_create('page', array());
+  return entity_get_form($page);
+}
+
+/**
+ * Page callback: Form constructor for page deletion confirmation form.
+ *
+ * @see page_menu()
+ */
+function page_confirm_delete($form, &$form_state, Page $page) {
+  // Always provide entity id in the same form key as in the entity edit form.
+  $form['id'] = array('#type' => 'value', '#value' => $page->id());
+  $form_state['page'] = $page;
+  return confirm_form($form,
+    t('Are you sure you want to remove the page %title?', array('%title' => $page->label())),
+    'admin/structure/pages',
+    t('This action cannot be undone.'),
+    t('Delete'),
+    t('Cancel')
+  );
+}
+
+/**
+ * Form submission handler for page_confirm_delete().
+ */
+function page_confirm_delete_submit($form, &$form_state) {
+  $page = $form_state['page'];
+  $page->delete();
+  drupal_set_message(t('Page %label has been deleted.', array('%label' => $page->label())));
+  watchdog('page', 'Page %label has been deleted.', array('%label' => $page->label()), WATCHDOG_NOTICE);
+  $form_state['redirect'] = 'admin/structure/pages';
+}
+
+/**
+ * Theme callback for page listing form.
+ */
+function theme_page_page_list_form($variables) {
+  $form = $variables['form'];
+  $rows = array();
+
+  foreach (element_children($form['items'], TRUE) as $id) {
+    $rows[] = array(
+      'data' => array(
+        drupal_render($form['items'][$id]['label']),
+        drupal_render($form['items'][$id]['paths']),
+        drupal_render($form['items'][$id]['weight']),
+        drupal_render($form['items'][$id]['operations']),
+      ),
+      'class' => array('draggable'),
+    );
+  }
+
+  $header = array(
+    t('Label'),
+    t('Paths'),
+    t('Weight'),
+    t('Operations'),
+  );
+
+  $variables = array(
+    'header' => $header,
+    'rows' => $rows,
+    'attributes' => array('id' => 'page-list'),
+  );
+  $table  = theme('table', $variables);
+  $table .= drupal_render_children($form);
+  drupal_add_tabledrag('page-list', 'order', 'sibling', 'entity-weight');
+  return $table;
+}
diff --git a/core/modules/page/page.info b/core/modules/page/page.info
new file mode 100644
index 0000000000..5abbdce26b
--- /dev/null
+++ b/core/modules/page/page.info
@@ -0,0 +1,8 @@
+name = Page
+description = Makes it possible to swap different page layouts.
+package = Core
+version = VERSION
+core = 8.x
+dependencies[] = config
+dependencies[] = layout
+configure = admin/structure/pages
diff --git a/core/modules/page/page.module b/core/modules/page/page.module
new file mode 100644
index 0000000000..88905540ab
--- /dev/null
+++ b/core/modules/page/page.module
@@ -0,0 +1,161 @@
+<?php
+
+/**
+ * @file
+ * Module to maintain a set of pages using layouts.
+ */
+
+use Drupal\page\Plugin\Core\Entity\Page;
+
+/**
+ * Implements hook_help().
+ */
+function page_help($path, $arg) {
+  switch($path) {
+    case 'admin/help#page':
+      return '<p>' . t('The page library lets you assign layouts to page patterns. Layouts are either provided by themes or modules.') . '</p>';
+  }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function page_menu() {
+  $items['admin/structure/pages'] = array(
+    'title' => 'Page library',
+    'description' => 'Manage pages using layouts that allow content to be placed.',
+    'page callback' => 'page_page_list',
+    'access callback' => 'user_access',
+    'access arguments' => array('administer pages'),
+    'file' => 'page.admin.inc',
+  );
+  $items['admin/structure/pages/add'] = array(
+    'title' => 'Add page',
+    'page callback' => 'page_page_add',
+    'access callback' => 'user_access',
+    'access arguments' => array('administer pages'),
+    'type' => MENU_LOCAL_ACTION,
+    'file' => 'page.admin.inc',
+  );
+  $items['admin/structure/pages/manage/%page'] = array(
+    'title' => 'Edit page',
+    'page callback' => 'page_page_edit',
+    'page arguments' => array(4),
+    'access callback' => 'user_access',
+    'access arguments' => array('administer pages'),
+    'type' => MENU_CALLBACK,
+    'file' => 'page.admin.inc',
+  );
+  $items['admin/structure/pages/manage/%page/edit'] = array(
+    'title' => 'Edit',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/structure/pages/manage/%page/delete'] = array(
+    'title' => 'Delete',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('page_confirm_delete', 4),
+    'access callback' => 'user_access',
+    'access arguments' => array('administer pages'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'page.admin.inc',
+  );
+  return $items;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function page_permission() {
+  return array(
+    'administer pages' => array(
+      'title' => t('Administer pages'),
+      'description' => t('Manage the set of pages with distinct layouts on the site.'),
+    ),
+  );
+}
+
+/**
+ * @todo remove this in favor of actually integrating with layout mappers.
+ */
+function page_init() {
+  if ($page = page_get_matched_page()) {
+    drupal_set_message(t('%page matched, %layout to be used.', array('%page' => $page->label(), '%layout' => $page->layout)));
+  }
+}
+
+/**
+ * API function to look up if the current page is matched by a config.
+ *
+ * @return Drupal\page\Page|FALSE
+ */
+function page_get_matched_page() {
+  $pages = page_load_all();
+  $page_match = FALSE;
+  foreach ($pages as $page) {
+    if (!empty($page->paths)) {
+      // Convert paths to lowercase. This allows comparison of the same path
+      // with different case. Ex: /Page, /page, /PAGE.
+      $paths = drupal_strtolower($page->paths);
+      // Compare the lowercase path alias (if any) and internal path.
+      $path = current_path();
+      $path_alias = drupal_strtolower(drupal_get_path_alias($path));
+      $page_match = drupal_match_path($path_alias, $paths) || (($path != $path_alias) && drupal_match_path($path, $paths));
+      // When $page->visibility has a value of 0, the page is used on all
+      // paths except those listed in $page->paths. When set to 1, it is
+      // used only on those pages listed in $page->paths.
+      $page_match = !($page->visibility xor $page_match);
+      if ($page_match) {
+        break;
+      }
+    }
+  }
+  return $page_match ? $page : FALSE;
+}
+
+/**
+ * Entity URI callback.
+ *
+ * @param Drupal\page\Page $page
+ *   Page configuration entity instance.
+ *
+ * @return array
+ *   Entity URI information.
+ */
+function page_uri(Page $page) {
+  return array(
+    'path' => 'admin/structure/pages/manage/' . $page->id(),
+  );
+}
+
+/**
+ * Load one page object by its identifier.
+ *
+ * @return Drupal\page\Page
+ *   Page configuration entity instance.
+ */
+function page_load($id) {
+  return entity_load('page', $id);
+}
+
+/**
+ * Load all page objects.
+ *
+ * @return array
+ *   List of Drupal\page\Page instances keyed by id.
+ */
+function page_load_all() {
+  return entity_load_multiple('page');
+}
+
+/**
+ * Implements hook_theme().
+ */
+function page_theme($existing, $type, $theme, $path) {
+  return array(
+    'page_page_list_form' => array(
+      'render element' => 'form',
+      'file' => 'page.admin.inc',
+    ),
+  );
+}
