diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php
index 102fa70..9c9d123 100644
--- a/core/includes/entity.api.php
+++ b/core/includes/entity.api.php
@@ -34,6 +34,10 @@
  *     different operations, the name of the operation is passed also to the
  *     constructor of the form controller class. This way, one class can be used
  *     for multiple entity forms.
+ *   - list controller class: The name of the class that is used to provide
+ *     listings of the entity. The class must implement
+ *     Drupal\Core\Entity\EntityListControllerInterface. Defaults to
+ *     Drupal\Core\Entity\EntityListController.
  *   - base table: (used by Drupal\Core\Entity\DatabaseStorageController) The
  *     name of the entity type's base table.
  *   - static cache: (used by Drupal\Core\Entity\DatabaseStorageController)
diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index b651e32..2b504aa 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -47,6 +47,7 @@ function entity_get_info($entity_type = NULL) {
           'fieldable' => FALSE,
           'entity class' => 'Drupal\Core\Entity\Entity',
           'controller class' => 'Drupal\Core\Entity\DatabaseStorageController',
+          'list controller class' => 'Drupal\Core\Entity\EntityListController',
           'form controller class' => array(
             'default' => 'Drupal\Core\Entity\EntityFormController',
           ),
@@ -530,3 +531,21 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st
     field_attach_submit($entity_type, $entity, $form, $form_state);
   }
 }
