diff --git entity/entity.api.php entity/entity.api.php
index 1caf299..f9218ca 100644
--- entity/entity.api.php
+++ entity/entity.api.php
@@ -41,6 +41,12 @@
  *   - default hook: What hook to invoke to find exportable entities that are
  *     currently defined. This hook is automatically called by the CRUD
  *     controller during entity_load(). Defaults to 'default_' . $entity_type.
+ * - admin ui: An array of optional information used for providing an
+ *   administrative user interface. Known keys are:
+ *   - path: a path where the UI should show up as expected by hook_menu().
+ *   - controller class: An optional controller class name for providing the
+ *     UI. The given class has to inherit from the default class being
+ *     EntityDefaultUIController.
  * - 'rules controller class': An optional controller class for providing Rules
  *   integration. The given class has to inherit from the default class being
  *   EntityDefaultRulesController. Set it to FALSE to disable this feature.
diff --git entity/entity.info entity/entity.info
index 0d11d29..4bce52a 100644
--- entity/entity.info
+++ entity/entity.info
@@ -10,3 +10,4 @@ files[] = entity.info.inc
 files[] = entity.module
 files[] = entity.rules.inc
 files[] = entity.test
+files[] = entity.ui.inc
diff --git entity/entity.module entity/entity.module
index bd4b9b1..c0e241c 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.
  */
