diff --git a/entityreference.devel_generate.inc b/entityreference.devel_generate.inc
index 1b8fdd1..8334019 100644
--- a/entityreference.devel_generate.inc
+++ b/entityreference.devel_generate.inc
@@ -17,7 +17,7 @@ function entityreference_devel_generate($object, $field, $instance, $bundle) {
 function _entityreference_devel_generate($object, $field, $instance, $bundle) {
   $object_field = array();
   // Get all the entity that are referencable here.
-  $referencable_entity = entityreference_get_handler($field)->getReferencableEntities();
+  $referencable_entity = entityreference_get_selection_handler($field)->getReferencableEntities();
   if (is_array($referencable_entity) && !empty($referencable_entity)) {
     // Get a random key.
     $object_field['target_id'] = array_rand($referencable_entity);
diff --git a/entityreference.handler.inc b/entityreference.handler.inc
deleted file mode 100644
index 3943a69..0000000
--- a/entityreference.handler.inc
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-
-/**
- * Abstraction of the business logic of an entity reference field.
- *
- * Implementations that wish to provide an implementation of this should
- * register it using CTools' plugin system.
- */
-interface EntityReferenceHandler {
-  /**
-   * Factory function: create a new instance of this handler for a given field.
-   *
-   * @param $field
-   *   A field datastructure.
-   * @return EntityReferenceHandler
-   */
-  public static function getInstance($field);
-
-  /**
-   * Return a list of referencable entities.
-   */
-  public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0);
-
-  /**
-   * Count entities that are referencable by a given field.
-   */
-  public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS');
-
-  /**
-   * Validate that entities can be referenced by this field.
-   *
-   * @return
-   *   An array of entity ids that are valid.
-   */
-  public function validateReferencableEntities(array $ids);
-
-  /**
-   * Give the handler a chance to alter the SelectQuery generated by EntityFieldQuery.
-   */
-  public function entityFieldQueryAlter(SelectQueryInterface $query);
-
-  /**
-   * Return the label of a given entity.
-   */
-  public function getLabel($entity);
-
-  /**
-   * Generate a settings form for this handler.
-   */
-  public static function settingsForm($field, $instance);
-}
-
-/**
- * A null implementation of EntityReferenceHandler.
- */
-class EntityReferenceHandler_broken implements EntityReferenceHandler {
-  public static function getInstance($field) {
-    return new EntityReferenceHandler_broken($field);
-  }
-
-  protected function __construct($field) {
-    $this->field = $field;
-  }
-
-  public static function settingsForm($field, $instance) {
-    $form['handler'] = array(
-      '#markup' => t('The selected handler is broken.'),
-    );
-    return $form;
-  }
-
-  public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
-    return array();
-  }
-
-  public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') {
-    return 0;
-  }
-
-  public function validateReferencableEntities(array $ids) {
-    return array();
-  }
-
-  public function entityFieldQueryAlter(SelectQueryInterface $query) {}
-
-  public function getLabel($entity) {
-    return '';
-  }
-}
diff --git a/entityreference.info b/entityreference.info
index e82e36a..10827e6 100644
--- a/entityreference.info
+++ b/entityreference.info
@@ -8,9 +8,11 @@ dependencies[] = ctools
 ; Migrate handler.
 files[] = entityreference.migrate.inc
 
-; Our default entity handler.
-files[] = entityreference.handler.inc
-files[] = handler/base.inc
+; Our plugins.
+files[] = plugins/selection/abstract.inc
+files[] = plugins/selection/base.inc
+files[] = plugins/behavior/abstract.inc
+files[] = plugins/behavior/base.inc
 
 ; Tests.
 files[] = tests/entityreference.handlers.test
diff --git a/entityreference.module b/entityreference.module
index 2ac8c91..b5ca0a9 100644
--- a/entityreference.module
+++ b/entityreference.module
@@ -5,7 +5,7 @@
  */
 function entityreference_ctools_plugin_directory($module, $plugin) {
   if ($module == 'entityreference') {
-    return $plugin;
+    return 'plugins/' . $plugin;
   }
 }
 
