diff --git a/core/modules/config/config.module b/core/modules/config/config.module
index b3d9bbc..b284998 100644
--- a/core/modules/config/config.module
+++ b/core/modules/config/config.module
@@ -1 +1,74 @@
 <?php
+
+use Drupal\config\EntityListControllerInterface;
+use Drupal\entity\EntityInterface;
+
+/**
+ * Implements hook_menu().
+ */
+function config_menu() {
+  $items = array();
+  foreach (entity_get_info() as $entity_type => $type_info) {
+    if (isset($type_info['list controller class'])) {
+      $controller = config_get_list_controller($entity_type);
+      $items += $controller->hookMenu();
+    }
+  }
+  return $items;
+}
+
+/**
+ * Gets the entity list controller class for an entity type.
+ *
+ * @return Drupal\entity\EntityListControllerInterface
+ */
+function config_get_list_controller($entity_type) {
+  $controllers = &drupal_static(__FUNCTION__, array());
+  if (!isset($controllers[$entity_type])) {
+    $type_info = entity_get_info($entity_type);
+    $class = $type_info['list controller class'];
+    $controllers[$entity_type] = new $class($entity_type);
+  }
+  return $controllers[$entity_type];
+}
+
+/**
+ * Page callback: Displays a config listing page.
+ *
+ * @param Drupal\config\EntityListControllerInterface $controller
+ *   The list controller for this entity.
+ *
+ * @return string
+ *   The page markup for the page.
+ */
+function config_entity_listing_page($controller) {
+  return $controller->renderList();
+}
+
+/**
+ * Page callback: Calls a method on a config entity and reloads the listing page.
+ *
+ * @param Drupal\config\EntityListControllerInterface $controller
+ *   The list controller for this entity.
+ * @param Drupal\entity\EntityInterface $entity
+ *   The config entity being acted upon.
+ * @param string $op
+ *   The action to perform, e.g., 'enable' or 'disable'.
+ *
+ * @return mixed
+ *   Either returns the listing page as JSON, or calls drupal_goto() to
+ *   redirect back to the listing page.
+ */
+function config_ajax_callback(EntityListControllerInterface $controller, EntityInterface $entity, $op) {
+  // Perform the operation.
+  $entity->$op();
+
+  // If the request is via AJAX, return the rendered list as JSON.
+  if (drupal_container()->get('request')->request->get('js')) {
+    return $controller->renderListAJAX();
+  }
+  // Otherwise, redirect back to the page.
+  else {
+    drupal_goto($controller->getPath());
+  }
+}
diff --git a/core/modules/config/lib/Drupal/config/EntityListControllerBase.php b/core/modules/config/lib/Drupal/config/EntityListControllerBase.php
new file mode 100644
index 0000000..963de3c
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/EntityListControllerBase.php
@@ -0,0 +1,174 @@
+<?php
+
+/**
+ * Definition of Drupal\config\EntityListControllerBase.
+ */
+
+namespace Drupal\config;
+
+use Drupal\entity\EntityInterface;
+use Symfony\Component\HttpFoundation\JsonResponse;
+
+/**
+ * Abstract base class for config entity listing plugins.
+ */
+abstract class EntityListControllerBase implements EntityListControllerInterface {
+
+  /**
+   * The Config storage controller class.
+   *
+   * @var Drupal\config\ConfigStorageController
+   */
+  protected $storage;
+
+  /**
+   * The Config entity type.
+   *
+   * @var string
+   */
+  protected $entityType;
+
+  /**
+   * The Config entity info.
+   *
+   * @var array
+   */
+  protected $entityInfo;
+
+  /**
+   * If ajax links are used on the listing page.
+   *
+   * @var bool
+   */
+  protected $usesAJAX;
+
+  public function __construct($entity_type) {
+    $this->entityType = $entity_type;
+    $this->storage = entity_get_controller($this->entityType);
+    $this->entityInfo = entity_get_info($this->entityType);
+  }
+
+  /**
+   * Implements Drupal\config\EntityListControllerInterface::getList();
+   */
+  public function getList() {
+    return $this->storage->load();
+  }
+
+  /**
+   * Implements Drupal\config\EntityListControllerInterface::getStorageController();
+   */
+  public function getStorageController() {
+    return $this->storage;
+  }
+
+  public function getPath() {
+    return $this->entityInfo['list path'];
+  }
+
+  /**
+   * Implements Drupal\config\EntityListControllerInterface::hookMenu();
+   */
+  public function hookMenu() {
+    $items = array();
+    $items[$this->entityInfo['list path']] = array(
+      'page callback' => 'config_entity_listing_page',
+      'page arguments' => array($this),
+      // @todo Add a proper access callback here.
+      'access callback' => TRUE,
+    );
+    return $items;
+  }
+
+  /**
+   * Implements Drupal\config\EntityListControllerInterface::getRowData();
+   */
+  public function getRowData(EntityInterface $entity) {
+    $row = array();
+
+    $row['id'] = $entity->id();
+    $row['label'] = $entity->label();
+    $actions = $this->buildActionLinks($entity);
+    $row['actions'] = drupal_render($actions);
+
+    return $row;
+  }
+
+  /**
+   * Implements Drupal\config\EntityListControllerInterface::getHeaderData();
+   */
+  public function getHeaderData() {
+    $row = array();
+    $row['id'] = t('ID');
+    $row['label'] = t('Label');
+    $row['actions'] = t('Actions');
+    return $row;
+  }
+
+  /**
+   * Implements Drupal\config\EntityListControllerInterface::buildActionLinks();
+   */
+  public function buildActionLinks(EntityInterface $entity) {
+    $links = array();
+
+    foreach ($this->defineActionLinks($entity) as $definition) {
+      $attributes = array();
+
+      if (!empty($definition['ajax'])) {
+        $attributes['class'][] = 'use-ajax';
+        // Set this to true if we haven't already.
+        if (!isset($this->usesAJAX)) {
+          $this->usesAJAX = TRUE;
+        }
+      }
+
+      $links[] = array(
+        'title' => $definition['title'],
+        'href' => $definition['href'],
+        'attributes' => $attributes,
+      );
+    }
+
+    return array(
+      '#theme' => 'links',
+      '#links' => $links,
+    );
+  }
+
+  /**
+   * Implements Drupal\config\EntityListControllerInterface::renderList();
+   */
+  public function renderList() {
+    $rows = array();
+
+    foreach ($this->getList() as $entity) {
+      $rows[] = $this->getRowData($entity);
+    }
+
+    // Add core AJAX library if we need to.
+    if (!empty($this->usesAJAX)) {
+      drupal_add_library('system', 'drupal.ajax');
+    }
+
+    return array(
+      '#theme' => 'table',
+      '#header' => $this->getHeaderData(),
+      '#rows' => $rows,
+      '#attributes' => array(
+        'id' => 'config-entity-listing',
+      ),
+    );
+  }
+
+  /**
+   * Implements Drupal\config\EntityListControllerInterface::renderList();
+   */
+  public function renderListAJAX() {
+    $list = $this->renderList();
+    $commands = array();
+    $commands[] = ajax_command_replace('#config-entity-listing', drupal_render($list));
+
+    return new JsonResponse(ajax_render($commands));
+  }
+
+}
diff --git a/core/modules/config/lib/Drupal/config/EntityListControllerInterface.php b/core/modules/config/lib/Drupal/config/EntityListControllerInterface.php
new file mode 100644
index 0000000..cfc22b0
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/EntityListControllerInterface.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config\EntityListControllerInterface.
+ */
+
+namespace Drupal\config;
+
+use Drupal\entity\EntityInterface;
+
+/**
+ * Defines an interface for Configuration entity listing plugins.
+ */
+interface EntityListControllerInterface {
+
+  /*
+   * Returns a list of all available config entites of this type.
+   */
+  public function getList();
+
+  /**
+   * Gets the ConfigEntityController.
+   *
+   * @todo Put in correct namespace and docs here.
+   */
+  public function getStorageController();
+
+  /**
+   * Gets the hook_menu array item.
+   *
+   * @todo Put in correct docs here.
+   */
+  public function hookMenu();
+
+  /**
+   * Builds an array of data for each row.
+   *
+   * @param EntityInterface $entity
+   *
+   * @return array
+   *   An array of fields to use for this entity.
+   */
+  public function getRowData(EntityInterface $entity);
+
+  /**
+   * Builds the header row.
+   *
+   * @return array
+   *   An array of header strings.
+   */
+  public function getHeaderData();
+
+  /**
+   * Renders the list page markup to be output.
+   *
+   * @return string
+   *   The output markup for the listing page.
+   */
+  public function renderList();
+
+  /**
+   * Returns the list page as JSON.
+   *
+   * @return Symfony\Component\HttpFoundation\JsonResponse
+   *   AJAX commands to render the list.
+   */
+  public function renderListAJAX();
+
+  /**
+   * Renders a list of action links.
+   *
+   * @return array
+   */
+  public function buildActionLinks(EntityInterface $entity);
+
+  /**
+   * Provides an array of information to render action links.
+   *
+   * @return array
+   */
+  public function defineActionLinks(EntityInterface $entity);
+
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListingTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListingTest.php
new file mode 100644
index 0000000..683c8ab
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListingTest.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config\Tests\ConfigEntityListingTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\config\ConfigEntityBase;
+
+/**
+ * Tests configuration entities.
+ */
+class ConfigEntityListingTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('config_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Configuration entity list',
+      'description' => 'Tests configuration entity listing.',
+      'group' => 'Configuration',
+    );
+  }
+
+  /**
+   * Tests basic listing plugin functionilty.
+   */
+  function testListingPlugin() {
+    $controller = config_get_list_controller('config_test');
+
+    // Get a list of Config entities.
+    $list = $controller->getList();
+    $this->assertEqual(count($list), 1, 'Correct number of plugins found.');
+    $this->assertTrue(!empty($list['default']), '"Default" config entity key found in list.');
+    $this->assertTrue($list['default'] instanceof ConfigEntityBase, '"Default" config entity is an instance of ConfigEntityBase');
+  }
+
+  /**
+   * Tests the listing UI.
+   */
+  function testListingUI() {
+    $page = $this->drupalGet('config-listing-test');
+
+    // Test that the page exists.
+    $this->assertText('Config test', 'Config test listing page title found.');
+
+    // Check we have the default id and label on the page too.
+    $this->assertText('default', '"default" ID found.');
+    $this->assertText('Default', '"Default" label found');
+
+    // Check each link.
+    foreach (array('edit', 'add', 'delete') as $link) {
+      $this->drupalSetContent($page);
+      $this->assertLink($link);
+      $this->clickLink($link);
+      $this->assertResponse(200);
+    }
+
+    // @todo Test AJAX links.
+  }
+
+}
diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module
index 44d4087..40dd3fd 100644
--- a/core/modules/config/tests/config_test/config_test.module
+++ b/core/modules/config/tests/config_test/config_test.module
@@ -82,6 +82,8 @@ function config_test_entity_info() {
     'label' => 'Test configuration',
     'controller class' => 'Drupal\config\ConfigStorageController',
     'entity class' => 'Drupal\config_test\ConfigTest',
+    'list controller class' => 'Drupal\config_test\ConfigTestListController',
+    'list path' => 'config-listing-test',
     'uri callback' => 'config_test_uri',
     'config prefix' => 'config_test.dynamic',
     'entity keys' => array(
diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestListController.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestListController.php
new file mode 100644
index 0000000..5d89d56
--- /dev/null
+++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestListController.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * Definition of Drupal\config_test\ConfigTestListController.
+ */
+
+namespace Drupal\config_test;
+
+use Drupal\config\EntityListControllerBase;
+use Drupal\entity\EntityInterface;
+
+/**
+ * Views config entity listing controller.
+ */
+class ConfigTestListController extends EntityListControllerBase {
+
+  /**
+   * Overrides Drupal\config\EntityListControllerBase::hookMenu();
+   */
+  public function hookMenu() {
+    $path = $this->entityInfo['list path'];
+
+    $items = parent::hookMenu();
+    $items[$path]['title'] = 'Config test';
+    $items[$path]['description'] = 'Config test listing page.';
+    return $items;
+  }
+
+  /**
+   * Implements Drupal\config\EntityListControllerInterface::actionLinkMappings().
+   */
+  public function defineActionLinks(EntityInterface $entity) {
+    $id = $entity->id();
+
+    // @todo Add AJAX link to test.
+    return array(
+      'edit' => array(
+        'title' => 'edit',
+        'href' => "admin/structure/config_test/manage/$id/edit",
+        'ajax' => FALSE,
+      ),
+      'add' => array(
+        'title' => 'add',
+        'href' => "admin/structure/config_test/add",
+        'ajax' => FALSE,
+      ),
+      'delete' => array(
+        'title' => 'delete',
+        'href' => "admin/structure/config_test/manage/$id/delete",
+        'ajax' => FALSE,
+      ),
+    );
+  }
+
+}
