diff --git a/data_entity/data_entity.admin.inc b/data_entity/data_entity.admin.inc
new file mode 100644
index 0000000..664904b
--- /dev/null
+++ b/data_entity/data_entity.admin.inc
@@ -0,0 +1,83 @@
+<?php
+/**
+ * @file
+ * Admin UI functions.
+ */
+
+/**
+ * Form builder for table entity options.
+ */
+function data_entity_admin_entity_form($form, &$form_state, $table) {
+  drupal_set_title($table->get('title'));
+
+  $schema = $table->get('table_schema');
+  $meta = $table->get('meta');
+
+  $form = array();
+  // Keep table.
+  $form['table'] = array(
+    '#type' => 'value',
+    '#value' => $table,
+  );
+
+  $form['fields'] = array('#tree' => TRUE);
+  if (isset($schema['fields'])) {
+    foreach ($schema['fields'] as $field_name => $field) {
+      $form['fields'][$field_name] = array();
+      $form['fields'][$field_name]['name'] = array('#markup' => $field_name);
+      $form['fields'][$field_name]['locked'] = array(
+        '#type' => 'checkbox',
+        '#default_value' => $meta['fields'][$field_name]['locked'],
+      );
+      $form['fields'][$field_name]['required'] = array(
+        '#type' => 'checkbox',
+        '#default_value' => $meta['fields'][$field_name]['required'],
+      );
+    }
+  }
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  return $form;
+}
+
+/**
+ * Theme data_ui_edit_form.
+ */
+function theme_data_entity_admin_entity_form($variables) {
+  $form = $variables['form'];
+
+  // Format existing fields.
+  $rows = array();
+  foreach (element_children($form['fields']) as $e) {
+    $row = array();
+    foreach (element_children($form['fields'][$e]) as $f) {
+      $row[] = drupal_render($form['fields'][$e][$f]);
+    }
+    $row[] = '&nbsp;';
+    $rows[] = $row;
+  }
+
+  $header = array(t('Name'), t('Locked'), t('Required'));
+  $output = theme('table', array('header' => $header, 'rows' => $rows));
+  $output .= drupal_render_children($form);
+  return $output;
+}
+
+function data_entity_admin_entity_form_submit($form, &$form_state) {
+  //dsm($form_state['values']);
+  $table = $form_state['values']['table'];
+
+  // Update meta data.
+  $meta = $table->get('meta');
+  foreach ($form_state['values']['fields'] as $field_name => $values) {
+    foreach ($values as $key => $value) {
+      $meta['fields'][$field_name][$key] = $value;
+    }
+  }
+  $table->update(array('meta' => $meta));
+  drupal_set_message(t('Saved changes'));
+}
diff --git a/data_entity/data_entity.entity.inc b/data_entity/data_entity.entity.inc
new file mode 100644
index 0000000..5a58199
--- /dev/null
+++ b/data_entity/data_entity.entity.inc
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * Entity controller class for Data entities.
+ *
+ * @todo: move to an .inc file.
+ */
+class DataEntityController extends EntityAPIController implements EntityAPIControllerInterface {
+
+  protected $dataTable;
+
+  /**
+   * Overridden.
+   * @see EntityAPIController#__construct()
+   */
+  public function __construct($entityType) {
+    parent::__construct($entityType);
+
+    // Set our data table. Our entity type is 'data_TABLENAME'.
+    $this->dataTable = substr($entityType, 5);
+  }
+
+  function load($ids = array(), $conditions = array()) {
+    $entities = parent::load($ids, $conditions);
+
+    foreach ($entities as $id => $entity) {
+      $entity->data_table   = $this->dataTable;
+      // Needed until Drupal core fixes this WTF. @see http://drupal.org/node/1042822.
+      $entity->entity_type  = $this->entityType;
+    }
+
+    return $entities;
+  }
+}
diff --git a/data_entity/data_entity.info b/data_entity/data_entity.info
new file mode 100644
index 0000000..4dac363
--- /dev/null
+++ b/data_entity/data_entity.info
@@ -0,0 +1,13 @@
+name = Data entity
+description = Defines data tables as entity types.
+package = Data
+dependencies[] = data
+dependencies[] = entity
+dependencies[] = data_ui
+core = 7.x
+files[] = data_entity.module
+files[] = data_entity.pages.inc
+files[] = data_entity.admin.inc
+files[] = data_entity.entity.inc
+files[] = views/data_entity.views.inc
+files[] = views/data_entity_views_handler_field_edit_link.inc
diff --git a/data_entity/data_entity.module b/data_entity/data_entity.module
new file mode 100644
index 0000000..3dc2afd
--- /dev/null
+++ b/data_entity/data_entity.module
@@ -0,0 +1,140 @@
+<?php
+/**
+ * @file data_entity.module
+ * TODO: Enter file description here.
+ */
+
+/**
+ * Get all data tables which are declared as entities.
+ *
+ * @todo This is just a wrapper around data_get_all_tables() for now;
+ *  add an admin UI later.
+ */
+function data_entity_get_entity_tables() {
+  return data_get_all_tables();
+}
+
+/**
+ * Implements hook_entity_info().
+ *
+ * Declare every data table as an entity.
+ *
+ * @todo Add an admin UI to request tables for this rather than do all.
+ */
+function data_entity_entity_info() {
+  $tables = data_entity_get_entity_tables();
+  $info = array();
+
+  foreach ($tables as $table_name => $table) {
+    // @todo: whopping assumption that there's a single primary key field.
+    $id_field = $table->table_schema['primary key'][0];
+
+    $info['data_' . $table_name] = array(
+      'label'       => $table->title,
+      'controller class' => 'DataEntityController',
+      'base table'  => $table_name,
+      'entity keys' => array(
+        'id' => $id_field,
+      ),
+      'bundles' => array(),
+      'view modes' => array(),
+      'uri callback' => 'data_entity_uri', // TODO
+    );
+  }
+
+  return $info;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function data_entity_menu() {
+  $items['admin/content/data/entity/%data_ui_table/%data_entity_item'] = array(
+    'title' => 'Edit data item',
+    'load arguments' => array(4),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('data_entity_entity_edit_form', 4, 5),
+    'file' => 'data_entity.pages.inc',
+    'access callback' => 'data_entity_table_access',
+    'access arguments' => array(4),
+  );
+
+  $items['admin/structure/data/edit/%data_ui_table/entity-form'] = array(
+    'title' => 'Configure entity form',
+    'description' => 'Administer data tables.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('data_entity_admin_entity_form', 4),
+    'file' => 'data_entity.admin.inc',
+    'access arguments' => array('administer data tables'),
+    'type' => MENU_LOCAL_TASK,
+  );
+
+  return $items;
+}
+
+/**
+ * Menu access callback.
+ */
+function data_entity_table_access($table) {
+  return user_access('edit data in table ' . $table->name);
+}
+
+/**
+ * Menu loader callback.
+ *
+ * Called 'data_entity_item_load' to avoid being data's hook_entity_load()!
+ */
+function data_entity_item_load($deid, $table_name) {
+  $entity_type = 'data_' . $table_name;
+  $data_entity = entity_load($entity_type, array($deid));
+  return $data_entity ? reset($data_entity) : FALSE;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function data_entity_permission() {
+  $tables = data_entity_get_entity_tables();
+  $permissions = array();
+
+  foreach ($tables as $table_name => $table) {
+    $permissions['edit data in table ' . $table_name] = array(
+      'title' => t('Edit data in the %table_name table', array('%table_name' => $table->title)), 
+    );
+  }
+
+  return $permissions;
+}
+
+/**
+ * Entity URI callback.
+ */
+function data_entity_uri($data_entity) {
+  $entity_type = $data_entity->entity_type;
+  list($id, ) = entity_extract_ids($entity_type, $data_entity);
+  return array(
+    'path' => 'admin/content/data/entity/' . $data_entity->data_table . '/' . $id,
+  );
+}
+
+/**
+ * Implements hook_theme().
+ */
+function data_entity_theme($existing, $type, $theme, $path) {
+  return array(
+    'data_entity_admin_entity_form' => array(
+      'render element' => 'form',
+    ),
+  );
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function data_entity_views_api() {
+  return array(
+    'api' => '3.0-alpha1',
+    'path' => drupal_get_path('module', 'data_entity') . '/views',
+  );
+}
+
diff --git a/data_entity/data_entity.pages.inc b/data_entity/data_entity.pages.inc
new file mode 100644
index 0000000..bb207d8
--- /dev/null
+++ b/data_entity/data_entity.pages.inc
@@ -0,0 +1,50 @@
+<?php
+/**
+ * @file
+ * Contains general page callbacks and associated functions.
+ */
+
+/**
+ * Form builder for viewing and editing a data entity.
+ */
+function data_entity_entity_edit_form($form, &$form_state, $table, $data_entity = NULL) {
+  $form = array();
+
+  $form['table'] = array(
+    '#type' => 'value',
+    '#value' => $table,
+  );
+
+  $form['data'] = array(
+    '#tree' => TRUE,
+  );
+  foreach ($table->table_schema['fields'] as $field_name => $field) {
+    // For some reason these are lower case as entity keys.
+    $field_id_safe = strtolower($field_name);
+    $label = !empty($table->meta['fields'][$field_name]['label']) ? $table->meta['fields'][$field_name]['label'] : $field_name;
+    $form['data'][$field_name] = array(
+      '#type' => 'textfield',
+      '#title' => $label,
+      '#default_value' => $data_entity->$field_id_safe,
+      '#disabled' => $table->meta['fields'][$field_name]['locked'],
+      '#required' => $table->meta['fields'][$field_name]['required'],
+    );
+  }
+
+  $form['buttons']['save'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  return $form;
+}
+
+/**
+ * Form submit handler for saving a data entity.
+ */
+function data_entity_entity_edit_form_submit($form, &$form_state) {
+  $table = $form_state['values']['table'];
+
+  $record = $form_state['values']['data'];
+  drupal_write_record($table->name, $record, $table->table_schema['primary key']);
+}
diff --git a/data_entity/views/data_entity.views.inc b/data_entity/views/data_entity.views.inc
new file mode 100644
index 0000000..296645c
--- /dev/null
+++ b/data_entity/views/data_entity.views.inc
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @file
+ * Contains Views hooks.
+ */
+
+/**
+ * Implements hook_views_data_alter().
+ */
+function data_entity_views_data_alter(&$data) {
+  $tables = data_entity_get_entity_tables();
+  foreach (array_keys($tables) as $table) {
+    $data[$table]['edit_link'] = array(
+      'field' => array(
+        'title' => t('Edit link'),
+        'help' => t('Displays an edit link to the data item'),
+        'handler' => 'data_entity_views_handler_field_edit_link',
+      ),
+    );
+  }
+}
+
+/**
+ * Implements hook_views_default_views_alter().
+ *
+ * Add our field to the default data table views.
+ */
+function data_entity_views_default_views_alter(&$views) {
+  $tables = data_entity_get_entity_tables();
+
+  foreach ($tables as $table) {
+    $view_name = $table_name = $table->get('name');
+    $view = $views[$view_name];
+
+    $handler =& $view->display['default']->handler;
+    /* Field: User: Name */
+    $handler->display->display_options['fields']['edit_link']['id'] = 'edit_link';
+    $handler->display->display_options['fields']['edit_link']['table'] = $table_name;
+    $handler->display->display_options['fields']['edit_link']['field'] = 'edit_link';
+  }
+}
+
diff --git a/data_entity/views/data_entity_views_handler_field_edit_link.inc b/data_entity/views/data_entity_views_handler_field_edit_link.inc
new file mode 100644
index 0000000..611f3b0
--- /dev/null
+++ b/data_entity/views/data_entity_views_handler_field_edit_link.inc
@@ -0,0 +1,25 @@
+<?php
+/**
+ * @file
+ * Class defition for data_entity_views_handler_field_edit_link.
+ */
+
+/**
+ * Provides a field handler that links to a data table entity's edit form.
+ */
+class data_entity_views_handler_field_edit_link extends views_handler_field {
+  function query() {
+    // Fake field, do nothing.
+  }
+
+  function render($values) {
+    $table = $this->view->base_table;
+    $base_field = $this->view->base_field;
+
+    $this->options['alter']['make_link'] = TRUE;
+    $this->options['alter']['path'] = 'admin/content/data/entity/' . $table . '/' . $values->$base_field;
+    $this->options['alter']['query'] = drupal_get_destination();
+
+    return t('edit item');
+  }
+}