@@ -13,11 +13,24 @@ function entityreference_ctools_plugin_directory($module, $plugin) {
  * Implements hook_ctools_plugin_type().
  */
 function entityreference_ctools_plugin_type() {
-  $plugins['handler'] = array();
+  $plugins['selection'] = array();
+  $plugins['behavior'] = array('process' => 'entityreference_behavior_plugin_process');
   return $plugins;
 }
 
 /**
+ * CTools callback; Process the behavoir plugins.
+ */
+function entityreference_behavior_plugin_process(&$plugin, $info) {
+  $plugin += array(
+    'description' => '',
+    'settings level' => 'field',
+    'access callback' => FALSE,
+    'force enabled' => FALSE,
+  );
+}
+
+/**
  * Implementation of hook_field_info().
  */
 function entityreference_field_info() {
@@ -25,7 +38,7 @@ function entityreference_field_info() {
     'label' => t('Entity Reference'),
     'description' => t('This field reference another entity.'),
     'settings' => array(
-      // Default to the core target entity type node. 
+      // Default to the core target entity type node.
       'target_type' => 'node',
       // The handler for this field.
       'handler' => 'base',
@@ -71,21 +84,96 @@ function entityreference_field_is_empty($item, $field) {
   return !isset($item['target_id']) || !is_numeric($item['target_id']);
 }
 
+
 /**
- * Get the handler for a given entityreference field.
+ * Get the behavior handlers for a given entityreference field.
+ */
+function entityreference_get_behavior_handlers($field, $instance = NULL) {
+  $object_cache = drupal_static(__FUNCTION__);
+  $identifier = $field['field_name'];
+  if (!empty($instance)) {
+    $identifier .= ':' . $instance['entity_type'] . ':' . $instance['bundle'];
+  }
+
+  if (!isset($object_cache[$identifier])) {
+    $object_cache[$identifier] = array();
+
+    // Merge in defaults.
+    $field['settings'] += array('behaviors' => array());
+
+    $object_cache[$field['field_name']] = array();
+    $behaviors = !empty($field['settings']['handler_settings']['behaviors']) ? $field['settings']['handler_settings']['behaviors'] : array();
+    if (!empty($instance['settings']['behaviors'])) {
+      $behaviors = array_merge($behaviors, $instance['settings']['behaviors']);
+    }
+    foreach ($behaviors as $behavior => $settings) {
+      if (empty($settings['status'])) {
+        // Behavior is not enabled.
+        continue;
+      }
+
+      $object_cache[$identifier][] = entityreference_get_behavior_handler($behavior, $field, $instance);
+    }
+  }
+
+  return $object_cache[$identifier];
+}
+
+/**
+ * Get the behavior handler for a given entityreference field and instance.
+ *
+ * @param $handler
+ *   The behavior handler name.
+ */
+function entityreference_get_behavior_handler($behavior, $field, $instance = NULL) {
+  $object_cache = drupal_static(__FUNCTION__);
+
+  if (!isset($object_cache[$behavior])) {
+    ctools_include('plugins');
+    $class = ctools_plugin_load_class('entityreference', 'behavior', $behavior, 'class');
+
+    if (class_exists($class)) {
+      $object_cache[$behavior] = new $class($behavior, $field, $instance);
+    }
+    else {
+      // TODO: Create a behavior broken.
+    }
+  }
+
+  return $object_cache[$behavior];
+}
+
+/**
+ * Get the selection handler for a given entityreference field.
  *
  * The handler contains most of the business logic of the field.
  */
-function entityreference_get_handler($field) {
-  $handler = $field['settings']['handler'];
-  ctools_include('plugins');
-  $class = ctools_plugin_load_class('entityreference', 'handler', $handler, 'handler');
+function entityreference_get_selection_handler($field, $instance = NULL) {
+  $object_cache = drupal_static(__FUNCTION__);
+
+  if (!isset($object_cache[$field['field_name']])) {
+    ctools_include('plugins');
+    $handler = $field['settings']['handler'];
+    $class = ctools_plugin_load_class('entityreference', 'selection', $handler, 'class');
 
-  if (class_exists($class)) {
-    return call_user_func(array($class, 'getInstance'), $field);
+    if (class_exists($class)) {
+      $object_cache[$field['field_name']] = call_user_func(array($class, 'getInstance'), $field, $instance);
+    }
+    else {
+      $object_cache[$field['field_name']] = EntityReference_SelectionHandler_Broken::getInstance($field, $instance);
+    }
   }
-  else {
-    return EntityReferenceHandler_broken::getInstance($field);
+
+  return $object_cache[$field['field_name']];
+}
+
+/**
+ * Implements hook_field_load().
+ */
+function entityreference_field_load($entity_type, $entities, $field, $instances, $langcode, &$items) {
+  // Invoke the behaviors.
+  foreach (entityreference_get_behavior_handlers($field) as $handler) {
+    $handler->load($entity_type, $entities, $field, $instances, $langcode, $items);
   }
 }
 
@@ -100,7 +188,7 @@ function entityreference_field_validate($entity_type, $entity, $field, $instance
     }
   }
 
-  $valid_ids = entityreference_get_handler($field)->validateReferencableEntities(array_keys($ids));
+  $valid_ids = entityreference_get_selection_handler($field, $instance)->validateReferencableEntities(array_keys($ids));
 
   $invalid_entities = array_diff_key($ids, array_flip($valid_ids));
   if ($invalid_entities) {
@@ -111,6 +199,11 @@ function entityreference_field_validate($entity_type, $entity, $field, $instance
       );
     }
   }
+
+  // Invoke the behaviors.
+  foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
+    $handler->validate($entity_type, $entity, $field, $instance, $langcode, $items, $errors);
+  }
 }
 
 /**
@@ -122,6 +215,41 @@ function entityreference_field_presave($entity_type, $entity, $field, $instance,
   foreach ($items as $delta => $item) {
     $items[$delta]['target_type'] = $field['settings']['target_type'];
   }
+
+  // Invoke the behaviors.
+  foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
+    $handler->presave($entity_type, $entity, $field, $instance, $langcode, $items);
+  }
+}
+
+/**
+ * Implements hook_field_insert().
+ */
+function entityreference_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  // Invoke the behaviors.
+  foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
+    $handler->insert($entity_type, $entity, $field, $instance, $langcode, $items);
+  }
+}
+
+/**
+ * Implements hook_field_update().
+ */
+function entityreference_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  // Invoke the behaviors.
+  foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
+    $handler->update($entity_type, $entity, $field, $instance, $langcode, $items);
+  }
+}
+
+/**
+ * Implements hook_field_delete().
+ */
+function entityreference_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  // Invoke the behaviors.
+  foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
+    $handler->delete($entity_type, $entity, $field, $instance, $langcode, $items);
+  }
 }
 
 /**
@@ -129,6 +257,7 @@ function entityreference_field_presave($entity_type, $entity, $field, $instance,
  */
 function entityreference_field_settings_form($field, $instance, $has_data) {
   $settings = $field['settings'];
+  $settings += array('handler' => 'base');
 
   // Select the target entity type.
   $entity_type_options = array();
@@ -153,7 +282,7 @@ function entityreference_field_settings_form($field, $instance, $has_data) {
   );
 
   ctools_include('plugins');
-  $handlers = ctools_get_plugins('entityreference', 'handler');
+  $handlers = ctools_get_plugins('entityreference', 'selection');
   uasort($handlers, 'ctools_plugin_sort');
   $handlers_options = array();
   foreach ($handlers as $handler => $handler_info) {
@@ -195,6 +324,22 @@ function entityreference_field_settings_form($field, $instance, $has_data) {
 }
 
 /**
+ * Implements hook_field_instance_settings_form().
+ */
+function entityreference_field_instance_settings_form($field, $instance) {
+  $settings = $instance['settings'];
+  ctools_include('plugins');
+  $class = ctools_plugin_load_class('entityreference', 'selection', $field['settings']['handler'], 'class');
+  if (!class_exists($class)) {
+    $class = 'EntityReference_SelectionHandler_Broken';
+  }
+
+  entityreference_get_behavior_elements($form, $class, $field, $instance, 'instance');
+
+  return $form;
+}
+
+/**
  * #process callback: generates the handler settings form.
  *
  * @see entityreference_field_settings_form()
@@ -206,9 +351,9 @@ function entityreference_render_settings($element, $form_state) {
   $handler = drupal_array_get_nested_value($form_state['values'], $parents);
 
   ctools_include('plugins');
-  $class = ctools_plugin_load_class('entityreference', 'handler', $handler, 'handler');
+  $class = ctools_plugin_load_class('entityreference', 'selection', $handler, 'class');
   if (!class_exists($class)) {
-    $class = 'EntityReferenceHandler_broken';
+    $class = 'EntityReference_SelectionHandler_Broken';
   }
 
   // Rebuild the field configuration based on the submitted structure.
@@ -218,11 +363,80 @@ function entityreference_render_settings($element, $form_state) {
     $field['settings'] = $form_state['values']['field']['settings'] + $field['settings'];
   }
 
+  entityreference_get_behavior_elements($element, $class, $field, $instance, 'field');
+
   $element += call_user_func(array($class, 'settingsForm'), $field, $instance);
   return $element;
 }
 
 /**
+ * Get the field or instance elements for the field configuration.
+ */
+function entityreference_get_behavior_elements(&$element, $class, $field, $instance, $level) {
+   // Add the accessible behavior handlers.
+  $behavior_plugins = entityreference_get_accessible_behavior_plugins($class, $field, $instance);
+
+  if ($behavior_plugins[$level]) {
+    $element['behaviors'] = array();
+
+    foreach ($behavior_plugins[$level] as $name => $plugin) {
+      if ($level == 'field') {
+        $settings = !empty($field['settings']['handler_settings']['behaviors'][$name]) ? $field['settings']['handler_settings']['behaviors'][$name] : array();
+      }
+      else {
+        $settings = !empty($instance['settings']['behaviors'][$name]) ? $instance['settings']['behaviors'][$name] : array();
+      }
+      $settings += array('status' => $plugin['force enabled']);
+      $element['behaviors'][$name] = array(
+        '#type' => 'fieldset',
+        '#title' => $plugin['title'],
+        '#default_value' => $settings['status'],
+      );
+      $element['behaviors'][$name]['status'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Enable %title', array('%title' => $plugin['title'])),
+        '#default_value' => $settings['status'],
+        '#disabled' => $plugin['force enabled'],
+      );
+
+      $behavior_class = ctools_plugin_load_class('entityreference', 'behavior', $name, 'class');
+      if ($behavior_elements = call_user_func(array($behavior_class, 'settingsForm'))) {
+        $enable_element = $level == 'field' ? 'field[settings][handler_settings][behaviors]' : 'instance[settings][behaviors]';
+        foreach ($behavior_elements as $key => &$behavior_element) {
+          $behavior_element += array(
+            '#default_value' => !empty($settings[$key]) ? $settings[$key] : NULL,
+            '#states' => array(
+              'visible' => array(
+                ':input[name="' . $enable_element . '[' . $name . '][status]"]' => array('checked' => TRUE),
+              ),
+            ),
+          );
+        }
+
+        // Get the behavior settings.
+        $element['behaviors'][$name] += $behavior_elements;
+      }
+    }
+  }
+}
+
+/**
+ * Get all accessible behavior plugins.
+ */
+function entityreference_get_accessible_behavior_plugins($select_class, $field, $instance) {
+  ctools_include('plugins');
+  $plugins = array('field' => array(), 'instance' => array());
+  foreach (ctools_get_plugins('entityreference', 'behavior') as $name => $plugin) {
+    $handler = entityreference_get_behavior_handler($name, $field, $instance);
+    $level = $plugin['settings level'];
+    if ($handler->access($select_class)) {
+      $plugins[$level][$name] = $plugin;
+    }
+  }
+  return $plugins;
+}
+
+/**
  * Ajax callback for the handler settings form.
  *
  * @see entityreference_field_settings_form()
@@ -343,22 +557,24 @@ function entityreference_field_widget_settings_form($field, $instance) {
 /**
  * Implements hook_options_list().
  */
-function entityreference_options_list($field) {
-  return entityreference_get_handler($field)->getReferencableEntities();
+function entityreference_options_list($field, $instance = NULL) {
+  return entityreference_get_selection_handler($field, $instance)->getReferencableEntities();
 }
 
 /**
  * Implements hook_query_TAG_alter().
  */
 function entityreference_query_entityreference_alter(QueryAlterableInterface $query) {
-  entityreference_get_handler($query->getMetadata('field'))->entityFieldQueryAlter($query);
+  $field = $query->getMetadata('field');
+  $instance = $query->getMetadata('instance');
+  entityreference_get_selection_handler($field, $instance)->entityFieldQueryAlter($query);
 }
 
 /**
  * Implements hook_field_widget_form().
  */
 function entityreference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
-  $handler = entityreference_get_handler($field);
+  $handler = entityreference_get_selection_handler($field, $instance);
 
   if ($instance['widget']['type'] == 'entityreference_autocomplete' || $instance['widget']['type'] == 'entityreference_autocomplete_tags') {
 
@@ -474,7 +690,7 @@ function entityreference_autocomplete_callback($type, $field_name, $entity_type,
     return MENU_ACCESS_DENIED;
   }
 
-  $handler = entityreference_get_handler($field);
+  $handler = entityreference_get_selection_handler($field, $instance);
 
   if ($type == 'tags') {
     // The user enters a comma-separated list of tags. We only autocomplete the last tag.
@@ -639,7 +855,7 @@ function entityreference_field_formatter_view($entity_type, $entity, $field, $in
 
   switch ($display['type']) {
     case 'entityreference_label':
-      $handler = entityreference_get_handler($field);
+      $handler = entityreference_get_selection_handler($field, $instance);
 
       foreach ($items as $delta => $item) {
         $label = $handler->getLabel($item['entity']);
diff --git a/examples/entityreference_behavior_example/entityreference_behavior_example.info b/examples/entityreference_behavior_example/entityreference_behavior_example.info
new file mode 100644
index 0000000..ba2ec17
--- /dev/null
+++ b/examples/entityreference_behavior_example/entityreference_behavior_example.info
@@ -0,0 +1,5 @@
+name = Entity Reference Behavior Example
+description = Provides some example code for implementing Entity Reference behaviors.
+core = 7.x
+package = Fields
+dependencies[] = entityreference
diff --git a/examples/entityreference_behavior_example/entityreference_behavior_example.module b/examples/entityreference_behavior_example/entityreference_behavior_example.module
new file mode 100644
index 0000000..f2c143c
--- /dev/null
+++ b/examples/entityreference_behavior_example/entityreference_behavior_example.module
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * Implements hook_ctools_plugin_directory().
+ */
+function entityreference_behavior_example_ctools_plugin_directory($module, $plugin) {
+  if ($module == 'entityreference') {
+    return 'plugins/' . $plugin;
+  }
+}
diff --git a/examples/entityreference_behavior_example/plugins/behavior/test_field_behavior.inc b/examples/entityreference_behavior_example/plugins/behavior/test_field_behavior.inc
new file mode 100644
index 0000000..864608b
--- /dev/null
+++ b/examples/entityreference_behavior_example/plugins/behavior/test_field_behavior.inc
@@ -0,0 +1,38 @@
+<?php
+
+$plugin = array(
+  'title' => t('Test behavior'),
+  'class' => 'EntityReferenceFieldBehaviorExample',
+  'weight' => 10,
+  'settings level' => 'field',
+);
+
+class EntityReferenceFieldBehaviorExample extends EntityReference_BehaviorHandler_Generic {
+
+  public function load($entity_type, $entities, $field, $instances, $langcode, &$items) {
+    drupal_set_message('Do something on load!');
+  }
+
+  public function insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
+    drupal_set_message('Do something on insert!');
+  }
+
+  public function update($entity_type, $entity, $field, $instance, $langcode, &$items) {
+    drupal_set_message('Do something on update!');
+  }
+
+  public function delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
+    drupal_set_message('Do something on delete!');
+  }
+
+  /**
+   * Generate a settings form for this handler.
+   */
+  public function settingsForm() {
+    $form['test_field'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Field behavoir setting'),
+    );
+    return $form;
+  }
+}
diff --git a/examples/entityreference_behavior_example/plugins/behavior/test_instance_behavior.inc b/examples/entityreference_behavior_example/plugins/behavior/test_instance_behavior.inc
new file mode 100644
index 0000000..55cf95f
--- /dev/null
+++ b/examples/entityreference_behavior_example/plugins/behavior/test_instance_behavior.inc
@@ -0,0 +1,38 @@
+<?php
+
+$plugin = array(
+  'title' => t('Test instance behavior'),
+  'class' => 'EntityReferenceInstanceBehaviorExample',
+  'weight' => 10,
+  'settings level' => 'instance',
+);
+
+class EntityReferenceInstanceBehaviorExample extends EntityReference_BehaviorHandler_Generic {
+
+  public function load($entity_type, $entities, $field, $instances, $langcode, &$items) {
+    drupal_set_message('Do something on load, on the instance level!');
+  }
+
+  public function insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
+    drupal_set_message('Do something on insert, on the instance level!');
+  }
+
+  public function update($entity_type, $entity, $field, $instance, $langcode, &$items) {
+    drupal_set_message('Do something on update, on the instance level!');
+  }
+
+  public function delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
+    drupal_set_message('Do something on delete, on the instance level!');
+  }
+
+  /**
+   * Generate a settings form for this handler.
+   */
+  public function settingsForm() {
+    $form['test_instance'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Instance behavoir setting'),
+    );
+    return $form;
+  }
+}
\ No newline at end of file
diff --git a/handler/base.inc b/handler/base.inc
deleted file mode 100644
index ddb9d30..0000000
--- a/handler/base.inc
+++ /dev/null
@@ -1,419 +0,0 @@
-<?php
-
-$plugin = array(
-  'title' => t('Simple (with optional filter by bundle)'),
-  'handler' => 'EntityReferenceHandler_base',
-  'weight' => -100,
-);
-
-/**
- * A generic Entity handler.
- *
- * The generic base implementation has a variety of overrides to workaround
- * core's largely deficient entity handling.
- */
-class EntityReferenceHandler_base implements EntityReferenceHandler {
-
-  /**
-   * Implements EntityReferenceHandler::getInstance().
-   */
-  public static function getInstance($field) {
-    $entity_type = $field['settings']['target_type'];
-    if (class_exists($class_name = 'EntityReferenceHandler_' . $entity_type)) {
-      return new $class_name($field);
-    }
-    else {
-      return new EntityReferenceHandler_base($field);
-    }
-  }
-
-  protected function __construct($field) {
-    $this->field = $field;
-  }
-
-  /**
-   * Implements EntityReferenceHandler::settingsForm().
-   */
-  public static function settingsForm($field, $instance) {
-    $entity_info = entity_get_info($field['settings']['target_type']);
-    $bundles = array();
-    foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
-      $bundles[$bundle_name] = $bundle_info['label'];
-    }
-
-    $form['target_bundles'] = array(
-      '#type' => 'select',
-      '#title' => t('Target bundles'),
-      '#options' => $bundles,
-      '#default_value' => isset($field['settings']['handler_settings']['target_bundles']) ? $field['settings']['handler_settings']['target_bundles'] : array(),
-      '#size' => 6,
-      '#multiple' => TRUE,
-      '#description' => t('The bundles of the entity type that can be referenced. Optional, leave empty for all bundles.')
-    );
-
-    $form['sort']['type'] = array(
-      '#type' => 'radios',
-      '#title' => t('Sort by'),
-      '#options' => array(
-        'none' => t("Don't sort"),
-        'property' => t('A property of the base table of the entity'),
-        'field' => t('A field attached to this entity'),
-      ),
-      '#default_value' => isset($field['settings']['handler_settings']['sort']['type']) ? $field['settings']['handler_settings']['sort']['type'] : 'none',
-    );
-
-    $form['sort']['property'] = array(
-      '#type' => 'select',
-      '#title' => t('Sort property'),
-      '#options' => drupal_map_assoc($entity_info['schema_fields_sql']['base table']),
-      '#default_value' => isset($field['settings']['handler_settings']['sort']['property']) ? $field['settings']['handler_settings']['sort']['property'] : '',
-      '#states' => array(
-        'visible' => array(
-          ':input[name="field[settings][handler_settings][sort][type]"]' => array('value' => 'property'),
-        ),
-      ),
-    );
-
-    $fields = array();
-    foreach (field_info_instances($field['settings']['target_type']) as $bundle_name => $bundle_instances) {
-      foreach ($bundle_instances as $instance_name => $instance_info) {
-        $field_info = field_info_field($instance_name);
-        foreach ($field_info['columns'] as $column_name => $column_info) {
-          $fields[$instance_name . ':' . $column_name] = t('@label (column @column)', array('@label' => $instance_info['label'], '@column' => $column_name));
-        }
-      }
-    }
-
-    $form['sort']['field'] = array(
-      '#type' => 'select',
-      '#title' => t('Sort field'),
-      '#options' => $fields,
-      '#default_value' => isset($field['settings']['handler_settings']['sort']['type']) ? $field['settings']['handler_settings']['sort']['type'] : '',
-      '#states' => array(
-        'visible' => array(
-          ':input[name="field[settings][handler_settings][sort][type]"]' => array('value' => 'field'),
-        ),
-      ),
-    );
-
-    $form['sort']['direction'] = array(
-      '#type' => 'select',
-      '#title' => t('Sort direction'),
-      '#options' => array(
-        'ASC' => t('Ascending'),
-        'DESC' => t('Descending'),
-      ),
-      '#default_value' => isset($field['settings']['handler_settings']['sort']['direction']) ? $field['settings']['handler_settings']['sort']['direction'] : 'ASC',
-      '#states' => array(
-        'invisible' => array(
-          ':input[name="field[settings][handler_settings][sort][type]"]' => array('value' => 'none'),
-        ),
-      ),
-    );
-
-    return $form;
-  }
-
-  /**
-   * Implements EntityReferenceHandler::getReferencableEntities().
-   */
-  public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
-    $options = array();
-    $entity_type = $this->field['settings']['target_type'];
-
-    $query = $this->buildEntityFieldQuery($match, $match_operator);
-    if ($limit > 0) {
-      $query->range(0, $limit);
-    }
-
-    $results = $query->execute();
-
-    if (!empty($results[$entity_type])) {
-      $entities = entity_load($entity_type, array_keys($results[$entity_type]));
-      foreach ($entities as $entity_id => $entity) {
-        $options[$entity_id] = $this->getLabel($entity);
-      }
-    }
-
-    return $options;
-  }
-
-  /**
-   * Implements EntityReferenceHandler::countReferencableEntities().
-   */
-  public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') {
-    $query = $this->buildEntityFieldQuery($match, $match_operator);
-    return $query
-      ->count()
-      ->execute();
-  }
-
-  /**
-   * Implements EntityReferenceHandler::validateReferencableEntities().
-   */
-  public function validateReferencableEntities(array $ids) {
-    if ($ids) {
-      $entity_type = $this->field['settings']['target_type'];
-      $query = $this->buildEntityFieldQuery();
-      $query->entityCondition('entity_id', $ids, 'IN');
-      $result = $query->execute();
-      if (!empty($result[$entity_type])) {
-        return array_keys($result[$entity_type]);
-      }
-    }
-
-    return array();
-  }
-
-  /**
-   * Build an EntityFieldQuery to get referencable entities.
-   */
-  protected function buildEntityFieldQuery($match = NULL, $match_operator = 'CONTAINS') {
-    $query = new EntityFieldQuery();
-    $query->entityCondition('entity_type', $this->field['settings']['target_type']);
-    if ($this->field['settings']['handler_settings']['target_bundles']) {
-      $query->entityCondition('bundle', $this->field['settings']['handler_settings']['target_bundles'], 'IN');
-    }
-    if (isset($match)) {
-      $entity_info = entity_get_info($this->field['settings']['target_type']);
-      if (isset($entity_info['entity keys']['label'])) {
-        $query->propertyCondition($entity_info['entity keys']['label'], $match, $match_operator);
-      }
-    }
-
-    // Add a generic entity access tag to the query.
-    $query->addTag($this->field['settings']['target_type'] . '_access');
-    $query->addTag('entityreference');
-    $query->addMetaData('field', $this->field);
-
-    // Add the sort option.
-    if (!empty($this->field['settings']['handler_settings']['sort'])) {
-      $sort_settings = $this->field['settings']['handler_settings']['sort'];
-      if ($sort_settings['type'] == 'property') {
-        $query->propertyOrderBy($sort_settings['property'], $sort_settings['direction']);
-      }
-      elseif ($sort_settings['type'] == 'field') {
-        list($field, $column) = explode(':', $sort_settings['field'], 2);
-        $query->fieldOrderBy($field, $column, $sort_settings['direction']);
-      }
-    }
-
-    return $query;
-  }
-
-  /**
-   * Implements EntityReferenceHandler::entityFieldQueryAlter().
-   */
-  public function entityFieldQueryAlter(SelectQueryInterface $query) {
-
-  }
-
-  /**
-   * Helper method: pass a query to the alteration system again.
-   *
-   * This allow Entity Reference to add a tag to an existing query, to ask
-   * access control mechanisms to alter it again.
-   */
-  protected function reAlterQuery(SelectQueryInterface $query, $tag, $base_table) {
-    // Save the old tags and metadata.
-    // For some reason, those are public.
-    $old_tags = $query->alterTags;
-    $old_metadata = $query->alterMetaData;
-
-    $query->alterTags = array($tag => TRUE);
-    $query->alterMetaData['base_table'] = $base_table;
-    drupal_alter(array('query', 'query_' . $tag), $query);
-
-    // Restore the tags and metadata.
-    $query->alterTags = $old_tags;
-    $query->alterMetaData = $old_metadata;
-  }
-
-  /**
-   * Implements EntityReferenceHandler::getLabel().
-   */
-  public function getLabel($entity) {
-    return entity_label($this->field['settings']['target_type'], $entity);
-  }
-}
-
-/**
- * Override for the Node type.
- *
- * This only exists to workaround core bugs.
- */
-class EntityReferenceHandler_node extends EntityReferenceHandler_base {
-  public function entityFieldQueryAlter(SelectQueryInterface $query) {
-    // Adding the 'node_access' tag is sadly insufficient for nodes: core
-    // requires us to also know about the concept of 'published' and
-    // 'unpublished'. We need to do that as long as there are no access control
-    // modules in use on the site. As long as one access control module is there,
-    // it is supposed to handle this check.
-    if (!user_access('bypass node access') && !count(module_implements('node_grants'))) {
-      $tables = $query->getTables();
-      $query->condition(key($tables) . '.status', NODE_PUBLISHED);
-    }
-  }
-}
-
-/**
- * Override for the User type.
- *
- * This only exists to workaround core bugs.
- */
-class EntityReferenceHandler_user extends EntityReferenceHandler_base {
-  public function buildEntityFieldQuery($match = NULL, $match_operator = 'CONTAINS') {
-    $query = parent::buildEntityFieldQuery($match, $match_operator);
-
-    // The user entity doesn't have a label column.
-    if (isset($match)) {
-      $query->propertyCondition('name', $match, $match_operator);
-    }
-
-    // Adding the 'user_access' tag is sadly insufficient for users: core
-    // requires us to also know about the concept of 'blocked' and
-    // 'active'.
-    if (!user_access('administer users')) {
-      $query->propertyCondition('status', 1);
-    }
-    return $query;
-  }
-
-  public function entityFieldQueryAlter(SelectQueryInterface $query) {
-    if (user_access('administer users')) {
-      // In addition, if the user is administrator, we need to make sure to
-      // match the anonymous user, that doesn't actually have a name in the
-      // database.
-      $conditions = &$query->conditions();
-      foreach ($conditions as $key => $condition) {
-        if ($condition['field'] == 'users.name') {
-          // Remove the condition.
-          unset($conditions[$key]);
-
-          // Re-add the condition and a condition on uid = 0 so that we end up
-          // with a query in the form:
-          //    WHERE (name LIKE :name) OR (:anonymous_name LIKE :name AND uid = 0)
-          $or = db_or();
-          $or->condition($condition['field'], $condition['value'], $condition['operator']);
-          // Sadly, the Database layer doesn't allow us to build a condition
-          // in the form ':placeholder = :placeholder2', because the 'field'
-          // part of a condition is always escaped.
-          // As a (cheap) workaround, we separately build a condition with no
-          // field, and concatenate the field and the condition separately.
-          $value_part = db_and();
-          $value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
-          $value_part->compile(Database::getConnection(), $query);
-          $or->condition(db_and()
-            ->where(str_replace('anonymous_name', ':anonymous_name', (string) $value_part), $value_part->arguments() + array(':anonymous_name' => format_username(user_load(0))))
-            ->condition('users.uid', 0)
-          );
-          $query->condition($or);
-        }
-      }
-    }
-  }
-}
-
-/**
- * Override for the Comment type.
- *
- * This only exists to workaround core bugs.
- */
-class EntityReferenceHandler_comment extends EntityReferenceHandler_base {
-  public function entityFieldQueryAlter(SelectQueryInterface $query) {
-    // Adding the 'comment_access' tag is sadly insufficient for comments: core
-    // requires us to also know about the concept of 'published' and
-    // 'unpublished'.
-    if (!user_access('administer comments')) {
-      $tables = $query->getTables();
-      $query->condition(key($tables) . '.status', COMMENT_PUBLISHED);
-    }
-
-    // The Comment module doesn't implement any proper comment access,
-    // and as a consequence doesn't make sure that comments cannot be viewed
-    // when the user doesn't have access to the node.
-    $tables = $query->getTables();
-    $base_table = key($tables);
-    $node_alias = $query->innerJoin('node', 'n', '%alias.nid = ' . $base_table . '.nid');
-    // Pass the query to the node access control.
-    $this->reAlterQuery($query, 'node_access', $node_alias);
-
-    // Alas, the comment entity exposes a bundle, but doesn't have a bundle column
-    // in the database. We have to alter the query ourself to go fetch the
-    // bundle.
-    $conditions = &$query->conditions();
-    foreach ($conditions as $id => &$condition) {
-      if (is_array($condition) && $condition['field'] == 'node_type') {
-        $condition['field'] = $node_alias . '.type';
-        foreach ($condition['value'] as &$value) {
-          if (substr($value, 0, 13) == 'comment_node_') {
-            $value = substr($value, 13);
-          }
-        }
-        break;
-      }
-    }
-
-    // Passing the query to node_query_node_access_alter() is sadly
-    // insufficient for nodes.
-    // @see EntityReferenceHandler_node::entityFieldQueryAlter()
-    if (!user_access('bypass node access') && !count(module_implements('node_grants'))) {
-      $query->condition($node_alias . '.status', 1);
-    }
-  }
-}
-
-/**
- * Override for the File type.
- *
- * This only exists to workaround core bugs.
- */
-class EntityReferenceHandler_file extends EntityReferenceHandler_base {
-  public function entityFieldQueryAlter(SelectQueryInterface $query) {
-    // Core forces us to know about 'permanent' vs. 'temporary' files.
-    $tables = $query->getTables();
-    $base_table = key($tables);
-    $query->condition('status', FILE_STATUS_PERMANENT);
-
-    // Access control to files is a very difficult business. For now, we are not
-    // going to give it a shot.
-    // @todo: fix this when core access control is less insane.
-    return $query;
-  }
-
-  public function getLabel($entity) {
-    // The file entity doesn't have a label. More over, the filename is
-    // sometimes empty, so use the basename in that case.
-    return $entity->filename !== '' ? $entity->filename : basename($entity->uri);
-  }
-}
-
-/**
- * Override for the Taxonomy term type.
- *
- * This only exists to workaround core bugs.
- */
-class EntityReferenceHandler_taxonomy_term extends EntityReferenceHandler_base {
-  public function entityFieldQueryAlter(SelectQueryInterface $query) {
-    // The Taxonomy module doesn't implement any proper taxonomy term access,
-    // and as a consequence doesn't make sure that taxonomy terms cannot be viewed
-    // when the user doesn't have access to the vocabulary.
-    $tables = $query->getTables();
-    $base_table = key($tables);
-    $vocabulary_alias = $query->innerJoin('taxonomy_vocabulary', 'n', '%alias.vid = ' . $base_table . '.vid');
-    $query->addMetadata('base_table', $vocabulary_alias);
-    // Pass the query to the taxonomy access control.
-    $this->reAlterQuery($query, 'taxonomy_vocabulary_access', $vocabulary_alias);
-
-    // Also, the taxonomy term entity exposes a bundle, but doesn't have a bundle
-    // column in the database. We have to alter the query ourself to go fetch
-    // the bundle.
-    $conditions = &$query->conditions();
-    foreach ($conditions as $id => &$condition) {
-      if (is_array($condition) && $condition['field'] == 'vocabulary_machine_name') {
-        $condition['field'] = $vocabulary_alias . '.machine_name';
-        break;
-      }
-    }
-  }
-}
diff --git a/plugins/behavior/abstract.inc b/plugins/behavior/abstract.inc
new file mode 100644
index 0000000..47c14f0
--- /dev/null
+++ b/plugins/behavior/abstract.inc
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * Additional behaviors for a Entity Reference field.
+ *
+ * Implementations that wish to provide an implementation of this should
+ * register it using CTools' plugin system.
+ */
+interface EntityReference_BehaviorHandler {
+
+  /**
+   * @TODO: document.
+   */
+  public function __construct($behavior, array $field, array $instance = NULL);
+
+  /**
+   * @TODO: document.
+   */
+  public function load($entity_type, $entities, $field, $instances, $langcode, &$items);
+
+  /**
+   * @TODO: document.
+   */
+  public function validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors);
+
+  /**
+   * @TODO: document.
+   */
+  public function presave($entity_type, $entity, $field, $instance, $langcode, &$items);
+
+  /**
+   * @TODO: document.
+   */
+  public function insert($entity_type, $entity, $field, $instance, $langcode, &$items);
+
+  /**
+   * @TODO: document.
+   */
+  public function update($entity_type, $entity, $field, $instance, $langcode, &$items);
+
+  /**
+   * @TODO: document.
+   */
+  public function delete($entity_type, $entity, $field, $instance, $langcode, &$items);
+
+  /**
+   * Generate a settings form for this handler.
+   */
+  public function settingsForm();
+
+  /**
+   * Determine if handler should appear.
+   */
+  public function access($selection_class);
+}
+
diff --git a/plugins/behavior/base.inc b/plugins/behavior/base.inc
new file mode 100644
index 0000000..22440fd
--- /dev/null
+++ b/plugins/behavior/base.inc
@@ -0,0 +1,41 @@
+<?php
+
+class EntityReference_BehaviorHandler_Generic implements EntityReference_BehaviorHandler {
+
+  public function __construct($behavior, array $field, array $instance = NULL) {
+    ctools_include('plugins');
+    $plugin = ctools_get_plugins('entityreference', 'behavior', $behavior);
+    $this->plugin = $plugin;
+
+    if ($plugin['settings level'] == 'field') {
+      $this->settings = !empty($field['settings']['handler_settings']['behavior']) ? $field['settings']['handler_settings']['behavior'] : array();
+    }
+    else {
+      $this->settings = !empty($instance['settings']['behavior']) ? $instance['settings']['behavior'] : array();
+    }
+
+    $this->status = !empty($settings['status']) ? $settings['status'] : FALSE;
+    unset($this->settings['status']);
+
+    $this->field = $field;
+    $this->instance = $instance;
+  }
+
+  public function load($entity_type, $entities, $field, $instances, $langcode, &$items) {}
+
+  public function validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {}
+
+  public function presave($entity_type, $entity, $field, $instance, $langcode, &$items) {}
+
+  public function insert($entity_type, $entity, $field, $instance, $langcode, &$items) {}
+
+  public function update($entity_type, $entity, $field, $instance, $langcode, &$items) {}
+
+  public function delete($entity_type, $entity, $field, $instance, $langcode, &$items) {}
+
+  public function settingsForm() {}
+
+  public function access($selection_class) {
+    return TRUE;
+  }
+}
diff --git a/plugins/selection/abstract.inc b/plugins/selection/abstract.inc
new file mode 100644
index 0000000..eafd1c7
--- /dev/null
+++ b/plugins/selection/abstract.inc
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * Abstraction of the selection logic of an entity reference field.
+ *
+ * Implementations that wish to provide an implementation of this should
+ * register it using CTools' plugin system.
+ */
+interface EntityReference_SelectionHandler {
+  /**
+   * Factory function: create a new instance of this handler for a given field.
+   *
+   * @param $field
+   *   A field datastructure.
+   * @return EntityReferenceHandler
+   */
+  public static function getInstance($field, $instance);
+
+  /**
+   * Return a list of referencable entities.
+   */
+  public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0);
+
+  /**
+   * Count entities that are referencable by a given field.
+   */
+  public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS');
+
+  /**
+   * Validate that entities can be referenced by this field.
+   *
+   * @return
+   *   An array of entity ids that are valid.
+   */
+  public function validateReferencableEntities(array $ids);
+
+  /**
+   * Give the handler a chance to alter the SelectQuery generated by EntityFieldQuery.
+   */
+  public function entityFieldQueryAlter(SelectQueryInterface $query);
+
+  /**
+   * Return the label of a given entity.
+   */
+  public function getLabel($entity);
+
+  /**
+   * Generate a settings form for this handler.
+   */
+  public static function settingsForm($field, $instance);
+}
+
+/**
+ * A null implementation of EntityReference_SelectionHandler.
+ */
+class EntityReference_SelectionHandler_Broken implements EntityReference_SelectionHandler {
+  public static function getInstance($field, $instance) {
+    return new EntityReference_SelectionHandler_Broken($field, $instance);
+  }
+
+  protected function __construct($field, $instance) {
+    $this->field = $field;
+    $this->instance = $instance;
+  }
+
+  public static function settingsForm($field, $instance) {
+    $form['handler'] = array(
+      '#markup' => t('The selected handler is broken.'),
+    );
+    return $form;
+  }
+
+  public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
+    return array();
+  }
+
+  public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') {
+    return 0;
+  }
+
+  public function validateReferencableEntities(array $ids) {
+    return array();
+  }
+
+  public function entityFieldQueryAlter(SelectQueryInterface $query) {}
+
+  public function getLabel($entity) {
+    return '';
+  }
+}
diff --git a/plugins/selection/base.inc b/plugins/selection/base.inc
new file mode 100644
index 0000000..698bd47
--- /dev/null
+++ b/plugins/selection/base.inc
@@ -0,0 +1,420 @@
+<?php
+
+$plugin = array(
+  'title' => t('Simple (with optional filter by bundle)'),
+  'class' => 'EntityReference_SelectionHandler_Generic',
+  'weight' => -100,
+);
+
+/**
+ * A generic Entity handler.
+ *
+ * The generic base implementation has a variety of overrides to workaround
+ * core's largely deficient entity handling.
+ */
+class EntityReference_SelectionHandler_Generic implements EntityReference_SelectionHandler {
+
+  /**
+   * Implements EntityReferenceHandler::getInstance().
+   */
+  public static function getInstance($field, $instance) {
+    $entity_type = $field['settings']['target_type'];
+    if (class_exists($class_name = 'EntityReference_SelectionHandler_Generic_' . $entity_type)) {
+      return new $class_name($field, $instance);
+    }
+    else {
+      return new EntityReference_SelectionHandler_Generic($field, $instance);
+    }
+  }
+
+  protected function __construct($field, $instance) {
+    $this->field = $field;
+    $this->instance = $instance;
+  }
+
+  /**
+   * Implements EntityReferenceHandler::settingsForm().
+   */
+  public static function settingsForm($field, $instance) {
+    $entity_info = entity_get_info($field['settings']['target_type']);
+    $bundles = array();
+    foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
+      $bundles[$bundle_name] = $bundle_info['label'];
+    }
+
+    $form['target_bundles'] = array(
+      '#type' => 'select',
+      '#title' => t('Target bundles'),
+      '#options' => $bundles,
+      '#default_value' => isset($field['settings']['handler_settings']['target_bundles']) ? $field['settings']['handler_settings']['target_bundles'] : array(),
+      '#size' => 6,
+      '#multiple' => TRUE,
+      '#description' => t('The bundles of the entity type that can be referenced. Optional, leave empty for all bundles.')
+    );
+
+    $form['sort']['type'] = array(
+      '#type' => 'radios',
+      '#title' => t('Sort by'),
+      '#options' => array(
+        'none' => t("Don't sort"),
+        'property' => t('A property of the base table of the entity'),
+        'field' => t('A field attached to this entity'),
+      ),
+      '#default_value' => isset($field['settings']['handler_settings']['sort']['type']) ? $field['settings']['handler_settings']['sort']['type'] : 'none',
+    );
+
+    $form['sort']['property'] = array(
+      '#type' => 'select',
+      '#title' => t('Sort property'),
+      '#options' => drupal_map_assoc($entity_info['schema_fields_sql']['base table']),
+      '#default_value' => isset($field['settings']['handler_settings']['sort']['property']) ? $field['settings']['handler_settings']['sort']['property'] : '',
+      '#states' => array(
+        'visible' => array(
+          ':input[name="field[settings][handler_settings][sort][type]"]' => array('value' => 'property'),
+        ),
+      ),
+    );
+
+    $fields = array();
+    foreach (field_info_instances($field['settings']['target_type']) as $bundle_name => $bundle_instances) {
+      foreach ($bundle_instances as $instance_name => $instance_info) {
+        $field_info = field_info_field($instance_name);
+        foreach ($field_info['columns'] as $column_name => $column_info) {
+          $fields[$instance_name . ':' . $column_name] = t('@label (column @column)', array('@label' => $instance_info['label'], '@column' => $column_name));
+        }
+      }
+    }
+
+    $form['sort']['field'] = array(
+      '#type' => 'select',
+      '#title' => t('Sort field'),
+      '#options' => $fields,
+      '#default_value' => isset($field['settings']['handler_settings']['sort']['type']) ? $field['settings']['handler_settings']['sort']['type'] : '',
+      '#states' => array(
+        'visible' => array(
+          ':input[name="field[settings][handler_settings][sort][type]"]' => array('value' => 'field'),
+        ),
+      ),
+    );
+
+    $form['sort']['direction'] = array(
+      '#type' => 'select',
+      '#title' => t('Sort direction'),
+      '#options' => array(
+        'ASC' => t('Ascending'),
+        'DESC' => t('Descending'),
+      ),
+      '#default_value' => isset($field['settings']['handler_settings']['sort']['direction']) ? $field['settings']['handler_settings']['sort']['direction'] : 'ASC',
+      '#states' => array(
+        'invisible' => array(
+          ':input[name="field[settings][handler_settings][sort][type]"]' => array('value' => 'none'),
+        ),
+      ),
+    );
+
+    return $form;
+  }
+
+  /**
+   * Implements EntityReferenceHandler::getReferencableEntities().
+   */
+  public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
+    $options = array();
+    $entity_type = $this->field['settings']['target_type'];
+
+    $query = $this->buildEntityFieldQuery($match, $match_operator);
+    if ($limit > 0) {
+      $query->range(0, $limit);
+    }
+
+    $results = $query->execute();
+
+    if (!empty($results[$entity_type])) {
+      $entities = entity_load($entity_type, array_keys($results[$entity_type]));
+      foreach ($entities as $entity_id => $entity) {
+        $options[$entity_id] = $this->getLabel($entity);
+      }
+    }
+
+    return $options;
+  }
+
+  /**
+   * Implements EntityReferenceHandler::countReferencableEntities().
+   */
+  public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') {
+    $query = $this->buildEntityFieldQuery($match, $match_operator);
+    return $query
+      ->count()
+      ->execute();
+  }
+
+  /**
+   * Implements EntityReferenceHandler::validateReferencableEntities().
+   */
+  public function validateReferencableEntities(array $ids) {
+    if ($ids) {
+      $entity_type = $this->field['settings']['target_type'];
+      $query = $this->buildEntityFieldQuery();
+      $query->entityCondition('entity_id', $ids, 'IN');
+      $result = $query->execute();
+      if (!empty($result[$entity_type])) {
+        return array_keys($result[$entity_type]);
+      }
+    }
+
+    return array();
+  }
+
+  /**
+   * Build an EntityFieldQuery to get referencable entities.
+   */
+  protected function buildEntityFieldQuery($match = NULL, $match_operator = 'CONTAINS') {
+    $query = new EntityFieldQuery();
+    $query->entityCondition('entity_type', $this->field['settings']['target_type']);
+    if ($this->field['settings']['handler_settings']['target_bundles']) {
+      $query->entityCondition('bundle', $this->field['settings']['handler_settings']['target_bundles'], 'IN');
+    }
+    if (isset($match)) {
+      $entity_info = entity_get_info($this->field['settings']['target_type']);
+      if (isset($entity_info['entity keys']['label'])) {
+        $query->propertyCondition($entity_info['entity keys']['label'], $match, $match_operator);
+      }
+    }
+
+    // Add a generic entity access tag to the query.
+    $query->addTag($this->field['settings']['target_type'] . '_access');
+    $query->addTag('entityreference');
+    $query->addMetaData('field', $this->field);
+
+    // Add the sort option.
+    if (!empty($this->field['settings']['handler_settings']['sort'])) {
+      $sort_settings = $this->field['settings']['handler_settings']['sort'];
+      if ($sort_settings['type'] == 'property') {
+        $query->propertyOrderBy($sort_settings['property'], $sort_settings['direction']);
+      }
+      elseif ($sort_settings['type'] == 'field') {
+        list($field, $column) = explode(':', $sort_settings['field'], 2);
+        $query->fieldOrderBy($field, $column, $sort_settings['direction']);
+      }
+    }
+
+    return $query;
+  }
+
+  /**
+   * Implements EntityReferenceHandler::entityFieldQueryAlter().
+   */
+  public function entityFieldQueryAlter(SelectQueryInterface $query) {
+
+  }
+
+  /**
+   * Helper method: pass a query to the alteration system again.
+   *
+   * This allow Entity Reference to add a tag to an existing query, to ask
+   * access control mechanisms to alter it again.
+   */
+  protected function reAlterQuery(SelectQueryInterface $query, $tag, $base_table) {
+    // Save the old tags and metadata.
+    // For some reason, those are public.
+    $old_tags = $query->alterTags;
+    $old_metadata = $query->alterMetaData;
+
+    $query->alterTags = array($tag => TRUE);
+    $query->alterMetaData['base_table'] = $base_table;
+    drupal_alter(array('query', 'query_' . $tag), $query);
+
+    // Restore the tags and metadata.
+    $query->alterTags = $old_tags;
+    $query->alterMetaData = $old_metadata;
+  }
+
+  /**
+   * Implements EntityReferenceHandler::getLabel().
+   */
+  public function getLabel($entity) {
+    return entity_label($this->field['settings']['target_type'], $entity);
+  }
+}
+
+/**
+ * Override for the Node type.
+ *
+ * This only exists to workaround core bugs.
+ */
+class EntityReference_SelectionHandler_Generic_node extends EntityReference_SelectionHandler_Generic {
+  public function entityFieldQueryAlter(SelectQueryInterface $query) {
+    // Adding the 'node_access' tag is sadly insufficient for nodes: core
+    // requires us to also know about the concept of 'published' and
+    // 'unpublished'. We need to do that as long as there are no access control
+    // modules in use on the site. As long as one access control module is there,
+    // it is supposed to handle this check.
+    if (!user_access('bypass node access') && !count(module_implements('node_grants'))) {
+      $tables = $query->getTables();
+      $query->condition(key($tables) . '.status', NODE_PUBLISHED);
+    }
+  }
+}
+
+/**
+ * Override for the User type.
+ *
+ * This only exists to workaround core bugs.
+ */
+class EntityReference_SelectionHandler_Generic_user extends EntityReference_SelectionHandler_Generic {
+  public function buildEntityFieldQuery($match = NULL, $match_operator = 'CONTAINS') {
+    $query = parent::buildEntityFieldQuery($match, $match_operator);
+
+    // The user entity doesn't have a label column.
+    if (isset($match)) {
+      $query->propertyCondition('name', $match, $match_operator);
+    }
+
+    // Adding the 'user_access' tag is sadly insufficient for users: core
+    // requires us to also know about the concept of 'blocked' and
+    // 'active'.
+    if (!user_access('administer users')) {
+      $query->propertyCondition('status', 1);
+    }
+    return $query;
+  }
+
+  public function entityFieldQueryAlter(SelectQueryInterface $query) {
+    if (user_access('administer users')) {
+      // In addition, if the user is administrator, we need to make sure to
+      // match the anonymous user, that doesn't actually have a name in the
+      // database.
+      $conditions = &$query->conditions();
+      foreach ($conditions as $key => $condition) {
+        if ($condition['field'] == 'users.name') {
+          // Remove the condition.
+          unset($conditions[$key]);
+
+          // Re-add the condition and a condition on uid = 0 so that we end up
+          // with a query in the form:
+          //    WHERE (name LIKE :name) OR (:anonymous_name LIKE :name AND uid = 0)
+          $or = db_or();
+          $or->condition($condition['field'], $condition['value'], $condition['operator']);
+          // Sadly, the Database layer doesn't allow us to build a condition
+          // in the form ':placeholder = :placeholder2', because the 'field'
+          // part of a condition is always escaped.
+          // As a (cheap) workaround, we separately build a condition with no
+          // field, and concatenate the field and the condition separately.
+          $value_part = db_and();
+          $value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
+          $value_part->compile(Database::getConnection(), $query);
+          $or->condition(db_and()
+            ->where(str_replace('anonymous_name', ':anonymous_name', (string) $value_part), $value_part->arguments() + array(':anonymous_name' => format_username(user_load(0))))
+            ->condition('users.uid', 0)
+          );
+          $query->condition($or);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Override for the Comment type.
+ *
+ * This only exists to workaround core bugs.
+ */
+class EntityReference_SelectionHandler_Generic_comment extends EntityReference_SelectionHandler_Generic {
+  public function entityFieldQueryAlter(SelectQueryInterface $query) {
+    // Adding the 'comment_access' tag is sadly insufficient for comments: core
+    // requires us to also know about the concept of 'published' and
+    // 'unpublished'.
+    if (!user_access('administer comments')) {
+      $tables = $query->getTables();
+      $query->condition(key($tables) . '.status', COMMENT_PUBLISHED);
+    }
+
+    // The Comment module doesn't implement any proper comment access,
+    // and as a consequence doesn't make sure that comments cannot be viewed
+    // when the user doesn't have access to the node.
+    $tables = $query->getTables();
+    $base_table = key($tables);
+    $node_alias = $query->innerJoin('node', 'n', '%alias.nid = ' . $base_table . '.nid');
+    // Pass the query to the node access control.
+    $this->reAlterQuery($query, 'node_access', $node_alias);
+
+    // Alas, the comment entity exposes a bundle, but doesn't have a bundle column
+    // in the database. We have to alter the query ourself to go fetch the
+    // bundle.
+    $conditions = &$query->conditions();
+    foreach ($conditions as $id => &$condition) {
+      if (is_array($condition) && $condition['field'] == 'node_type') {
+        $condition['field'] = $node_alias . '.type';
+        foreach ($condition['value'] as &$value) {
+          if (substr($value, 0, 13) == 'comment_node_') {
+            $value = substr($value, 13);
+          }
+        }
+        break;
+      }
+    }
+
+    // Passing the query to node_query_node_access_alter() is sadly
+    // insufficient for nodes.
+    // @see EntityReferenceHandler_node::entityFieldQueryAlter()
+    if (!user_access('bypass node access') && !count(module_implements('node_grants'))) {
+      $query->condition($node_alias . '.status', 1);
+    }
+  }
+}
+
+/**
+ * Override for the File type.
+ *
+ * This only exists to workaround core bugs.
+ */
+class EntityReference_SelectionHandler_Generic_file extends EntityReference_SelectionHandler_Generic {
+  public function entityFieldQueryAlter(SelectQueryInterface $query) {
+    // Core forces us to know about 'permanent' vs. 'temporary' files.
+    $tables = $query->getTables();
+    $base_table = key($tables);
+    $query->condition('status', FILE_STATUS_PERMANENT);
+
+    // Access control to files is a very difficult business. For now, we are not
+    // going to give it a shot.
+    // @todo: fix this when core access control is less insane.
+    return $query;
+  }
+
+  public function getLabel($entity) {
+    // The file entity doesn't have a label. More over, the filename is
+    // sometimes empty, so use the basename in that case.
+    return $entity->filename !== '' ? $entity->filename : basename($entity->uri);
+  }
+}
+
+/**
+ * Override for the Taxonomy term type.
+ *
+ * This only exists to workaround core bugs.
+ */
+class EntityReference_SelectionHandler_Generic_taxonomy_term extends EntityReference_SelectionHandler_Generic {
+  public function entityFieldQueryAlter(SelectQueryInterface $query) {
+    // The Taxonomy module doesn't implement any proper taxonomy term access,
+    // and as a consequence doesn't make sure that taxonomy terms cannot be viewed
+    // when the user doesn't have access to the vocabulary.
+    $tables = $query->getTables();
+    $base_table = key($tables);
+    $vocabulary_alias = $query->innerJoin('taxonomy_vocabulary', 'n', '%alias.vid = ' . $base_table . '.vid');
+    $query->addMetadata('base_table', $vocabulary_alias);
+    // Pass the query to the taxonomy access control.
+    $this->reAlterQuery($query, 'taxonomy_vocabulary_access', $vocabulary_alias);
+
+    // Also, the taxonomy term entity exposes a bundle, but doesn't have a bundle
+    // column in the database. We have to alter the query ourself to go fetch
+    // the bundle.
+    $conditions = &$query->conditions();
+    foreach ($conditions as $id => &$condition) {
+      if (is_array($condition) && $condition['field'] == 'vocabulary_machine_name') {
+        $condition['field'] = $vocabulary_alias . '.machine_name';
+        break;
+      }
+    }
+  }
+}
diff --git a/tests/entityreference.handlers.test b/tests/entityreference.handlers.test
index ae9266d..b660664 100644
--- a/tests/entityreference.handlers.test
+++ b/tests/entityreference.handlers.test
@@ -17,7 +17,7 @@ class EntityReferenceHandlersTestCase extends DrupalWebTestCase {
   }
 
   protected function assertReferencable($field, $tests, $handler_name) {
-    $handler = entityreference_get_handler($field);
+    $handler = entityreference_get_selection_handler($field);
 
     foreach ($tests as $test) {
       foreach ($test['arguments'] as $arguments) {