@@ -428,3 +435,87 @@ function entity_class_label($entity) {
 function entity_class_uri($entity) {
   return $entity->uri();
 }
+
+/**
+ * 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 ($entity_info as $type => $info) {
+      if (isset($info['admin ui'])) {
+        $forms[$type . '_overview_form']['callback'] = 'entity_ui_overview_form';
+        $forms[$type . '_operation_form']['callback'] = 'entity_ui_operation_form';
+        // Edit form for single bundle.
+        if (count($info['bundles']) == 1 && isset($info['bundles'][$type])) {
+          $forms[$type . '_form']['callback'] = 'entity_ui_operation_form';
+        }
+        // Edit forms for multiple bundles.
+        else {
+          foreach ($info['bundles'] as $bundle => $bundle_info) {
+            $forms[$type . '_edit_' . $bundle . '_form']['callback'] = 'entity_ui_operation_form';
+          }
+        }
+      }
+    }
+  }
+  return $forms;
+}
+
+/**
+ * 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);
+}
+
+/**
+ * Checks if a given entity has a certain status.
+ *
+ * @param $entity_type
+ *   The type of the entity.
+ * @param $entity
+ *   The entity to check the status on.
+ * @param $status
+ *   The constant status like ENTITY_IN_DB, ENTITY_IN_CODE, ENTITY_OVERRIDDEN
+ *   or ENTITY_FIXED.
+ *
+ * @return
+ *   TRUE if the entity has the status, FALSE otherwise.
+ */
+function entity_has_status($entity_type, $entity, $status) {
+  $info = entity_get_info($entity_type);
+  $status_property = empty($info['status key']) ? 'status' : $info['status key'];
+  if (($entity->{$status_property} & $status) == $status) {
+    return TRUE;
+  }
+  return FALSE;
+}
diff --git entity/entity.ui.inc entity/entity.ui.inc
new file mode 100644
index 0000000..684b84c
--- /dev/null
+++ entity/entity.ui.inc
@@ -0,0 +1,285 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Provides a controller for building an entity overview form.
+ */
+
+class EntityDefaultUIController {
+
+  // @todo change var names to camel case
+  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_ui_overview_form', $this->entity_type),
+      'description' => 'Administer entity type ' . $this->entity_info['label'],
+      // @todo description callback?
+      'access callback' => 'entity_access',
+      'access arguments' => array('view', $this->entity_type),
+      'file' => 'entity.ui.inc',
+    );
+    $items[$this->entity_info['admin ui']['path'] . '/add'] = array(
+      'title callback' => 'entity_ui_add_title',
+      'title arguments' => array($this->entity_info['label']),
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array($this->entity_type . '_form', NULL, 'add'),
+      'access callback' => 'entity_access',
+      'access arguments' => array('create', $this->entity_type),
+      'type' => MENU_LOCAL_ACTION,
+      'file' => 'entity.ui.inc',
+    );
+    // Edit form
+    $path = $this->entity_info['admin ui']['path'] . '/%entity_menu/edit';
+    $id_count = count(explode('/', $path)) - 2;
+    $items[$path] = array(
+      'title callback' => 'entity_ui_operation_title',
+      'title arguments' => array('edit', $this->entity_type, $id_count),
+      'page callback' => 'entity_ui_get_form',
+      'page arguments' => array($this->entity_type, $id_count, 'edit'),
+      'load arguments' => array($this->entity_type),
+      'access callback' => 'entity_access',
+      'access arguments' => array('update', $this->entity_type),
+      'type' => MENU_CALLBACK | MENU_VISIBLE_IN_BREADCRUMB,
+      'file' => 'entity.ui.inc',
+    );
+    // Clone form, a special case for the edit form.
+    $path = $this->entity_info['admin ui']['path'] . '/%entity_menu/clone';
+    $items[$path] = array(
+      'title callback' => 'entity_ui_operation_title',
+      'title arguments' => array('clone', $this->entity_type, $id_count),
+      'page callback' => 'entity_ui_get_form',
+      'page arguments' => array($this->entity_type, $id_count, 'clone'),
+      'load arguments' => array($this->entity_type),
+      'access callback' => 'entity_access',
+      'access arguments' => array('update', $this->entity_type),
+      'type' => MENU_CALLBACK | MENU_VISIBLE_IN_BREADCRUMB,
+      'file' => 'entity.ui.inc',
+    );
+    // menu path for operations like revert, delete
+    $path = $this->entity_info['admin ui']['path'] . '/%entity_menu/%';
+    $op_count = $id_count + 1;
+    $items[$path] = array(
+      'title callback' => 'entity_ui_operation_title',
+      'title arguments' => array($op_count, $this->entity_type, $id_count),
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array($this->entity_type . '_operation_form', $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 | MENU_VISIBLE_IN_BREADCRUMB,
+      '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);
+
+    $rows = array();
+    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)
+      // Operations (edit, clone, revert, delete)
+      switch ($entity->status) {
+        case ENTITY_IN_DB:
+          $row[] = t('Custom');
+          $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('delete'), $this->entity_info['admin ui']['path'] . '/' . $id . '/delete', array('query' => drupal_get_destination()));
+          break;
+        case ENTITY_IN_CODE:
+          $row[] = t('Default');
+          $row[] = l(t('edit'), $this->entity_info['admin ui']['path'] . '/' . $id . '/edit');
+          $row[] = l(t('clone'), $this->entity_info['admin ui']['path'] . '/' . $id . '/clone');
+          break;
+        case ENTITY_OVERRIDDEN:
+          $row[] = t('Overridden');
+          $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', array('query' => drupal_get_destination()));
+          break;
+        case ENTITY_FIXED:
+          $row[] = t('Fixed');
+          $row[] = l(t('clone'), $this->entity_info['admin ui']['path'] . '/' . $id . '/clone');
+          break;
+      }
+
+      $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);
+    $form_state += array(
+      'type' => $this->entity_type,
+      'entity' => $entity,
+      'op' => $op
+    );
+    switch ($op) {
+      case 'revert':
+        if (entity_has_status($this->entity_type, $entity, ENTITY_OVERRIDDEN)) {
+          $confirm_question = t('Are you sure you want to revert the %entity %label?', array('%entity' => $this->entity_info['label'], '%label' => $label));
+          return confirm_form($form, $confirm_question, $this->entity_info['admin ui']['path']);
+        }
+        break;
+
+      case 'delete':
+        if (entity_has_status($this->entity_type, $entity, ENTITY_IN_DB)) {
+          $confirm_question = t('Are you sure you want to delete the %entity %label?', array('%entity' => $this->entity_info['label'], '%label' => $label));
+          return confirm_form($form, $confirm_question, $this->entity_info['admin ui']['path']);
+        }
+        break;
+    }
+    drupal_not_found();
+    exit;
+  }
+
+  public function operationFormSubmit($form, &$form_state) {
+    if ($form_state['values']['confirm']) {
+      $msg = $this->applyOperation($form_state['op'], $form_state['entity']);
+      drupal_set_message($msg);
+    }
+  }
+
+  /**
+   * Applies an operation to the given entity.
+   *
+   * @param string $op
+   *   The operation (edit, clone, revert or delete).
+   * @param $entity
+   *   The entity to manipulate.
+   */
+  public function operationApply($op, $entity) {
+    $label = entity_label($this->entity_type, $entity);
+    $vars = array('%entity' => $this->entity_info['label'], '%label' => $label);
+    $edit_link = l(t('edit'), $this->entity_info['admin ui']['path'] . '/' . $entity->id . '/edit');
+    switch ($op) {
+      case 'revert':
+        $entity->delete();
+        watchdog('entity', 'Reverted %entity %label to the defaults.', $vars, WATCHDOG_NOTICE, $edit_link);
+        return t('Reverted %entity %label to the defaults.', $vars);
+
+      case 'delete':
+        $entity->delete();
+        watchdog('entity', 'Deleted %entity %label.', $vars);
+        return t('Deleted %entity %label.', $vars);
+
+      default:
+        return FALSE;
+    }
+  }
+
+}
+
+/**
+ * Form API callback that invokes the UI controller of the given type to render
+ * the entity overview form.
+ */
+function entity_ui_overview_form($form, &$form_state, $type) {
+  return entity_ui_controller($type)->overviewForm($form, $form_state);
+}
+
+/**
+ * Retrieve the edit form for an entity, which depends on the bundle.
+ */
+function entity_ui_get_form($entity_type, $entity, $op) {
+  list(,,$bundle) = entity_extract_ids($entity_type, $entity);
+  if ($bundle == $entity_type) {
+    $form_id = $entity_type . '_form';
+  }
+  else {
+    $form_id = $entity_type . '_edit_' . $bundle . '_form';
+  }
+  return drupal_get_form($form_id, $entity, $op);
+}
+
+/**
+ * Entity operation form.
+ */
+function entity_ui_operation_form($form, &$form_state, $type, $entity, $op) {
+  return entity_ui_controller($type)->operationForm($form, $form_state, $entity, $op);
+}
+
+/**
+ * Entity operation form - submit callback.
+ */
+function entity_ui_operation_form_submit($form, &$form_state) {
+  $type = $form_state['type'];
+  entity_ui_controller($type)->operationFormSubmit($form, $form_state);
+}
+
+/**
+ * Title callback for add operations on entities.
+ */
+function entity_ui_add_title($type_label) {
+  return t('Add @entity_type', array('@entity_type' => $type_label));
+}
+
+/**
+ * Title callback for operations on entities.
+ */
+function entity_ui_operation_title($op, $entity_type, $entity) {
+  $label = entity_label($entity_type, $entity);
+  switch ($op) {
+    case 'edit':
+      return t('Edit @entity', array('@entity' => $label));
+    case 'clone':
+      return t('Clone @entity', array('@entity' => $label));
+    case 'revert':
+      return t('Revert @entity', array('@entity' => $label));
+    case 'delete':
+      return t('Delete @entity', array('@entity' => $label));
+  }
+}
