diff --git a/includes/index_entity.inc b/includes/index_entity.inc
index 35f0769..f910972 100644
--- a/includes/index_entity.inc
+++ b/includes/index_entity.inc
@@ -111,10 +111,13 @@ class SearchApiIndex extends Entity {
    *   - name: The human-readable name for the field.
    *   - indexed: Boolean indicating whether the field is indexed or not.
    *   - type: The type set for this field. One of the types returned by
-   *     search_api_field_types().
+   *     search_api_default_field_types().
+   *   - real_type: (optional) If a custom data type was selected for this
+   *     field, this type will be stored here, and "type" contain the fallback
+   *     default data type.
    *   - boost: A boost value for terms found in this field during searches.
    *     Usually only relevant for fulltext fields.
-   *   - entity_type (optional): If set, the type of this field is really an
+   *   - entity_type: (optional) If set, the type of this field is really an
    *     entity. The "type" key will then contain "integer", meaning that
    *     servers will ignore this and merely index the entity's ID. Components
    *     displaying this field, though, are advised to use the entity label
@@ -420,7 +423,16 @@ class SearchApiIndex extends Entity {
       if (!$info['indexed']) {
         unset($fields[$field]);
       }
-      unset($fields[$field]['indexed']);
+      else {
+        if (isset($info['real_type'])) {
+          $custom_type = search_api_extract_inner_type($info['real_type']);
+          if ($this->server()->supportsFeature('search_api_data_type_' . $custom_type)) {
+            $fields[$field]['type'] = $info['real_type'];
+            $this->options['fields'][$field]['type'] = $info['real_type'];
+          }
+        }
+        unset($fields[$field]['indexed']);
+      }
     }
     if (empty($fields)) {
       throw new SearchApiException(t("Couldn't index values on '!name' index (no fields selected)", array('!name' => $this->name)));
diff --git a/search_api.admin.inc b/search_api.admin.inc
index 6854d83..2f25269 100644
--- a/search_api.admin.inc
+++ b/search_api.admin.inc
@@ -1459,6 +1459,7 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp
   $types = array(0 => search_api_field_types());
   $fulltext_type = array(0 => 'text');
   $entity_types = entity_get_info();
+  $default_types = search_api_default_field_types();
   $boosts = drupal_map_assoc(array('0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0'));
 
   $form_state['index'] = $index;
@@ -1493,7 +1494,7 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp
       '#default_value' => $info['indexed'],
     );
     $inner_type = search_api_extract_inner_type($info['type']);
-    if (isset($types[0][$inner_type])) {
+    if (isset($default_types[$inner_type])) {
       // Determine the correct type options (i.e., with the correct nesting level).
       $level = search_api_list_nesting_level($info['type']);
       if (empty($types[$level])) {
@@ -1510,7 +1511,7 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp
       $form['fields'][$key]['type'] = array(
         '#type' => 'select',
         '#options' => $types[$level],
-        '#default_value' => $info['type'],
+        '#default_value' => isset($info['real_type']) ? $info['real_type'] : $info['type'],
         '#states' => array(
           'visible' => array(
             $css_key . '-indexed' => array('checked' => TRUE),
@@ -1546,7 +1547,7 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp
       );
       $form['fields'][$key]['entity_type'] = array(
         '#type' => 'value',
-        '#value' => search_api_extract_inner_type($info['type']),
+        '#value' => $inner_type,
       );
       $form['fields'][$key]['type_name'] = array(
         '#markup' => check_plain($entity_types[$inner_type]['label']),
@@ -1627,7 +1628,7 @@ function _search_api_admin_get_fields(SearchApiIndex $index, EntityMetadataWrapp
     // The list nesting level for entities with a certain prefix
   $nesting_levels = array('' => 0);
 
-  $types = search_api_field_types();
+  $types = search_api_default_field_types();
   $flat = array();
   while ($wrappers) {
     foreach ($wrappers as $prefix => $wrapper) {
@@ -1651,7 +1652,7 @@ function _search_api_admin_get_fields(SearchApiIndex $index, EntityMetadataWrapp
         $type = search_api_extract_inner_type($info['type']);
         // Treat Entity API type "token" as our "string" type.
         // Also let text fields with limited options be of type "string" by default.
-        if ($type == 'token' || ($type == 'text' && $value->optionsList('view'))) {
+        if ($type == 'token' || ($type == 'text' && !empty($info['options list']))) {
           // Inner type is changed to "string".
           $type = 'string';
           // Set the field type accordingly.
@@ -1672,7 +1673,9 @@ function _search_api_admin_get_fields(SearchApiIndex $index, EntityMetadataWrapp
             }
             else {
               // Else, only update the nesting level.
-              $flat[$key]['type'] = search_api_nest_type(search_api_extract_inner_type($flat[$key]['type']), $info['type']);
+              $set_type = search_api_extract_inner_type(isset($flat[$key]['real_type']) ? $flat[$key]['real_type'] : $flat[$key]['type']);
+              $flat[$key]['type'] = $info['type'];
+              $flat[$key]['real_type'] = search_api_nest_type($set_type, $info['type']);
             }
           }
           else {
@@ -1768,8 +1771,18 @@ function search_api_admin_index_fields_submit(array $form, array &$form_state) {
   $options = isset($index->options) ? $index->options : array();
   if ($form_state['values']['op'] == t('Save changes')) {
     $fields = $form_state['values']['fields'];
+    $default_types = search_api_default_field_types();
+    $custom_types = search_api_custom_field_types();
     foreach ($fields as $name => &$field) {
+      // Don't store the description.
       unset($field['description']);
+      // For non-default types, set type to the fallback and only real_type to
+      // the custom type.
+      $inner_type = search_api_extract_inner_type($field['type']);
+      if (!isset($default_types[$inner_type])) {
+        $field['real_type'] = $field['type'];
+        $field['type'] = search_api_nest_type($custom_types[$inner_type]['fallback'], $field['type']);
+      }
     }
     $options['fields'] = $fields;
     $ret = $index->update(array('options' => $options));
diff --git a/search_api.api.php b/search_api.api.php
index e245a4d..331f27b 100644
--- a/search_api.api.php
+++ b/search_api.api.php
@@ -129,7 +129,6 @@ function hook_search_api_item_type_info() {
  * @see hook_search_api_item_type_info()
  */
 function hook_search_api_item_type_info_alter(array &$infos) {
-  hook_entity_info_alter();
   // Adds a boolean value is_entity to all type options telling whether the item
   // type represents an entity type.
   foreach ($infos as $type => $info) {
@@ -138,6 +137,49 @@ function hook_search_api_item_type_info_alter(array &$infos) {
 }
 
 /**
+ * Define new data types for indexed properties.
+ *
+ * New data types will appear as new option for the „Type“ field on indexes'
+ * „Fields“ tabs. Whether choosing a custom data type will have any effect
+ * depends on the server on which the data is indexed.
+ *
+ * @return array
+ *   An array containing custom data type definitions, keyed by their type
+ *   identifier and containing the following keys:
+ *   - name: The human-readable name of the type.
+ *   - fallback: (optional) One of the default data types (the keys from
+ *     search_api_default_field_types()) which should be used as a fallback if
+ *     the server doesn't support this data type. Defaults to "string".
+ *
+ * @see hook_search_api_data_type_info_alter()
+ * @see search_api_custom_field_types()
+ */
+function hook_search_api_data_type_info() {
+  return array(
+    'example_type' => array(
+      'name' => t('Example type'),
+      'fallback' => 'string',// Could be omitted, as "string" is the default.
+    ),
+  );
+}
+
+/**
+ * Alter the data type info.
+ *
+ * Modules may implement this hook to alter the information that defines a data
+ * type, or to add/remove some entirely. All properties that are available in
+ * hook_search_api_data_type_info() can be altered here.
+ *
+ * @param array $infos
+ *   The data type info array, keyed by type identifier.
+ *
+ * @see hook_search_api_data_type_info()
+ */
+function hook_search_api_data_type_info_alter(array &$infos) {
+  $infos['example_type']['name'] .= ' 2';
+}
+
+/**
  * Registers one or more callbacks that can be called at index time to add
  * additional data to the indexed items (e.g. comments or attachments to nodes),
  * alter the data in other forms or remove items from the array.
diff --git a/search_api.module b/search_api.module
index 0d8d407..cf63416 100644
--- a/search_api.module
+++ b/search_api.module
@@ -1071,8 +1071,26 @@ function search_api_current_search($search_id = NULL, SearchApiQuery $query = NU
  * @return array
  *   An associative array with all recognized types as keys, mapped to their
  *   translated display names.
+ *
+ * @see search_api_default_field_types()
+ * @see search_api_custom_field_types()
  */
 function search_api_field_types() {
+  $types = search_api_default_field_types();
+  foreach (search_api_custom_field_types() as $id => $type) {
+    $types[$id] = $type['name'];
+  }
+  return $types;
+}
+
+/**
+ * Returns the default field types recognized by the Search API framework.
+ *
+ * @return array
+ *   An associative array with the default types as keys, mapped to their
+ *   translated display names.
+ */
+function search_api_default_field_types() {
   return array(
     'text' => t('Fulltext'),
     'string' => t('String'),
@@ -1086,6 +1104,38 @@ function search_api_field_types() {
 }
 
 /**
+ * Returns either all custom field type definitions, or a specific one.
+ *
+ * @param $type
+ *   If specified, the type whose definition should be returned.
+ *
+ * @return array
+ *   If $type was not given, an array containing all custom data types, in the
+ *   format specified by hook_search_api_data_type_info().
+ *   Otherwise, the definition for the given type, or NULL if it is unknown.
+ *
+ * @see hook_search_api_data_type_info()
+ */
+function search_api_custom_field_types($type = NULL) {
+  $types = &drupal_static(__FUNCTION__);
+  if (!isset($types)) {
+    $default_types = search_api_default_field_types();
+    $types = module_invoke_all('search_api_data_type_info');
+    $types = $types ? $types : array();
+    foreach ($types as &$type_info) {
+      if (!isset($type_info['fallback']) || !isset($default_types[$type_info['fallback']])) {
+        $type_info['fallback'] = 'string';
+      }
+    }
+    drupal_alter('search_api_data_type_info', $types);
+  }
+  if (isset($type)) {
+    return isset($types[$type]) ? $types[$type] : NULL;
+  }
+  return $types;
+}
+
+/**
  * Returns either a list of all available service infos, or a specific one.
  *
  * @see hook_search_api_service_info()
