diff --git entity/entity.info entity/entity.info
index d1ab00b..63b0390 100644
--- entity/entity.info
+++ entity/entity.info
@@ -5,6 +5,7 @@ core = 7.x
 files[] = entity.controller.inc
 files[] = entity.db.inc
 files[] = entity.db_faces.inc
+files[] = entity.ui.inc
 files[] = entity.info.inc
 files[] = entity.rules.inc
 files[] = entity.test
diff --git entity/entity.module entity/entity.module
index d01f000..ad79804 100644
--- entity/entity.module
+++ entity/entity.module
@@ -33,6 +33,13 @@ define('ENTITY_IN_DB', 0x01);
 define('ENTITY_IN_CODE', 0x02);
 
 /**
+ * A bit flag used to mark entities as overridden, e.g. they were originally
+ * definded in code and are saved now in the database. Same as
+ * (ENTITY_IN_DB | ENTITY_IN_CODE).
+ */
+define('ENTITY_OVERRIDDEN', 0x03);
+
+/**
  * A bit flag used to mark entities as fixed, thus not changeable for any
  * user.
  */
@@ -271,4 +278,74 @@ function theme_entity_status($variables) {
   }
 }
 
+/**
+ * Implements hook_menu().
+ */
+function entity_menu() {
+  $items = array();
+  $entity_info = entity_get_info();
+  foreach ($entity_info as $type => $info) {
+    if (isset($info['admin ui']['path'])) {
+      $items += entity_ui_controller($type)->menu();
+    }
+  }
+  return $items;
+}
 
