diff --git a/entity.api.php b/entity.api.php
index f77e85e..549ce86 100644
--- a/entity.api.php
+++ b/entity.api.php
@@ -100,6 +100,8 @@
  *   module has been specified (see 'module') and the module does not provide
  *   any views integration. Else it defaults to FALSE, which disables this
  *   feature. See EntityDefaultViewsController.
+ * - views handlers: (optional) Views data about handlers, overriding the data
+ *   generated by EntityDefaultViewsController.
  * - access callback: (optional) Specify a callback that returns access
  *   permissions for the operations 'create', 'update', 'delete' and 'view'.
  *   The callback gets optionally the entity and the user account to check for
diff --git a/entity.info b/entity.info
index d7aed09..8d5cb8e 100644
--- a/entity.info
+++ b/entity.info
@@ -2,6 +2,11 @@ name = Entity API
 description = Enables modules to work with any entity type and to provide entities.
 core = 7.x
 files[] = views/plugins/entity.row_view.inc
+files[] = views/handlers/entity_handler_field_property_boolean.inc
+files[] = views/handlers/entity_handler_field_property_date.inc
+files[] = views/handlers/entity_handler_field_property_numeric.inc
+files[] = views/handlers/entity_handler_field_property_uri.inc
+files[] = views/handlers/entity_handler_field_property_string.inc
 files[] = includes/entity.controller.inc
 files[] = includes/entity.inc
 files[] = includes/entity.ui.inc
diff --git a/entity.module b/entity.module
index c4df05f..26c9177 100644
--- a/entity.module
+++ b/entity.module
@@ -863,7 +863,7 @@ function entity_ui_get_form($entity_type, $entity, $op = 'edit') {
  */
 function entity_views_api() {
   return array(
-    'api' => '3.0-alpha1',
+    'api' => 3,
     'path' => drupal_get_path('module', 'entity') . '/views',
   );
 }
@@ -978,4 +978,3 @@ function entity_entity_info_alter(&$entity_info) {
     $entity_info['taxonomy_vocabulary']['token type'] = 'vocabulary';
   }
 }