+
+/**
+ * Returns an entity list controller for a given entity type.
+ *
+ * @param string $entity_type
+ *   The type of the entity.
+ *
+ * @return Drupal\Core\Entity\EntityListControllerInterface
+ *   An entity list controller.
+ *
+ * @see hook_entity_info()
+ */
+function entity_list_controller($entity_type) {
+  $storage = entity_get_controller($entity_type);
+  $entity_info = entity_get_info($entity_type);
+  $class = $entity_info['list controller class'];
+  return new $class($entity_type, $storage);
+}
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
new file mode 100644
index 0000000..3bd4b82
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Entity\ConfigEntityListController.
+ */
+
+namespace Drupal\Core\Config\Entity;
+
+use Drupal\Core\Entity\EntityListController;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Defines the default list controller for ConfigEntity objects.
+ */
+class ConfigEntityListController extends EntityListController {
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityListController::load().
+   */
+  public function load() {
+    $entities = parent::load();
+    uasort($entities, 'Drupal\Core\Config\Entity\ConfigEntityBase::sort');
+    return $entities;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityListController.php b/core/lib/Drupal/Core/Entity/EntityListController.php
new file mode 100644
index 0000000..508db1c
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityListController.php
@@ -0,0 +1,162 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Entity\EntityListController.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Provides a generic implementation of an entity list controller.
+ */
+class EntityListController implements EntityListControllerInterface {
+
+  /**
+   * The entity storage controller class.
+   *
+   * @var Drupal\Core\Entity\EntityStorageControllerInterface
+   */
+  protected $storage;
+
+  /**
+   * The entity type name.
+   *
+   * @var string
+   */
+  protected $entityType;
+
+  /**
+   * The entity info array.
+   *
+   * @var array
+   *
+   * @see entity_get_info()
+   */
+  protected $entityInfo;
+
+  /**
+   * Constructs a new EntityListController object.
+   *
+   * @param string $entity_type.
+   *   The type of entity to be listed.
+   * @param Drupal\Core\Entity\EntityStorageControllerInterface $storage.
+   *   The entity storage controller class.
+   */
+  public function __construct($entity_type, EntityStorageControllerInterface $storage) {
+    $this->entityType = $entity_type;
+    $this->storage = $storage;
+    $this->entityInfo = entity_get_info($this->entityType);
+  }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityListControllerInterface::getStorageController().
+   */
+  public function getStorageController() {
+    return $this->storage;
+  }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityListControllerInterface::load().
+   */
+  public function load() {
+    return $this->storage->load();
+  }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityListControllerInterface::getOperations().
+   */
+  public function getOperations(EntityInterface $entity) {
+    $uri = $entity->uri();
+    $operations['edit'] = array(
+      'title' => t('Edit'),
+      'href' => $uri['path'] . '/edit',
+      'options' => $uri['options'],
+      'weight' => 10,
+    );
+    $operations['delete'] = array(
+      'title' => t('Delete'),
+      'href' => $uri['path'] . '/delete',
+      'options' => $uri['options'],
+      'weight' => 100,
+    );
+    return $operations;
+  }
+
+  /**
+   * Builds the header row for the entity listing.
+   *
+   * @return array
+   *   A render array structure of header strings.
+   *
+   * @see Drupal\Core\Entity\EntityListController::render()
+   */
+  public function buildHeader() {
+    $row['label'] = t('Label');
+    $row['id'] = t('Machine name');
+    $row['operations'] = t('Operations');
+    return $row;
+  }
+
+  /**
+   * Builds a row for an entity in the entity listing.
+   *
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   The entity for this row of the list.
+   *
+   * @return array
+   *   A render array structure of fields for this entity.
+   *
+   * @see Drupal\Core\Entity\EntityListController::render()
+   */
+  public function buildRow(EntityInterface $entity) {
+    $row['label'] = $entity->label();
+    $row['id'] = $entity->id();
+    $operations = $this->buildOperations($entity);
+    $row['operations']['data'] = $operations;
+    return $row;
+  }
+
+  /**
+   * Builds a renderable list of operation links for the entity.
+   *
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   The entity on which the linked operations will be performed.
+   *
+   * @return array
+   *   A renderable array of operation links.
+   *
+   * @see Drupal\Core\Entity\EntityListController::render()
+   */
+  public function buildOperations(EntityInterface $entity) {
+    // Retrieve and sort operations.
+    $operations = $this->getOperations($entity);
+    uasort($operations, 'drupal_sort_weight');
+    $build = array(
+      '#theme' => 'links',
+      '#links' => $operations,
+    );
+    return $build;
+  }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityListControllerInterface::render().
+   *
+   * Builds the entity list as renderable array for theme_table().
+   *
+   * @todo Add a link to add a new item to the #empty text.
+   */
+  public function render() {
+    $build = array(
+      '#theme' => 'table',
+      '#header' => $this->buildHeader(),
+      '#rows' => array(),
+      '#empty' => t('There is no @label yet.', array('@label' => $this->entityInfo['label'])),
+    );
+    foreach ($this->load() as $entity) {
+      $build['#rows'][$entity->id()] = $this->buildRow($entity);
+    }
+    return $build;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php
new file mode 100644
index 0000000..85790c3
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Entity\EntityListControllerInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Defines an interface for entity list controllers.
+ */
+interface EntityListControllerInterface {
+
+  /**
+   * Gets the entity storage controller.
+   *
+   * @return Drupal\Core\Entity\EntityStorageControllerInterface
+   *   The storage controller used by this list controller.
+   */
+  public function getStorageController();
+
+  /**
+   * Loads entities of this type from storage for listing.
+   *
+   * This allows the controller to manipulate the list, like filtering or
+   * sorting the loaded entities.
+   *
+   * @return array
+   *   An array of entities implementing Drupal\Core\Entity\EntityInterface.
+   */
+  public function load();
+
+  /**
+   * Provides an array of information to build a list of operation links.
+   *
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   The entity the operations are for.
+   *
+   * @return array
+   *   An associative array of operation link data for this list, keyed by
+   *   operation name, containing the following key-value pairs:
+   *   - title: The localized title of the operation.
+   *   - href: The path for the operation.
+   *   - options: An array of URL options for the path.
+   *   - weight: The weight of this operation.
+   */
+  public function getOperations(EntityInterface $entity);
+
+  /**
+   * Renders the list page markup to be output.
+   *
+   * @return string
+   *   The output markup for the listing page.
+   */
+  public function render();
+
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php
new file mode 100644
index 0000000..661d87d
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php
@@ -0,0 +1,206 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config\Tests\ConfigEntityListTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\config_test\ConfigTest;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+
+/**
+ * Tests the listing of configuration entities.
+ */
+class ConfigEntityListTest 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 the listing of configuration entities.',
+      'group' => 'Configuration',
+    );
+  }
+
+  /**
+   * Tests entity list controller methods.
+   */
+  function testList() {
+    $controller = entity_list_controller('config_test');
+
+    // Test getStorageController() method.
+    $this->assertTrue($controller->getStorageController() instanceof EntityStorageControllerInterface, 'EntityStorageController instance in storage.');
+
+    // Get a list of ConfigTest entities and confirm that it contains the
+    // ConfigTest entity provided by the config_test module.
+    // @see config_test.dynamic.default.yml
+    $list = $controller->load();
+    $this->assertEqual(count($list), 1, '1 ConfigTest entity found.');
+    $entity = $list['default'];
+    $this->assertTrue(!empty($entity), '"Default" ConfigTest entity ID found.');
+    $this->assertTrue($entity instanceof ConfigTest, '"Default" ConfigTest entity is an instance of ConfigTest.');
+
+    // Test getOperations() method.
+    $uri = $entity->uri();
+    $expected_operations = array(
+      'edit' => array (
+        'title' => 'Edit',
+        'href' => 'admin/structure/config_test/manage/default/edit',
+        'options' => $uri['options'],
+        'weight' => 10,
+      ),
+      'delete' => array (
+        'title' => 'Delete',
+        'href' => 'admin/structure/config_test/manage/default/delete',
+        'options' => $uri['options'],
+        'weight' => 100,
+      ),
+    );
+    $actual_operations = $controller->getOperations($entity);
+    $this->assertIdentical($expected_operations, $actual_operations, 'Return value from getOperations matches expected.');
+
+    // Test buildHeader() method.
+    $expected_items = array(
+      'label' => 'Label',
+      'id' => 'Machine name',
+      'operations' => 'Operations',
+    );
+    $actual_items = $controller->buildHeader();
+    $this->assertIdentical($expected_items, $actual_items, 'Return value from buildHeader matches expected.');
+
+    // Test buildRow() method.
+    $build_operations = $controller->buildOperations($entity);
+    $expected_items = array(
+      'label' => 'Default',
+      'id' => 'default',
+      'operations' => array(
+        'data' => $build_operations,
+      ),
+    );
+    $actual_items = $controller->buildRow($entity);
+    $this->assertIdentical($expected_items, $actual_items, 'Return value from buildRow matches expected.');
+
+    // Test sorting.
+    $entity = entity_create('config_test', array(
+      'id' => 'albatros',
+      'label' => 'Albatros',
+      'weight' => 1,
+    ));
+    $entity->save();
+    $entity = entity_create('config_test', array(
+      'id' => 'antelope',
+      'label' => 'Antelope',
+      'weight' => 1,
+    ));
+    $entity->save();
+    $list = $controller->load();
+    $entity = end($list);
+    $this->assertEqual($entity->weight, 1);
+  }
+
+  /**
+   * Tests the listing UI.
+   */
+  function testListUI() {
+    // Log in as an administrative user to access the full menu trail.
+    $this->drupalLogin($this->drupalCreateUser(array('access administration pages')));
+
+    // Get the list callback page.
+    $this->drupalGet('admin/structure/config_test');
+
+    // Test for the page title.
+    $this->assertTitle('Test configuration | Drupal');
+
+    // Test for the table.
+    $element = $this->xpath('//div[@id="content"]//table');
+    $this->assertTrue($element, 'Configuration entity list table found.');
+
+    // Test the table header.
+    $elements = $this->xpath('//div[@id="content"]//table/thead/tr/th');
+    $this->assertEqual(count($elements), 3, 'Correct number of table header cells found.');
+
+    // Test the contents of each th cell.
+    $expected_items = array('Label', 'Machine name', 'Operations');
+    foreach ($elements as $key => $element) {
+      $this->assertIdentical((string) $element[0], $expected_items[$key]);
+    }
+
+    // Check the number of table row cells.
+    $elements = $this->xpath('//div[@id="content"]//table/tbody/tr[@class="odd"]/td');
+    $this->assertEqual(count($elements), 3, 'Correct number of table row cells found.');
+
+    // Check the contents of each row cell. The first cell contains the label,
+    // the second contains the machine name, and the third contains the
+    // operations list.
+    $this->assertIdentical((string) $elements[0], 'Default');
+    $this->assertIdentical((string) $elements[1], 'default');
+    $this->assertTrue($elements[2]->children()->xpath('//ul'), 'Operations list found.');
+
+    // Add a new entity using the operations link.
+    $this->assertLink('Add test configuration');
+    $this->clickLink('Add test configuration');
+    $this->assertResponse(200);
+    $edit = array('label' => 'Antelope', 'id' => 'antelope');
+    $this->drupalPost(NULL, $edit, t('Save'));
+
+    // Confirm that the user is returned to the listing, and verify that the
+    // text of the label and machine name appears in the list (versus elsewhere
+    // on the page).
+    $this->assertFieldByXpath('//td', 'Antelope', "Label found for added 'Antelope' entity.");
+    $this->assertFieldByXpath('//td', 'antelope', "Machine name found for added 'Antelope' entity.");
+
+    // Edit the entity using the operations link.
+    $this->assertLink('Edit');
+    $this->clickLink('Edit');
+    $this->assertResponse(200);
+    $this->assertTitle('Edit test configuration | Drupal');
+    $edit = array(
+      'label' => 'Albatross',
+      'id' => 'albatross',
+      'weight' => 1,
+    );
+    $this->drupalPost(NULL, $edit, t('Save'));
+
+    // Confirm that the user is returned to the listing, and verify that the
+    // text of the label and machine name appears in the list (versus elsewhere
+    // on the page).
+    $this->assertFieldByXpath('//td', 'Albatross', "Label found for updated 'Albatross' entity.");
+    $this->assertFieldByXpath('//td', 'albatross', "Machine name found for updated 'Albatross' entity.");
+
+    // Delete the added entity using the operations link.
+    $this->assertLink('Delete', 1);
+    $this->clickLink('Delete', 1);
+    $this->assertResponse(200);
+    $this->assertTitle('Are you sure you want to delete Albatross | Drupal');
+    $this->drupalPost(NULL, array(), t('Delete'));
+
+    // Verify that the text of the label and machine name does not appear in
+    // the list (though it may appear elsewhere on the page).
+    $this->assertNoFieldByXpath('//td', 'Albatross', "No label found for deleted 'Albatross' entity.");
+    $this->assertNoFieldByXpath('//td', 'albatross', "No machine name found for deleted 'Albatross' entity.");
+
+    // Delete the original entity using the operations link.
+    $this->clickLink('Delete');
+    $this->assertResponse(200);
+    $this->assertTitle('Are you sure you want to delete Default | Drupal');
+    $this->drupalPost(NULL, array(), t('Delete'));
+
+    // Verify that the text of the label and machine name does not appear in
+    // the list (though it may appear elsewhere on the page).
+    $this->assertNoFieldByXpath('//td', 'Default', "No label found for deleted 'Default' entity.");
+    $this->assertNoFieldByXpath('//td', 'default', "No machine name found for deleted 'Default' entity.");
+
+    // Confirm that the empty text is displayed.
+    $this->assertText('There is no Test configuration yet.');
+  }
+
+}
diff --git a/core/modules/config/tests/config_test/config/config_test.dynamic.default.yml b/core/modules/config/tests/config_test/config/config_test.dynamic.default.yml
index 3e50e3b..c22273b 100644
--- a/core/modules/config/tests/config_test/config/config_test.dynamic.default.yml
+++ b/core/modules/config/tests/config_test/config/config_test.dynamic.default.yml
@@ -1,2 +1,3 @@
 id: default
 label: Default
+weight: 0
diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module
index 61d92df..670124b 100644
--- a/core/modules/config/tests/config_test/config_test.module
+++ b/core/modules/config/tests/config_test/config_test.module
@@ -82,6 +82,7 @@ function config_test_entity_info() {
     'label' => 'Test configuration',
     'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController',
     'entity class' => 'Drupal\config_test\ConfigTest',
+    'list controller class' => 'Drupal\Core\Config\Entity\ConfigEntityListController',
     'uri callback' => 'config_test_uri',
     'config prefix' => 'config_test.dynamic',
     'entity keys' => array(
@@ -176,36 +177,8 @@ function config_test_delete($id) {
  * Page callback; Lists available ConfigTest objects.
  */
 function config_test_list_page() {
-  $entities = entity_load_multiple('config_test');
-  uasort($entities, 'Drupal\Core\Config\Entity\ConfigEntityBase::sort');
-
-  $rows = array();
-  foreach ($entities as $config_test) {
-    $uri = $config_test->uri();
-    $row = array();
-    $row['name']['data'] = array(
-      '#type' => 'link',
-      '#title' => $config_test->label(),
-      '#href' => $uri['path'],
-      '#options' => $uri['options'],
-    );
-    $row['delete']['data'] = array(
-      '#type' => 'link',
-      '#title' => t('Delete'),
-      '#href' => $uri['path'] . '/delete',
-      '#options' => $uri['options'],
-    );
-    $rows[] = $row;
-  }
-  $build = array(
-    '#theme' => 'table',
-    '#header' => array('Name', 'Operations'),
-    '#rows' => $rows,
-    '#empty' => format_string('No test configuration defined. <a href="@add-url">Add some</a>', array(
-      '@add-url' => url('admin/structure/config_test/add'),
-    )),
-  );
-  return $build;
+  $controller = entity_list_controller('config_test');
+  return $controller->render();
 }
 
 /**
@@ -245,6 +218,11 @@ function config_test_form($form, &$form_state, ConfigTest $config_test = NULL) {
       'source' => array('label'),
     ),
   );
+  $form['weight'] = array(
+    '#type' => 'weight',
+    '#title' => 'Weight',
+    '#default_value' => $config_test->weight,
+  );
   $form['style'] = array(
     '#type' => 'select',
     '#title' => 'Image style',
diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php
index e1df5c4..a4c7cbf 100644
--- a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php
+++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php
@@ -42,4 +42,11 @@ class ConfigTest extends ConfigEntityBase {
    */
   public $style;
 
+  /**
+   * The weight of the configuration entity in relation to others.
+   *
+   * @var integer
+   */
+  public $weight = 0;
+
 }