+/**
+ * Determines the UI controller class for a given entity type.
+ */
+function entity_ui_controller($type) {
+  $info = entity_get_info($type);
+  if (isset($info['admin ui']['controller class'])) {
+    return new $info['admin ui']['controller class']($type, $info);
+  }
+  else {
+    return new EntityDefaultUIController($type, $info);
+  }
+}
+
+/**
+ * Implements hook_forms().
+ */
+function entity_forms() {
+  $forms = array();
+  if ($entity_info = entity_get_info()) {
+    foreach (array_keys($entity_info) as $type) {
+      $forms[$type . '_entity_overview_form']['callback'] = 'entity_overview_form';
+    }
+  }
+  return $forms;
+}
+
+/**
+ * Determines whether the given user has access to an entity.
+ *
+ * @param $op
+ *   The operation being performed. One of 'view', 'update', 'create' or
+ *   'delete'.
+ * @param $entity_type
+ *   The entity type of the entity to check for.
+ * @param $entity
+ *   Optionally an entity to check access for. If no entity is given, it will be
+ *   determined whether access is allowed for all entities of the given type.
+ * @param $account
+ *   The user to check for. Leave it to NULL to check for the global user.
+ * @return boolean
+ *   Whether access is allowed or not. If no information is available, NULL is
+ *   returned.
+ */
+function entity_access($op, $entity_type, $entity = NULL, $account = NULL) {
+  if (($info = entity_get_info()) && isset($info[$entity_type]['access callback'])) {
+    return $info[$entity_type]['access callback']($op, $entity, $account, $entity_type);
+  }
+  return FALSE;
+}
+
+/**
+ * Load callback to retrieve an entity object from its id.
+ */
+function entity_menu_load($id, $type) {
+  $result = entity_load($type, array($id));
+  return reset($result);
+}
diff --git entity/entity.ui.inc entity/entity.ui.inc
new file mode 100644
index 0000000..2e538cd
--- /dev/null
+++ entity/entity.ui.inc
@@ -0,0 +1,187 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Provides a controller for building an entity overview form.
+ */
+
+class EntityDefaultUIController {
+
+  protected $entity_type;
+
+  protected $entity_info;
+
+  public function __construct($entity_type, $entity_info) {
+    $this->entity_type = $entity_type;
+    $this->entity_info = $entity_info;
+  }
+
+  /**
+   * Provides menu items as expected by hook_menu().
+   */
+  public function menu() {
+    $items = array();
+    $items[$this->entity_info['admin ui']['path']] = array(
+      'title' => $this->entity_info['label'],
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('entity_overview_form', $this->entity_type),
+      'description' => 'Administer entity type ' . $this->entity_info['label'],
+      'access callback' => 'entity_access',
+      'access arguments' => array('view', $this->entity_type),
+      'file' => 'entity.ui.inc',
+    );
+    $entities = entity_load($this->entity_type);
+    foreach ($entities as $id => $entity) {
+      // menu path for edit, clone, delete
+      $path = $this->entity_info['admin ui']['path'] . '/%entity_menu/%';
+      $id_count = count(explode('/', $path)) - 2;
+      $op_count = $id_count + 1;
+      $items[$path] = array(
+        'page callback' => 'drupal_get_form',
+        'page arguments' => array('entity_confirm_op', $this->entity_type, $id_count, $op_count),
+        'load arguments' => array($this->entity_type),
+        'access callback' => 'entity_access',
+        'access arguments' => array($op_count, $this->entity_type),
+        'type' => MENU_CALLBACK,
+        'file' => 'entity.ui.inc',
+      );
+    }
+    return $items;
+  }
+
+  /**
+   * Returns an entity overview form.
+   */
+  public function overviewForm($form, &$form_state) {
+    // Test form element
+    $form['filter'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Filter'),
+      '#collapsible' => TRUE,
+    );
+
+    $entities = entity_load($this->entity_type);
+
+    foreach ($entities as $id => $entity) {
+      $row = array();
+      // Label
+      $label = entity_label($this->entity_type, $entity);
+      if ($entity_uri = entity_uri($this->entity_type, $entity)) {
+        $row[] = l($label, url($entity_uri['path'], $entity_uri['options']));
+      }
+      else {
+        $row[] = $label;
+      }
+      // Status (custom, default, overridden or fixed)
+      switch ($entity->status) {
+        case ENTITY_IN_DB:
+          $row[] = t('Custom');
+          break;
+        case ENTITY_IN_CODE:
+          $row[] = t('Default');
+          break;
+        case ENTITY_OVERRIDDEN:
+          $row[] = t('Overridden');
+          break;
+        case ENTITY_FIXED:
+          $row[] = t('Fixed');
+          break;
+      }
+      // Operations (edit, clone, revert, delete)
+      $row[] = l(t('edit'), $this->entity_info['admin ui']['path'] . '/' . $id . '/edit');
+      $row[] = l(t('clone'), $this->entity_info['admin ui']['path'] . '/' . $id . '/clone');
+      $row[] = l(t('revert'), $this->entity_info['admin ui']['path'] . '/' . $id . '/revert');
+      $row[] = l(t('delete'), $this->entity_info['admin ui']['path'] . '/' . $id . '/delete', array('query' => drupal_get_destination()));
+
+      $rows[] = $row;
+    }
+    $header = array(t('Label'), t('Status'), array('data' => t('Operations'), 'colspan' => 4));
+
+    $table = array(
+      '#theme' => 'table',
+      '#header' => $header,
+      '#rows' => $rows,
+      '#empty' => t('None'),
+    );
+    //$table['#attributes']['class'][] = 'rules-overview-table';
+    //$table['#attached']['css'][] = drupal_get_path('module', 'rules') . '/ui/rules.ui.css';
+
+    $form['list'] = $table;
+    $form['list']['#caption'] = t('Available entities');
+    $form['list']['#empty'] = t('There are no matching entities.');
+    return $form;
+  }
+
+  public function overviewFormValidate() {}
+
+  public function overviewFormSubmit() {}
+
+  public function operationForm($form, &$form_state, $entity, $op) {
+    $label = entity_label($this->entity_type, $entity);
+    switch ($op) {
+      case 'revert':
+        $confirm_question = t('Are you sure you want to revert the %entity %label?', array('%entity' => $this->entity_info['label'], '%label' => $label));
+        $form = confirm_form($form, $confirm_question, $this->entity_info['admin ui']['path']);
+        break;
+      case 'delete':
+        $confirm_question = t('Are you sure you want to delete the %entity %label?', array('%entity' => $this->entity_info['label'], '%label' => $label));
+        $form = confirm_form($form, $confirm_question, $this->entity_info['admin ui']['path']);
+        break;
+      default:
+        drupal_not_found();
+        exit;
+    }
+    $form_state += array(
+      'type' => $this->entity_type,
+      'entity' => $entity,
+      'op' => $op
+    );
+    return $form;
+  }
+
+  public function operationFormSubmit($form, &$form_state) {
+    if ($form_state['values']['confirm']) {
+      $entity = $form_state['entity'];
+      $label = entity_label($this->entity_type, $entity);
+      $vars = array('%entity' => $this->entity_info['label'], '%label' => $label);
+
+      switch ($form_state['op']) {
+        case 'revert':
+          $entity->delete();
+          watchdog('entity', 'Reverted %entity %label to the defaults.', $vars);
+          drupal_set_message(t('Reverted %entity %label to the defaults.', $vars));
+          break;
+        case 'delete':
+          $entity->delete();
+          watchdog('entity', 'Deleted %entity %label.', $vars);
+          drupal_set_message(t('Deleted %entity %label.', $vars));
+          break;
+      }
+    }
+  }
+
+}
+
+/**
+ * Form API callback that invokes the UI controller of the given type to render
+ * the entity overview form.
+ */
+function entity_overview_form($form, &$form_state, $type) {
+  return entity_ui_controller($type)->overviewForm($form, $form_state);
+}
+
+/**
+ * Entity operation confirm form.
+ */
+function entity_confirm_op($form, &$form_state, $type, $entity, $op) {
+  return entity_ui_controller($type)->operationForm($form, $form_state, $entity, $op);
+}
+
+/**
+ * Entity operation confirm form - submit callback.
+ */
+function entity_confirm_op_submit($form, &$form_state) {
+  $type = $form_state['type'];
+  entity_ui_controller($type)->operationFormSubmit($form, $form_state);
+}