-
diff --git a/views/entity.views.inc b/views/entity.views.inc
index e46fc9f..a8b9bac 100644
--- a/views/entity.views.inc
+++ b/views/entity.views.inc
@@ -20,7 +20,7 @@ function entity_views_data() {
       $controller = new $info['views controller class']($type);
       // Relationship data may return views data for already existing tables,
       // so merge results on the second level.
-      foreach ($controller->views_data() as $table => $table_data) {
+      foreach ($controller->viewsData() as $table => $table_data) {
         $data += array($table => array());
         $data[$table] = array_merge($data[$table], $table_data);
       }
@@ -78,16 +78,17 @@ class EntityDefaultViewsController {
   /**
    * Defines the result for hook_views_data().
    */
-  public function views_data() {
+  public function viewsData() {
     $data = array();
     $this->relationships = array();
 
     if (!empty($this->info['base table'])) {
       $table = $this->info['base table'];
+      $schema = drupal_get_schema($this->info['base table']);
+      $properties = entity_get_property_info($this->type);
       // Define the base group of this table. Fields that don't
       // have a group defined will go into this field by default.
       $data[$table]['table']['group']  = drupal_ucfirst($this->info['label']);
-
       $data[$table]['table']['base'] = array(
         'field' => $this->info['entity keys']['id'],
         'title' => drupal_ucfirst($this->info['label']),
@@ -95,42 +96,27 @@ class EntityDefaultViewsController {
         'help' => '',
         'entity type' => $this->type,
       );
-      $data[$table] += $this->schema_fields();
-
-      // Add in any reverse-relationships which have been determined.
-      $data += $this->relationships;
-    }
-    return $data;
-  }
-
-  /**
-   * Try to come up with some views fields with the help of the schema and
-   * the entity property information.
-   */
-  protected function schema_fields() {
-    $schema = drupal_get_schema($this->info['base table']);
-    $properties = entity_get_property_info($this->type);
-    $data = array();
 
-    foreach ($properties['properties'] as $name => $property_info) {
-      $views_field_name = isset($property_info['views field']) ? $property_info['views field'] : $name;
-      if (isset($schema['fields'][$views_field_name])) {
-        if ($views_info = $this->map_from_schema_info($name, $schema['fields'][$views_field_name], $property_info)) {
-          $data[$name] = $views_info + array(
+      foreach ($properties['properties'] as $name => $property_info) {
+        $views_field_name = isset($property_info['views field']) ? $property_info['views field'] : $name;
+        $schema_field_info = isset($schema['fields'][$views_field_name]) ? $schema['fields'][$views_field_name] : array();
+        if ($views_info = $this->mapHandlers($name, $property_info, $schema_field_info)) {
+          $data[$table][$name] = $views_info + array(
             'title' => $property_info['label'],
             'help' => $property_info['description'],
           );
         }
       }
+      // Add in any reverse-relationships which have been determined.
+      $data += $this->relationships;
     }
     return $data;
   }
 
   /**
-   * Comes up with views information based on the given schema and property
-   * info.
+   * Generates views data based on property info, and schema (if provided).
    */
-  protected function map_from_schema_info($name, $schema_field_info, $property_info) {
+  protected function mapHandlers($name, $property_info, $schema_field_info) {
     $type = isset($property_info['type']) ? $property_info['type'] : 'text';
     $views_field_name = isset($property_info['views field']) ? $property_info['views field'] : $name;
 
@@ -164,66 +150,132 @@ class EntityDefaultViewsController {
       );
     }
 
-    if (!empty($schema_field_info['serialize'])) {
-      return FALSE;
+    // Does the property map to a column in the database?
+    if (!empty($schema_field_info)) {
+      if (!empty($schema_field_info['serialize'])) {
+        return FALSE;
+      }
+      $handlers = $this->mapFromSchema($type, $views_field_name);
+    }
+    else {
+      $handlers = $this->mapFromProperty($type, $views_field_name);
     }
 
+    // Allow developers to override the generated views data through the
+    // property info of a given property.
+    if (!empty($property_info['views handlers'])) {
+      foreach ((array) $property_info['views handlers'] as $type => $data) {
+        // Allow developers to unset certain handlers by passing an empty value.
+        // For example, 'argument' => NULL would disable the argument for that
+        // property value.
+        if (empty($data)) {
+          unset($handlers[$type]);
+        }
+        // Go through all values and override handler values with matching keys.
+        foreach ((array) $data as $key => $value) {
+          $handlers[$type][$key] = $value;
+        }
+      }
+    }
+
+    return $handlers;
+  }
+
+  /**
+   * Returns the views data for a property that maps to a column in the
+   * database (allowing it to have arguments, filters and sorts).
+   */
+  protected function mapFromSchema($type, $views_field_name) {
     switch ($type) {
+      case 'boolean':
+        $field_handler = 'views_handler_field_boolean';
+        $sort_handler = 'views_handler_sort';
+        $filter_handler = 'views_handler_filter_boolean_operator';
+        break;
       case 'token':
       case 'text':
-        return array(
-          'field' => array(
-            'field' => $views_field_name,
-            'handler' => 'views_handler_field',
-            'click sortable' => TRUE,
-           ),
-          'sort' => array(
-            'handler' => 'views_handler_sort',
-          ),
-          'filter' => array(
-            'handler' => 'views_handler_filter_string',
-          ),
-          'argument' => array(
-            'handler' => 'views_handler_argument_string',
-          ),
-        );
-
+        $field_handler = 'views_handler_field';
+        $sort_handler = 'views_handler_sort';
+        $filter_handler = 'views_handler_filter_string';
+        $argument_handler = 'views_handler_argument_string';
+        break;
       case 'decimal':
       case 'integer':
-        return array(
-          'field' => array(
-            'field' => $views_field_name,
-            'handler' => 'views_handler_field_numeric',
-            'click sortable' => TRUE,
-           ),
-          'sort' => array(
-            'handler' => 'views_handler_sort',
-          ),
-          'filter' => array(
-            'handler' => 'views_handler_filter_numeric',
-          ),
-          'argument' => array(
-            'handler' => 'views_handler_argument_numeric',
-          ),
-        );
+        $field_handler = 'views_handler_field_numeric';
+        $sort_handler = 'views_handler_sort';
+        $filter_handler = 'views_handler_filter_numeric';
+        $argument_handler = 'views_handler_argument_numeric';
+        break;
+      case 'date':
+        $field_handler = 'views_handler_field_date';
+        $sort_handler = 'views_handler_sort_date';
+        $filter_handler = 'views_handler_filter_date';
+        $argument_handler = 'views_handler_argument_date';
+        break;
+    }
 
+    $handlers = array(
+      'field' => array(
+        'field' => $views_field_name,
+        'handler' => $field_handler,
+        'click sortable' => TRUE,
+       ),
+    );
+    // Sort/filter/argument handlers are optional and might not be present.
+    if (isset($sort_handler)) {
+      $handlers['sort'] = array(
+        'handler' => $sort_handler,
+      );
+    }
+    if (isset($filter_handler)) {
+      $handlers['filter'] = array(
+        'handler' => $filter_handler,
+      );
+    }
+    if (isset($argument_handler)) {
+      $handlers['argument'] = array(
+        'handler' => $argument_handler,
+      );
+    }
+    return $handlers;
+  }
+
+  /**
+   * Returns the views data for a property that doesn't map to a column in the
+   * database (meaning it can't have arguments, filters and sorts).
+   */
+  protected function mapFromProperty($type, $views_field_name) {
+    switch ($type) {
+      case 'boolean':
+        $field_handler = 'entity_handler_field_property_boolean';
+        break;
       case 'date':
-        return array(
-          'field' => array(
-            'field' => $views_field_name,
-            'handler' => 'views_handler_field_date',
-            'click sortable' => TRUE,
-           ),
-          'sort' => array(
-            'handler' => 'views_handler_sort_date',
-          ),
-          'filter' => array(
-            'handler' => 'views_handler_filter_date',
-          ),
-          'argument' => array(
-            'handler' => 'views_handler_argument_date',
-          ),
-        );
+        $field_handler = 'entity_handler_field_property_date';
+        break;
+      case 'decimal':
+      case 'integer':
+        $field_handler = 'entity_handler_field_property_numeric';
+        break;
+      case 'uri':
+        $field_handler = 'entity_handler_field_property_uri';
+        break;
+      case 'token':
+      case 'text':
+        $field_handler = 'entity_handler_field_property_string';
+        break;
+      default:
+        // Unsupported property type.
+        return array();
     }
+
+    $handlers = array(
+      'field' => array(
+        'field' => $views_field_name,
+        'handler' => $field_handler,
+        'click sortable' => FALSE,
+        'entity type' => $this->type,
+       ),
+    );
+    return $handlers;
   }
 }
diff --git a/views/handlers/entity_handler_field_property_boolean.inc b/views/handlers/entity_handler_field_property_boolean.inc
new file mode 100644
index 0000000..6213c75
--- /dev/null
+++ b/views/handlers/entity_handler_field_property_boolean.inc
@@ -0,0 +1,76 @@
+<?php
+// $Id$
+
+/**
+ * Field handler for boolean properties.
+ */
+class entity_handler_field_property_boolean extends views_handler_field_boolean {
+  /**
+   * Override the parent's query method, since it doesn't need to do anything.
+   */
+  function query() {
+    $base_table_alias = $base_table = $this->view->base_table;
+    $base_field = $this->view->base_field;
+
+    if (!empty($this->relationship)) {
+      foreach ($this->view->relationship as $relationship) {
+        if ($relationship->alias == $this->relationship) {
+          $base_table = $relationship->definition['base'];
+          $base_table_alias = $relationship->alias;
+
+          $table_data = views_fetch_data($base_table);
+          $base_field = $table_data['table']['base']['field'];
+        }
+      }
+    }
+
+    // We always need the base field (entity_id / revision_id).
+    $this->field_alias = $this->query->add_field($base_table_alias, $base_field, '');
+  }
+
+  /**
+   * Load the entities for all fields that are about to be displayed.
+   */
+  function pre_render(&$values) {
+    if (!empty($values)) {
+      // Divide the entity ids by entity type, so they can be loaded in bulk.
+      $entities_by_type = array();
+      foreach ($values as $key => $object) {
+        if (isset($object->{$this->field_alias}) && !isset($values[$key]->_property_data[$this->real_field])) {
+          $entity_type = $this->definition['entity type'];
+          $entity_id = $object->{$this->field_alias};
+          $entities_by_type[$entity_type][$key] = $entity_id;
+        }
+      }
+
+      // Load the entities.
+      foreach ($entities_by_type as $entity_type => $entity_ids) {
+        $entities = entity_load($entity_type, $entity_ids);
+
+        foreach ($entity_ids as $key => $entity_id) {
+          if (isset($entities[$entity_id])) {
+            $values[$key]->_property_data[$this->real_field] = array(
+              'entity_type' => $entity_type,
+              'entity' => $entities[$entity_id],
+            );
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Get the value that's supposed to be rendered.
+   *
+   * @param $values
+   *   An entity object containing all retrieved values.
+   * @param $field
+   *   Optional name of the field where the value is stored.
+   */
+  function get_value($values, $field = NULL) {
+    $field = isset($field) ? $field : $this->real_field;
+    $entity = $values->_property_data[$this->real_field]['entity'];
+    $wrapper = entity_metadata_wrapper($this->definition['entity type'], $entity);
+    return $wrapper->{$field}->value();
+  }
+}
diff --git a/views/handlers/entity_handler_field_property_date.inc b/views/handlers/entity_handler_field_property_date.inc
new file mode 100644
index 0000000..115c342
--- /dev/null
+++ b/views/handlers/entity_handler_field_property_date.inc
@@ -0,0 +1,76 @@
+<?php
+// $Id$
+
+/**
+ * Field handler for date properties.
+ */
+class entity_handler_field_property_date extends views_handler_field_date {
+  /**
+   * Override the parent's query method, since it doesn't need to do anything.
+   */
+  function query() {
+    $base_table_alias = $base_table = $this->view->base_table;
+    $base_field = $this->view->base_field;
+
+    if (!empty($this->relationship)) {
+      foreach ($this->view->relationship as $relationship) {
+        if ($relationship->alias == $this->relationship) {
+          $base_table = $relationship->definition['base'];
+          $base_table_alias = $relationship->alias;
+
+          $table_data = views_fetch_data($base_table);
+          $base_field = $table_data['table']['base']['field'];
+        }
+      }
+    }
+
+    // We always need the base field (entity_id / revision_id).
+    $this->field_alias = $this->query->add_field($base_table_alias, $base_field, '');
+  }
+
+  /**
+   * Load the entities for all fields that are about to be displayed.
+   */
+  function pre_render(&$values) {
+    if (!empty($values)) {
+      // Divide the entity ids by entity type, so they can be loaded in bulk.
+      $entities_by_type = array();
+      foreach ($values as $key => $object) {
+        if (isset($object->{$this->field_alias}) && !isset($values[$key]->_property_data[$this->real_field])) {
+          $entity_type = $this->definition['entity type'];
+          $entity_id = $object->{$this->field_alias};
+          $entities_by_type[$entity_type][$key] = $entity_id;
+        }
+      }
+
+      // Load the entities.
+      foreach ($entities_by_type as $entity_type => $entity_ids) {
+        $entities = entity_load($entity_type, $entity_ids);
+
+        foreach ($entity_ids as $key => $entity_id) {
+          if (isset($entities[$entity_id])) {
+            $values[$key]->_property_data[$this->real_field] = array(
+              'entity_type' => $entity_type,
+              'entity' => $entities[$entity_id],
+            );
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Get the value that's supposed to be rendered.
+   *
+   * @param $values
+   *   An entity object containing all retrieved values.
+   * @param $field
+   *   Optional name of the field where the value is stored.
+   */
+  function get_value($values, $field = NULL) {
+    $field = isset($field) ? $field : $this->real_field;
+    $entity = $values->_property_data[$this->real_field]['entity'];
+    $wrapper = entity_metadata_wrapper($this->definition['entity type'], $entity);
+    return $wrapper->{$field}->value();
+  }
+}
diff --git a/views/handlers/entity_handler_field_property_numeric.inc b/views/handlers/entity_handler_field_property_numeric.inc
new file mode 100644
index 0000000..042af61
--- /dev/null
+++ b/views/handlers/entity_handler_field_property_numeric.inc
@@ -0,0 +1,76 @@
+<?php
+// $Id$
+
+/**
+ * Field handler for numeric properties.
+ */
+class entity_handler_field_property_numeric extends views_handler_field_numeric {
+  /**
+   * Override the parent's query method, since it doesn't need to do anything.
+   */
+  function query() {
+    $base_table_alias = $base_table = $this->view->base_table;
+    $base_field = $this->view->base_field;
+
+    if (!empty($this->relationship)) {
+      foreach ($this->view->relationship as $relationship) {
+        if ($relationship->alias == $this->relationship) {
+          $base_table = $relationship->definition['base'];
+          $base_table_alias = $relationship->alias;
+
+          $table_data = views_fetch_data($base_table);
+          $base_field = $table_data['table']['base']['field'];
+        }
+      }
+    }
+
+    // We always need the base field (entity_id / revision_id).
+    $this->field_alias = $this->query->add_field($base_table_alias, $base_field, '');
+  }
+
+  /**
+   * Load the entities for all fields that are about to be displayed.
+   */
+  function pre_render(&$values) {
+    if (!empty($values)) {
+      // Divide the entity ids by entity type, so they can be loaded in bulk.
+      $entities_by_type = array();
+      foreach ($values as $key => $object) {
+        if (isset($object->{$this->field_alias}) && !isset($values[$key]->_property_data[$this->real_field])) {
+          $entity_type = $this->definition['entity type'];
+          $entity_id = $object->{$this->field_alias};
+          $entities_by_type[$entity_type][$key] = $entity_id;
+        }
+      }
+
+      // Load the entities.
+      foreach ($entities_by_type as $entity_type => $entity_ids) {
+        $entities = entity_load($entity_type, $entity_ids);
+
+        foreach ($entity_ids as $key => $entity_id) {
+          if (isset($entities[$entity_id])) {
+            $values[$key]->_property_data[$this->real_field] = array(
+              'entity_type' => $entity_type,
+              'entity' => $entities[$entity_id],
+            );
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Get the value that's supposed to be rendered.
+   *
+   * @param $values
+   *   An entity object containing all retrieved values.
+   * @param $field
+   *   Optional name of the field where the value is stored.
+   */
+  function get_value($values, $field = NULL) {
+    $field = isset($field) ? $field : $this->real_field;
+    $entity = $values->_property_data[$this->real_field]['entity'];
+    $wrapper = entity_metadata_wrapper($this->definition['entity type'], $entity);
+    return $wrapper->{$field}->value();
+  }
+}
diff --git a/views/handlers/entity_handler_field_property_string.inc b/views/handlers/entity_handler_field_property_string.inc
new file mode 100644
index 0000000..789e6e7
--- /dev/null
+++ b/views/handlers/entity_handler_field_property_string.inc
@@ -0,0 +1,76 @@
+<?php
+// $Id$
+
+/**
+ * Field handler for textual properties.
+ */
+class entity_handler_field_property_string extends views_handler_field {
+  /**
+   * Override the parent's query method, since it doesn't need to do anything.
+   */
+  function query() {
+    $base_table_alias = $base_table = $this->view->base_table;
+    $base_field = $this->view->base_field;
+
+    if (!empty($this->relationship)) {
+      foreach ($this->view->relationship as $relationship) {
+        if ($relationship->alias == $this->relationship) {
+          $base_table = $relationship->definition['base'];
+          $base_table_alias = $relationship->alias;
+
+          $table_data = views_fetch_data($base_table);
+          $base_field = $table_data['table']['base']['field'];
+        }
+      }
+    }
+
+    // We always need the base field (entity_id / revision_id).
+    $this->field_alias = $this->query->add_field($base_table_alias, $base_field, '');
+  }
+
+  /**
+   * Load the entities for all fields that are about to be displayed.
+   */
+  function pre_render(&$values) {
+    if (!empty($values)) {
+      // Divide the entity ids by entity type, so they can be loaded in bulk.
+      $entities_by_type = array();
+      foreach ($values as $key => $object) {
+        if (isset($object->{$this->field_alias}) && !isset($values[$key]->_property_data[$this->real_field])) {
+          $entity_type = $this->definition['entity type'];
+          $entity_id = $object->{$this->field_alias};
+          $entities_by_type[$entity_type][$key] = $entity_id;
+        }
+      }
+
+      // Load the entities.
+      foreach ($entities_by_type as $entity_type => $entity_ids) {
+        $entities = entity_load($entity_type, $entity_ids);
+
+        foreach ($entity_ids as $key => $entity_id) {
+          if (isset($entities[$entity_id])) {
+            $values[$key]->_property_data[$this->real_field] = array(
+              'entity_type' => $entity_type,
+              'entity' => $entities[$entity_id],
+            );
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Get the value that's supposed to be rendered.
+   *
+   * @param $values
+   *   An entity object containing all retrieved values.
+   * @param $field
+   *   Optional name of the field where the value is stored.
+   */
+  function get_value($values, $field = NULL) {
+    $field = isset($field) ? $field : $this->real_field;
+    $entity = $values->_property_data[$this->real_field]['entity'];
+    $wrapper = entity_metadata_wrapper($this->definition['entity type'], $entity);
+    return $wrapper->{$field}->value();
+  }
+}
diff --git a/views/handlers/entity_handler_field_property_uri.inc b/views/handlers/entity_handler_field_property_uri.inc
new file mode 100644
index 0000000..f97d727
--- /dev/null
+++ b/views/handlers/entity_handler_field_property_uri.inc
@@ -0,0 +1,76 @@
+<?php
+// $Id$
+
+/**
+ * Field handler for URL/URI properties.
+ */
+class entity_handler_field_property_uri extends views_handler_field_url {
+  /**
+   * Override the parent's query method, since it doesn't need to do anything.
+   */
+  function query() {
+    $base_table_alias = $base_table = $this->view->base_table;
+    $base_field = $this->view->base_field;
+
+    if (!empty($this->relationship)) {
+      foreach ($this->view->relationship as $relationship) {
+        if ($relationship->alias == $this->relationship) {
+          $base_table = $relationship->definition['base'];
+          $base_table_alias = $relationship->alias;
+
+          $table_data = views_fetch_data($base_table);
+          $base_field = $table_data['table']['base']['field'];
+        }
+      }
+    }
+
+    // We always need the base field (entity_id / revision_id).
+    $this->field_alias = $this->query->add_field($base_table_alias, $base_field, '');
+  }
+
+  /**
+   * Load the entities for all fields that are about to be displayed.
+   */
+  function pre_render(&$values) {
+    if (!empty($values)) {
+      // Divide the entity ids by entity type, so they can be loaded in bulk.
+      $entities_by_type = array();
+      foreach ($values as $key => $object) {
+        if (isset($object->{$this->field_alias}) && !isset($values[$key]->_property_data[$this->real_field])) {
+          $entity_type = $this->definition['entity type'];
+          $entity_id = $object->{$this->field_alias};
+          $entities_by_type[$entity_type][$key] = $entity_id;
+        }
+      }
+
+      // Load the entities.
+      foreach ($entities_by_type as $entity_type => $entity_ids) {
+        $entities = entity_load($entity_type, $entity_ids);
+
+        foreach ($entity_ids as $key => $entity_id) {
+          if (isset($entities[$entity_id])) {
+            $values[$key]->_property_data[$this->real_field] = array(
+              'entity_type' => $entity_type,
+              'entity' => $entities[$entity_id],
+            );
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Get the value that's supposed to be rendered.
+   *
+   * @param $values
+   *   An entity object containing all retrieved values.
+   * @param $field
+   *   Optional name of the field where the value is stored.
+   */
+  function get_value($values, $field = NULL) {
+    $field = isset($field) ? $field : $this->real_field;
+    $entity = $values->_property_data[$this->real_field]['entity'];
+    $wrapper = entity_metadata_wrapper($this->definition['entity type'], $entity);
+    return $wrapper->{$field}->value();
+  }
+}
