diff --git a/includes/commerce_line_item.type.inc b/includes/commerce_line_item.type.inc
deleted file mode 100644
index 6e1b19e..0000000
--- a/includes/commerce_line_item.type.inc
+++ /dev/null
@@ -1,152 +0,0 @@
-<?php
-
-/**
- * @file
- * Provides functionality for inline managing commerce line items.
- */
-
-function inline_entity_form_commerce_line_item_default_fields($types) {
-  $fields = array();
-
-  $fields['line_item_label'] = array(
-    'type' => 'extra_field',
-    'label' => t('Label'),
-    'visible' => TRUE,
-    'weight' => 1,
-  );
-  $fields['commerce_unit_price'] = array(
-    'type' => 'field',
-    'label' => t('Unit price'),
-    'formatter' => 'commerce_price_formatted_amount',
-    'settings' => array(),
-    'visible' => TRUE,
-    'weight' => 2,
-  );
-  $fields['quantity'] = array(
-    'type' => 'extra_field',
-    'label' => t('Quantity'),
-    'visible' => TRUE,
-    'weight' => 3,
-  );
-  $fields['commerce_total'] = array(
-    'type' => 'field',
-    'label' => t('Total'),
-    'formatter' => 'commerce_price_formatted_amount',
-    'settings' => array(),
-    'visible' => TRUE,
-    'weight' => 4,
-  );
-
-  return $fields;
-}
-
-/**
- * IEF add/edit form callback: Returns the line item form to be embedded.
- */
-function inline_entity_form_commerce_line_item_form($form, &$form_state) {
-  $line_item = $form['#entity'];
-
-  $form['#element_validate'] = array(
-    'inline_entity_form_commerce_line_item_form_validate',
-  );
-  $form['#element_submit'] = array(
-    'inline_entity_form_commerce_line_item_form_submit',
-  );
-
-  $form['line_item_details'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Line item details'),
-    '#attributes' => array('class' => array('ief-line_item-details', 'ief-entity-fieldset')),
-  );
-  $form['line_item_label'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Line item label'),
-    '#description' => t('Supply the line item label to be used for this line item.'),
-    '#default_value' => $line_item->line_item_label,
-    '#maxlength' => 128,
-    '#required' => TRUE,
-    '#weight' => -10,
-    '#fieldset' => 'line_item_details',
-  );
-  $form['quantity'] = array(
-    '#type' => 'textfield',
-    '#datatype' => 'integer',
-    '#title' => t('Quantity'),
-    '#description' => t('The quantity of line items.'),
-    '#default_value' => (int) $line_item->quantity,
-    '#size' => 4,
-    '#maxlength' => max(4, strlen($line_item->quantity)),
-    '#required' => TRUE,
-    '#fieldset' => 'line_item_details',
-  );
-  field_attach_form('commerce_line_item', $line_item, $form, $form_state, LANGUAGE_NONE);
-
-  // Tweaks specific to product line items.
-  if (in_array($line_item->type, commerce_product_line_item_types())) {
-    $form['line_item_label']['#access'] = FALSE;
-    $form['commerce_display_path']['#access'] = FALSE;
-    $form['commerce_product']['#weight'] = -100;
-  }
-
-  // Add all fields to the main fieldset.
-  foreach (field_info_instances('commerce_line_item', $line_item->type) as $a => $instance) {
-    $form[$instance['field_name']]['#fieldset'] = 'line_item_details';
-  }
-
-  return $form;
-}
-
-/**
- * IEF add/edit form validation callback.
- */
-function inline_entity_form_commerce_line_item_form_validate($form, &$form_state) {
-  $line_item = $form['#entity'];
-
-  $parents_path = implode('][', $form['#parents']);
-  $line_item_values = drupal_array_get_nested_value($form_state['values'], $form['#parents']);
-  $line_item_label = trim($line_item_values['line_item_label']);
-  $quantity = $line_item_values['quantity'];
-  // Trim leading and trailing whitespace from the line item label.
-  drupal_array_set_nested_value($form_state['values'], array_merge($form['#parents'], array('line_item_label')), $line_item_label);
-
-  if (!is_numeric($quantity) || $quantity <= 0) {
-    form_set_error($parents_path . '][quantity', t('You must specify a positive number for the quantity'));
-  }
-  elseif ($form['quantity']['#datatype'] == 'integer' &&
-    (int) $quantity != $quantity) {
-    form_set_error($parents_path . '][quantity', t('You must specify a whole number for the quantity.'));
-  }
-
-  field_attach_form_validate('commerce_line_item', $line_item, $form, $form_state);
-}
-
-/**
- * IEF add/edit form submit callback: Modifies the passed-in line item before it
- * is saved.
- */
-function inline_entity_form_commerce_line_item_form_submit($form, &$form_state) {
-  $line_item = $form['#entity'];
-  $line_item_values = drupal_array_get_nested_value($form_state['values'], $form['#parents']);
-  $line_item->line_item_label = $line_item_values['line_item_label'];
-  $line_item->quantity = sprintf("%.2f", $line_item_values['quantity']);
-
-  field_attach_submit('commerce_line_item', $line_item, $form, $form_state);
-
-  // Product line item types by default get the product SKU as the label.
-  if (empty($line_item->line_item_id) && in_array($line_item->type, commerce_product_line_item_types())) {
-    $product = commerce_product_load($line_item->commerce_product[LANGUAGE_NONE][0]['product_id']);
-    $line_item->line_item_label = $product->sku;
-  }
-}
-
-/**
- * IEF delete form callback: Returns the confirmation message.
- */
-function inline_entity_form_commerce_line_item_delete_form($form, $form_state) {
-  $line_item = $form['#entity'];
-  $form['message'] = array(
-    '#markup' => '<div>' . t('Are you sure you want to delete %title?', array('%title' => $line_item->line_item_label)) . '</div>',
-  );
-
-  return $form;
-}
diff --git a/includes/commerce_product.type.inc b/includes/commerce_product.type.inc
deleted file mode 100644
index a111851..0000000
--- a/includes/commerce_product.type.inc
+++ /dev/null
@@ -1,346 +0,0 @@
-<?php
-
-/**
- * @file
- * Provides functionality for inline managing commerce products.
- */
-
-/**
- * IEF settings callback: Returns a settings form embedded into the IEF widget
- * settings form. The returned settings are later available in form callbacks
- * through $form['#settings'].
- */
-function inline_entity_form_commerce_product_settings($settings, $instance) {
-  $form = array();
-  $form['autogenerate_title'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Auto generate the product title'),
-    '#description' => t('This will hide the title input field and generate the title by appending any available attributes to the @entity title.', array('@entity' => $instance['entity_type'])),
-    '#default_value' => isset($settings['autogenerate_title']) ? $settings['autogenerate_title'] : TRUE,
-  );
-
-  return $form;
-}
-
-/**
- * IEF default fields callback: Returns an array of fields (which can be either
- * Field API fields or properties provided through hook_field_extra_fields())
- * that should be used to represent a selected product.
- *
- * The IEF widget can have its own fields specified in the widget settings, in
- * which case the output of this function is ingored.
- *
- * @param $types
- *   An array of allowed product types for this widget.
- *
- * @return
- *   An array of field information, keyed by field name. Allowed keys:
- *   - type: 'field' or 'extra_field',
- *   - label: Human readable name of the field, shown to the user.
- *   - weight: The position of the field relative to other fields.
- *   - visible: Whether the field should be displayed.
- *   Special keys for type 'field':
- *   - formatter: The formatter used to display the field, or "hidden".
- *   - settings: An array passed to the formatter. If empty, defaults are used.
- *   - delta: If provided, limits the field to just the specified delta.
- */
-function inline_entity_form_commerce_product_default_fields($types) {
-  $fields = array();
-
-  $fields['title'] = array(
-    'type' => 'extra_field',
-    'label' => t('Variation title'),
-    'visible' => TRUE,
-    'weight' => 1,
-  );
-  $fields['sku'] = array(
-    'type' => 'extra_field',
-    'label' => 'SKU',
-    'visible' => TRUE,
-    'weight' => 2,
-  );
-
-  // If only one product type is allowed, its fields can be used as columns.
-  if (count($types) == 1) {
-    $type = reset($types);
-
-    foreach (field_info_instances('commerce_product', $type) as $field_name => $instance) {
-      $field = field_info_field($field_name);
-
-      // If the product has an imagefield, show it.
-      if ($field['type'] == 'image') {
-        $fields[$field_name] = array(
-          'type' => 'field',
-          'label' => $instance['label'],
-          'formatter' => 'image',
-          'settings' => array('image_style' => 'thumbnail'),
-          'delta' => 0,
-          'visible' => TRUE,
-          'weight' => -10,
-        );
-        // Don't add any other imagefields. One is enough.
-        break;
-      }
-    }
-
-    // If the type has up to 3 attributes, show them instead of the title field.
-    $attributes = _inline_entity_form_commerce_product_attributes($type);
-    if (count($attributes) <= 3) {
-      $fields['title']['visible'] = FALSE;
-
-      foreach ($attributes as $field_name => $attribute) {
-        $field_type = field_info_field_types($attribute['field']['type']);
-
-        $weight = -3;
-        $fields[$field_name] = array(
-          'type' => 'field',
-          'label' => $attribute['instance']['label'],
-          'formatter' => $field_type['default_formatter'],
-          'settings' => array(),
-          'visible' => TRUE,
-          'weight' => ++$weight,
-        );
-      }
-    }
-  }
-
-  $fields['commerce_price'] = array(
-    'type' => 'field',
-    'label' => t('Price'),
-    'formatter' => 'commerce_price_formatted_amount',
-    'settings' => array(),
-    'visible' => TRUE,
-    'weight' => 99,
-  );
-
-  return $fields;
-}
-
-/**
- * IEF add/edit form callback: Returns the product form to be embedded.
- *
- * When adding data to $form_state it should be noted that there can be several
- * IEF widgets on one master form, each with several form rows, leading to
- * possible key collisions if the keys are not prefixed with $parents.
- */
-function inline_entity_form_commerce_product_form($form, &$form_state) {
-  $product = $form['#entity'];
-
-  $form['#element_validate'] = array(
-    'inline_entity_form_commerce_product_form_validate',
-  );
-  $form['#element_submit'] = array(
-    'inline_entity_form_commerce_product_form_submit',
-  );
-
-  $form['product_attributes'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Attributes'),
-    '#attributes' => array('class' => array('container-inline', 'ief-product-attributes', 'ief-entity-fieldset')),
-  );
-  $form['product_details'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Details'),
-    '#attributes' => array('class' => array('ief-product-details', 'ief-entity-fieldset')),
-  );
-  $form['product_image'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Variation image'),
-    '#attributes' => array('class' => array('ief-product-image', 'ief-entity-fieldset')),
-  );
-
-  $language = !empty($product->language) ? $product->language : LANGUAGE_NONE;
-  $form['sku'] = array(
-    '#type' => 'textfield',
-    '#title' => t('SKU'),
-    '#description' => t('Supply a unique identifier using letters, numbers, hyphens, and underscores. Commas may not be used.'),
-    '#default_value' => $product->sku,
-    '#maxlength' => 128,
-    '#required' => TRUE,
-    '#fieldset' => 'product_details',
-    '#weight' => -10,
-  );
-  $form['title'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Variation Title'),
-    '#default_value' => $product->title,
-    '#maxlength' => 128,
-    '#required' => TRUE,
-    '#fieldset' => 'product_details',
-    '#weight' => -9,
-  );
-  // Hide or disable the SKU field if it is auto-generated by Commerce AutoSKU.
-  if (module_exists('commerce_autosku') && $settings = commerce_autosku_get_settings($product)) {
-    $form['sku']['#required'] = FALSE;
-    $form['sku']['#disabled'] = TRUE;
-    if ($settings['advanced']['hide_sku']) {
-      $form['sku']['#access'] = FALSE;
-    }
-    else {
-      $form['sku']['#description'] = t('SKU will be automatically generated.');
-    }
-  }
-  // Hide the title field if it is auto-generated.
-  if (!empty($form['#settings']['autogenerate_title'])) {
-    $form['title']['#required'] = FALSE;
-    $form['title']['#access'] = FALSE;
-  }
-
-  // Attach fields.
-  field_attach_form('commerce_product', $product, $form, $form_state, $language);
-
-  // Arrange attributes.
-  $attributes = _inline_entity_form_commerce_product_attributes($product->type);
-  if (empty($attributes)) {
-    // Hide the fieldset, it will be empty.
-    $form['product_attributes']['#access'] = FALSE;
-  }
-  else {
-    foreach ($attributes as $field_name => $attribute) {
-      $form[$field_name]['#fieldset'] = 'product_attributes';
-    }
-  }
-
-  // Arrange images and other non-attribute fields.
-  $imagefields = array();
-  foreach (field_info_instances('commerce_product', $product->type) as $name => $instance) {
-    $field_name = $instance['field_name'];
-    $field = field_info_field($field_name);
-
-    if ($field['type'] == 'image') {
-      $form[$field_name]['#fieldset'] = 'product_image';
-      $imagefields[] = $field_name;
-    }
-    elseif (!isset($attributes[$field_name])) {
-      $form[$field_name]['#fieldset'] = 'product_details';
-    }
-  }
-
-  if (count($imagefields) == 0) {
-    // The fieldset is empty, hide it.
-    $form['product_image']['#access'] = FALSE;
-  }
-  elseif (count($imagefields) == 1) {
-    // There's only one image on the product, no need to show its title, the
-    // fieldset title is enough.
-    $field_name = reset($imagefields);
-    $language = $form[$field_name]['#language'];
-    unset($form[$field_name][$language][0]['#title']);
-    // Prevent multi-value imagefields from rendering their own fieldsets.
-    if (isset($form[$field_name][$language]['#theme_wrappers']) && in_array('fieldset', $form[$field_name][$language]['#theme_wrappers'])) {
-      $key = array_search('fieldset', $form[$field_name][$language]['#theme_wrappers']);
-      unset($form[$field_name][$language]['#theme_wrappers'][$key]);
-    }
-  }
-
-  return $form;
-}
-
-/**
- * IEF add/edit form validation callback.
- */
-function inline_entity_form_commerce_product_form_validate(&$form, &$form_state) {
-  $product = &$form['#entity'];
-
-  $parents_path = implode('][', $form['#parents']);
-  $product_values = drupal_array_get_nested_value($form_state['values'], $form['#parents']);
-  $sku = trim($product_values['sku']);
-
-  // Ensure the proposed SKU is unique.
-  if (!commerce_product_validate_sku_unique($sku, $product->product_id)) {
-    form_set_error($parents_path . '][sku', t('This SKU is already in use and must be unique. Please supply another value.'));
-  }
-  // Validate the SKU for invalid characters.
-  if (!commerce_product_validate_sku($sku)) {
-    form_set_error($parents_path . '][sku', t('The SKU %sku contains invalid characters.', array('%sku' => $sku)));
-  }
-  // Trim leading and trailing whitespace from the SKU.
-  drupal_array_set_nested_value($form_state['values'], array_merge($form['#parents'], array('sku')), $sku);
-
-  field_attach_form_validate('commerce_product', $product, $form, $form_state);
-}
-
-/**
- * IEF add/edit form submit callback.
- */
-function inline_entity_form_commerce_product_form_submit(&$form, &$form_state) {
-  global $user;
-
-  $product_values = drupal_array_get_nested_value($form_state['values'], $form['#parents']);
-  $product = &$form['#entity'];
-  $product->sku = $product_values['sku'];
-  // Assign newly created products to the current user.
-  if (empty($product->product_id)) {
-    $product->uid = $user->uid;
-  }
-
-  field_attach_submit('commerce_product', $product, $form, $form_state);
-  inline_entity_form_cleanup_field_form_state($form, $form_state);
-
-  if (empty($form['#settings']['autogenerate_title'])) {
-    $product->title = $product_values['title'];
-  }
-  else {
-    // Generate the product title. Take the parent entity title as the base.
-    $product->title = $form_state['values']['title'];
-    $attributes = _inline_entity_form_commerce_product_attributes($product->type);
-    if (!empty($attributes)) {
-      $wrapper = entity_metadata_wrapper('commerce_product', $product);
-      foreach ($attributes as $field_name => $attribute) {
-        $attribute_values[] = $wrapper->{$field_name}->label();
-      }
-      $product->title .= ' (' . implode(', ', $attribute_values) . ')';
-    }
-  }
-}
-
-/**
- * IEF delete form callback: Returns the confirmation message.
- */
-function inline_entity_form_commerce_product_delete_form($form, $form_state) {
-  $product = $form['#entity'];
-  $form['message'] = array(
-    '#markup' => '<div>' . t('Are you sure you want to delete %title?', array('%title' => $product->title)) . '</div>',
-  );
-
-  return $form;
-}
-
-/**
- * Returns a list of field names that are used as attributes for the given
- * product type.
- */
-function _inline_entity_form_commerce_product_attributes($type) {
-  // Attributes are tied to the commerce_cart module.
-  if (!module_exists('commerce_cart')) {
-    return array();
-  }
-
-  $attributes = array();
-  // Loop through all the field instances on that product type.
-  foreach (field_info_instances('commerce_product', $type) as $name => $instance) {
-    // A field qualifies if it is single value, required and uses a widget
-    // with a definite set of options. For the sake of simplicity, this is
-    // currently restricted to fields defined by the options module.
-    $field = field_info_field($instance['field_name']);
-
-    // Get the array of Cart settings pertaining to this instance.
-    $commerce_cart_settings = commerce_cart_field_instance_attribute_settings($instance);
-
-    // If the instance is of a field type that is eligible to function as
-    // a product attribute field and if its attribute field settings
-    // specify that this functionality is enabled...
-    if (commerce_cart_field_attribute_eligible($field) && $commerce_cart_settings['attribute_field']) {
-      $attributes[$name] = array(
-        'field' => $field,
-        'instance' => $instance,
-        'weight' => $instance['widget']['weight'],
-      );
-    }
-  }
-
-  // Sort the fields by weight.
-  uasort($attributes, 'drupal_sort_weight');
-
-  return $attributes;
-}
diff --git a/inline_entity_form.api.php b/inline_entity_form.api.php
index 3695b6e..a922b2c 100644
--- a/inline_entity_form.api.php
+++ b/inline_entity_form.api.php
@@ -4,67 +4,3 @@
  * @file
  * Hooks provided by the Inline Entity Form module.
  */
-
-/**
- * Defines inline entity types, managed through the inline entity form widget.
- *
- * Supported keys:
- * - file: the filepath of an include file containing the callback functions
- *   for this type.
- * - callbacks: an array of callback function names used by the widget for
- *   various operations on the referenced entities (list, add, edit, delete).
- *   - settings: Provides a settings form shown in the widget settings.
- *     The settings are later available to all form callbacks.
- *   - default fields: Provides a default list of fields that should be used to
- *     represent each selected entity.
- *   - form: Provides an add / edit form.
- *   - delete form: Provides a confirmation delete form.
- * - labels: an array of labels shown in the widget for this entity type.
- *   - add fieldset: Label of the fieldset shown around the add form.
- *   - add button: Label of the button that launches the add form.
- *   - save button: Label of the save button on the add / edit form.
- * - empty text: Text shown above the add form fieldset when no entities
- *   have been added yet.
- * - css - an array of css filepaths for the inline entity type.
- *   - base - The base css file, included in all themes.
- *   - seven - The Seven-specific css file, included in that theme only.
- *
- * @return
- *   An array of types, keyed by entity type.
- */
-function hook_inline_entity_type_info() {
-  $types = array();
-  $types['commerce_product'] = array(
-    'file' => drupal_get_path('module', 'inline_entity_form') . '/includes/commerce_product.type.inc',
-    'callbacks' => array(
-      'settings' => 'inline_entity_form_commerce_product_settings',
-      'default fields' => 'inline_entity_form_commerce_product_default_fields',
-      'form' => 'inline_entity_form_commerce_product_form',
-      'delete form' => 'inline_entity_form_commerce_product_delete_form',
-    ),
-    'labels' => array(
-      'add fieldset' => t('Add new product variation'),
-      'add button' => t('Add variation'),
-      'save button' => t('Save variation'),
-    ),
-    'empty text' => t('No product variations have been created. At least one variation is required.'),
-    'css' => array(
-      'base' => drupal_get_path('module', 'inline_entity_form') . '/includes/entity_types/commerce-product.css',
-      'seven' => drupal_get_path('module', 'inline_entity_form') . '/includes/entity_types/commerce-product.seven.css',
-    ),
-  );
-
-  return $types;
-}
-
-/**
- * Allows modules to alter inline entity types defined by other modules.
- *
- * @param $types
- *   The array of inline entity type arrays.
- *
- * @see hook_inline_entity_type_info()
- */
-function hook_inline_entity_type_info_alter(&$types) {
-  $types['commerce_product']['labels']['save button'] = t('Save');
-}
diff --git a/inline_entity_form.info b/inline_entity_form.info
index 6bb0ee7..3b7bf1a 100644
--- a/inline_entity_form.info
+++ b/inline_entity_form.info
@@ -3,3 +3,7 @@ description = "Provides a widget for inline management (creation, modification,
 package = Fields
 dependencies[] = entity
 core = 7.x
+
+files[] = includes/entity.inline_entity_form.inc
+files[] = includes/commerce_product.inline_entity_form.inc
+files[] = includes/commerce_line_item.inline_entity_form.inc
diff --git a/inline_entity_form.js b/inline_entity_form.js
deleted file mode 100644
index 5d809d3..0000000
--- a/inline_entity_form.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @file
- * Provides JavaScript for Inline Entity Form.
- */
-
-(function ($) {
-
-/**
- * Allows submit buttons in entity forms to trigger uploads by undoing
- * work done by Drupal.behaviors.fileButtons.
- */
-Drupal.behaviors.inlineEntityForm = {
-  attach: function (context) {
-    $('input.ief-entity-submit', context).unbind('mousedown', Drupal.file.disableFields);
-  },
-  detach: function (context) {
-    $('input.form-submit', context).bind('mousedown', Drupal.file.disableFields);
-  }
-};
-
-})(jQuery);
diff --git a/inline_entity_form.module b/inline_entity_form.module
index daaf87b..06cf052 100644
--- a/inline_entity_form.module
+++ b/inline_entity_form.module
@@ -9,96 +9,15 @@
  */
 
 /**
- * Implements hook_inline_entity_type_info().
+ * Implements hook_entity_info_alter().
  */
-function inline_entity_form_inline_entity_type_info() {
-  $types = array();
-  $types['commerce_product'] = array(
-    'file' => drupal_get_path('module', 'inline_entity_form') . '/includes/commerce_product.type.inc',
-    'callbacks' => array(
-      'settings' => 'inline_entity_form_commerce_product_settings',
-      'default fields' => 'inline_entity_form_commerce_product_default_fields',
-      'form' => 'inline_entity_form_commerce_product_form',
-      'delete form' => 'inline_entity_form_commerce_product_delete_form',
-    ),
-    'labels' => array(
-      'add fieldset' => t('Add new product variation'),
-      'add button' => t('Add variation'),
-      'save button' => t('Save variation'),
-    ),
-    'empty text' => t('No product variations have been created. At least one variation is required.'),
-    'css' => array(
-      'base' => drupal_get_path('module', 'inline_entity_form') . '/includes/entity_types/commerce-product.css',
-    ),
+function inline_entity_form_entity_info_alter(&$entity_info) {
+  $entity_info['commerce_product']['inline entity form'] = array(
+    'controller' => 'CommerceProductInlineEntityFormController',
   );
-  $types['commerce_line_item'] = array(
-    'file' => drupal_get_path('module', 'inline_entity_form') . '/includes/commerce_line_item.type.inc',
-    'callbacks' => array(
-      'default fields' => 'inline_entity_form_commerce_line_item_default_fields',
-      'form' => 'inline_entity_form_commerce_line_item_form',
-      'delete form' => 'inline_entity_form_commerce_line_item_delete_form',
-    ),
-    'labels' => array(
-      'add fieldset' => t('Add new line item'),
-      'add button' => t('Add line item'),
-      'save button' => t('Save line item'),
-    ),
-    'empty text' => t('No line items found.'),
+  $entity_info['commerce_line_item']['inline entity form'] = array(
+    'controller' => 'CommerceLineItemInlineEntityFormController',
   );
-
-  return $types;
-}
-
-/**
- * Return an array of all defined inline entity types.
- *
- * @return
- *   The array of types, keyed by entity type.
- */
-function inline_entity_form_types() {
-  $types = &drupal_static(__FUNCTION__);
-
-  if (!isset($types)) {
-    $types = array();
-    foreach (module_implements('inline_entity_type_info') as $module) {
-      foreach (module_invoke($module, 'inline_entity_type_info') as $entity_type => $info) {
-        $info += array(
-          // Remember the providing module.
-          'module' => $module,
-          // Provide defaults.
-          'callbacks' => array(),
-          'labels' => array(),
-          'css' => array(),
-          'empty text' => '',
-        );
-        $types[$entity_type] = $info;
-      }
-    }
-
-    drupal_alter('inline_entity_type_info', $types);
-  }
-
-  return $types;
-}
-
-/**
- * Loads the data for a specific inline entity type.
- *
- * @param $entity_type
- *   Entity type.
- *
- * @return
- *   The requested array or FALSE if the entity type is not supported.
- */
-function inline_entity_form_type($entity_type) {
-  $types = inline_entity_form_types();
-
-  // The passed-in entity type is not supported for inline management.
-  if (empty($types[$entity_type])) {
-    return FALSE;
-  }
-
-  return $types[$entity_type];
 }
 
 /**
@@ -110,6 +29,10 @@ function inline_entity_form_type($entity_type) {
  *   The $form['#attached']['css'] array, modified by reference.
  */
 function _inline_entity_form_attach_css($theme_css, &$css) {
+  if (empty($theme_css)) {
+    return;
+  }
+
   // Add the base CSS file, if provided.
   if (!empty($theme_css['base'])) {
     $css[] = $theme_css['base'];
@@ -164,13 +87,10 @@ function inline_entity_form_field_widget_settings_form($field, $instance) {
   $settings = $widget['settings'];
   $ief_settings = inline_entity_form_settings($field, $instance);
   $entity_info = entity_get_info($ief_settings['entity_type']);
-  $type_info = inline_entity_form_type($ief_settings['entity_type']);
   // The current entity type is not supported, execution can't continue.
-  if (!$type_info) {
+  if (!isset($entity_info['inline entity form'])) {
     return array();
   }
-  // The callbacks are in another file, include it.
-  include_once $type_info['file'];
 
   $element = array();
   // The fields are not editable from the UI for now.
@@ -179,13 +99,15 @@ function inline_entity_form_field_widget_settings_form($field, $instance) {
     '#value' => $settings['fields'],
   );
 
-  // Add type-specific settings if they exist.
-  if (!empty($type_info['callbacks']['settings'])) {
+  // Add entity type specific settings if they exist.
+  $controller = new $entity_info['inline entity form']['controller']($ief_settings['entity_type'], $settings['type_settings']);
+  $settings_form = $controller->settingsForm($field, $instance);
+  if (!empty($settings_form)) {
     $element['type_settings'] = array(
       '#type' => 'fieldset',
       '#title' => t('Inline Entity Form: %type', array('%type' => $entity_info['label'])),
     );
-    $element['type_settings'] += $type_info['callbacks']['settings']($settings['type_settings'], $instance);
+    $element['type_settings'] += $settings_form;
   }
 
   return $element;
@@ -252,65 +174,65 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins
 
   $widget = $instance['widget'];
   $settings = inline_entity_form_settings($field, $instance);
-  $type_info = inline_entity_form_type($settings['entity_type']);
+  $entity_info = entity_get_info($settings['entity_type']);
   $type_settings = $widget['settings']['type_settings'];
   // The current entity type is not supported, execution can't continue.
-  if (!$type_info) {
-    return $element;
+  if (!isset($entity_info['inline entity form'])) {
+    return array();
   }
-  // The callbacks are in another file, include it.
-  include_once $type_info['file'];
 
-  if ($widget['type'] == 'inline_entity_form') {
-    // Build a parents array for this element's values in the form.
-    $parents = array_merge($element['#field_parents'], array($element['#field_name'], $element['#language']));
-    $parents_key = strtr(implode('-', $parents), '_', '-');
+  // Instantiate the inline entity form controller for the current entity type.
+  $controller = new $entity_info['inline entity form']['controller']($settings['entity_type'], $type_settings);
+  // Get the UI strings.
+  $strings = $controller->strings();
 
-    if (!isset($parents_delta[$parents_key])) {
-      $parents_delta[$parents_key] = count($parents_delta);
-    }
+  // Build a parents array for this element's values in the form.
+  $parents = array_merge($element['#field_parents'], array($element['#field_name'], $element['#language']));
+  $parents_key = strtr(implode('-', $parents), '_', '-');
+  if (!isset($parents_delta[$parents_key])) {
+    $parents_delta[$parents_key] = count($parents_delta);
+  }
 
-    // Determine the wrapper ID for the entire element.
-    $wrapper = 'inline-entity-form-' . $parents_key;
-    $element = array(
-      '#type' => 'fieldset',
-      '#tree' => TRUE,
-      '#element_validate' => array('inline_entity_form_element_validate'),
-      '#description' => NULL,
-      '#prefix' => '<div id="' . $wrapper . '">',
-      '#suffix' => '</div>',
-      '#attached' => array(
-        'css' => array(),
-        'js' => array(
-          // Give the JS file a weight so that it runs after file.js.
-          drupal_get_path('module', 'inline_entity_form') . '/inline_entity_form.js' => array('weight' => 10),
-        ),
+  // Determine the wrapper ID for the entire element.
+  $wrapper = 'inline-entity-form-' . $parents_key;
+  $element = array(
+    '#type' => 'fieldset',
+    '#tree' => TRUE,
+    '#description' => NULL,
+    '#prefix' => '<div id="' . $wrapper . '">',
+    '#suffix' => '</div>',
+    '#attached' => array(
+      'css' => array(),
+      'js' => array(
+        // Give the JS file a weight so that it runs after file.js.
+        drupal_get_path('module', 'inline_entity_form') . '/inline_entity_form.js' => array('weight' => 10),
       ),
-    ) + $element;
+    ),
+  ) + $element;
 
-    $base_css = array(
-      'base' => drupal_get_path('module', 'inline_entity_form') . '/theme/inline_entity_form.css',
-      'seven' => drupal_get_path('module', 'inline_entity_form') . '/theme/inline_entity_form.seven.css',
-    );
-    // Add the base module CSS.
-    _inline_entity_form_attach_css($base_css, $element['#attached']['css']);
-    // Add entity type specific CSS.
-    _inline_entity_form_attach_css($type_info['css'], $element['#attached']['css']);
-
-    // Load the entities from the $items array and store them in the form state
-    // for further manipulation if this is the first time the widget form is
-    // being built.
-    if (empty($form_state['inline_entity_form'][$parents_key])) {
-      // Initialize the last action to NULL.
-      $form_state['inline_entity_form'][$parents_key]['action'] = NULL;
+  $base_css = array(
+    'base' => drupal_get_path('module', 'inline_entity_form') . '/theme/inline_entity_form.css',
+    'seven' => drupal_get_path('module', 'inline_entity_form') . '/theme/inline_entity_form.seven.css',
+  );
+  // Add the base module CSS.
+  _inline_entity_form_attach_css($base_css, $element['#attached']['css']);
+  // Add entity type specific CSS.
+  _inline_entity_form_attach_css($controller->css(), $element['#attached']['css']);
 
-      // Initialize the add form to NULL.
-      $form_state['inline_entity_form'][$parents_key]['add_form'] = NULL;
+  if ($widget['type'] == 'inline_entity_form') {
+    $element['#element_validate'] = array('inline_entity_form_element_validate');
 
-      // Add the element's settings to the array.
-      $form_state['inline_entity_form'][$parents_key]['settings'] = $settings;
+    // Initialize the IEF array in form state.
+    if (empty($form_state['inline_entity_form'][$parents_key])) {
+      $form_state['inline_entity_form'][$parents_key] = array(
+        'action' => NULL,
+        'add_form' => NULL,
+        'settings' => $settings,
+        'type_settings' => $type_settings,
+      );
 
-      // Extract the entity IDs from the items array and load the entities.
+      // Load the entities from the $items array and store them in the form
+      // state for further manipulation.
       $form_state['inline_entity_form'][$parents_key]['entities'] = array();
       $entity_ids = array();
       foreach ($items as $item) {
@@ -331,7 +253,10 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins
 
       // If no entities were found, open the add form.
       if (empty($form_state['inline_entity_form'][$parents_key]['entities'])) {
-        $element['#description'] = $type_info['empty text'];
+        $element['#description'] = $strings['empty text'];
+        if ($instance['required']) {
+          $element['#description'] .= ' ' . $strings['required'];
+        }
 
         if (count($settings['bundles']) == 1) {
           $form_state['inline_entity_form'][$parents_key]['add_form'] = array(
@@ -354,7 +279,7 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins
       $element['entities']['#fields'] = $widget['settings']['fields'];
     }
     else {
-      $element['entities']['#fields'] = $type_info['callbacks']['default fields']($settings['bundles']);
+      $element['entities']['#fields'] = $controller->defaultFields($settings['bundles']);
     }
 
     foreach ($form_state['inline_entity_form'][$parents_key]['entities'] as $key => $value) {
@@ -373,26 +298,26 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins
         $element['entities'][$key]['form'] = array(
           '#type' => 'container',
           '#attributes' => array('class' => array('inline-entity-form-title')),
-          // Used by Field API, #element_validate and #element_submit to find
-          // the relevant values in $form_state.
+          '#op' => $value['form'],
+          // Identifies the IEF widget to which the form belongs.
+          '#ief_parents_key' => $parents_key,
+          '#delta' => $parents_delta[$parents_key] . '-' . $key,
+          // Used by Field API and controller methods to find the relevant
+          // values in $form_state.
           '#parents' => array_merge($parents, array('entities', $key, 'form')),
-          // Store the entity on the form, to be modified in #element_submit.
+          // Store the entity on the form, later modified in the controller.
           '#entity' => $entity,
           '#entity_type' => $settings['entity_type'],
-          // Add IEF-specific keys to be used when generating form actions.
-          '#ief_wrapper' => $wrapper,
-          '#op' => $value['form'],
-          '#delta' => $parents_delta[$parents_key] . '-' . $key,
         );
         // Prepare data for the form callbacks.
         $form = &$element['entities'][$key]['form'];
 
         // Add the appropriate form.
         if ($value['form'] == 'edit') {
-          $form += inline_entity_form_type_form($settings['entity_type'], $form, $form_state, $type_settings);
+          $form += inline_entity_form_entity_form($controller, $form, $form_state);
         }
         elseif ($value['form'] == 'delete') {
-          $form += inline_entity_form_type_delete_form($settings['entity_type'], $form, $form_state, $type_settings);
+          $form += inline_entity_form_delete_form($controller, $form, $form_state);
         }
       }
       else {
@@ -442,8 +367,7 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins
       // Let the user select the bundle, if multiple are available.
       if (count($settings['bundles']) > 1) {
         $bundles = array();
-        $info = entity_get_info($settings['entity_type']);
-        foreach ($info['bundles'] as $bundle_name => $bundle_info) {
+        foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
           if (in_array($bundle_name, $settings['bundles'])) {
             $bundles[$bundle_name] = $bundle_info['label'];
           }
@@ -463,7 +387,7 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins
 
       $element['actions']['ief_add'] = array(
         '#type' => 'submit',
-        '#value' => $type_info['labels']['add button'],
+        '#value' => $strings['add button'],
         '#name' => 'ief-' . $parents_delta[$parents_key] . '-add',
         '#limit_validation_errors' => array(),
         '#submit' => array(),
@@ -479,8 +403,7 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins
       $form_settings = &$form_state['inline_entity_form'][$parents_key]['add_form'];
 
       // Create a new entity that will be passed to the form.
-      $info = entity_get_info($settings['entity_type']);
-      $bundle_key = $info['entity keys']['bundle'];
+      $bundle_key = $entity_info['entity keys']['bundle'];
       $default_values = array();
       // If the bundle key exists, it must always be set on an entity.
       if (!empty($bundle_key)) {
@@ -490,205 +413,168 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins
 
       $element['add_form'] = array(
         '#type' => 'fieldset',
-        '#title' => $type_info['labels']['add fieldset'],
+        '#title' => $strings['add fieldset'],
+        '#op' => 'add',
+        // Identifies the IEF widget to which the form belongs.
+        '#ief_parents_key' => $parents_key,
+        '#delta' => $parents_delta[$parents_key],
         // Used by Field API, #element_validate and #element_submit to find
         // the relevant values in $form_state.
         '#parents' => array_merge($parents, array('add_form')),
         // Store the entity on the form, to be modified in #element_submit.
         '#entity' => $entity,
         '#entity_type' => $settings['entity_type'],
-        // Add IEF-specific keys to be used when generating form actions.
-        '#ief_wrapper' => $wrapper,
-        '#op' => 'add',
-        '#delta' => $parents_delta[$parents_key],
       );
-      $element['add_form'] += inline_entity_form_type_form($settings['entity_type'], $element['add_form'], $form_state, $type_settings);
+      $element['add_form'] += inline_entity_form_entity_form($controller, $element['add_form'], $form_state);
 
       // Make sure the Cancel button is not shown when there are no entities.
       if (empty($form_state['inline_entity_form'][$parents_key]['entities'])) {
         $element['add_form']['actions']['ief_add_cancel']['#access'] = FALSE;
       }
     }
-
-    return $element;
   }
+
+  return $element;
 }
 
 /**
- * Returns the add / edit form for the passed entity type.
+ * Wraps and returns the entity form provided by the passed-in controller.
  *
- * @param $entity_type
- *   The entity type being managed inline.
- * @param $form
- *   The form array that will receive the add / edit form.
+ * @param $controller
+ *   The inline entity form controller.
+ * @param $entity_form
+ *   The form array that will receive the entity form.
  * @param $form_state
- *   The form state of the complete IEF widget.
- * @param $type_settings
- *   Type-specific settings specified through the IEF widget settings.
+ *   The form state of the parent form.
  *
  * @return
- *   The form array containing the embedded add / edit form.
+ *   The form array containing the embedded entity form.
  */
-function inline_entity_form_type_form($entity_type, $form, &$form_state, $type_settings) {
-  $type_info = inline_entity_form_type($entity_type);
-
-  // Ensure that the include file is loaded when the form is rebuilt from cache.
-  $form_state['build_info']['files']['inline_form'] = $type_info['file'];
-
-  // Store the type settings so that the callbacks can access them.
-  $form['#settings'] = $type_settings;
-  // Retrieve the form provided by the type callback.
-  $form = $type_info['callbacks']['form']($form, $form_state);
+function inline_entity_form_entity_form($controller, $entity_form, &$form_state) {
+  $strings = $controller->strings();
 
+  // Retrieve the form provided by the controller.
+  $entity_form = $controller->entityForm($entity_form, $form_state);
   // Add the actions
-  $form['actions'] = array(
+  $entity_form['actions'] = array(
     '#type' => 'container',
     '#weight' => 100,
   );
-  $form['actions']['ief_' . $form['#op'] . '_save'] = array(
+  $entity_form['actions']['ief_' . $entity_form['#op'] . '_save'] = array(
     '#type' => 'submit',
-    '#value' => $type_info['labels']['save button'],
-    '#name' => 'ief-' . $form['#op'] . '-submit-' . $form['#delta'],
-    '#limit_validation_errors' => array($form['#parents']),
+    '#value' => $strings['save button'],
+    '#name' => 'ief-' . $entity_form['#op'] . '-submit-' . $entity_form['#delta'],
+    '#limit_validation_errors' => array($entity_form['#parents']),
     '#submit' => array(),
     '#attributes' => array('class' => array('ief-entity-submit')),
     '#ajax' => array(
       'callback' => 'inline_entity_form_widget_refresh',
-      'wrapper' => $form['#ief_wrapper'],
+      'wrapper' => 'inline-entity-form-' . $entity_form['#ief_parents_key'],
     ),
   );
-  $form['actions']['ief_' . $form['#op'] . '_cancel'] = array(
+  $entity_form['actions']['ief_' . $entity_form['#op'] . '_cancel'] = array(
     '#type' => 'submit',
     '#value' => t('Cancel'),
-    '#name' => 'ief-' . $form['#op'] . '-cancel-' . $form['#delta'],
+    '#name' => 'ief-' . $entity_form['#op'] . '-cancel-' . $entity_form['#delta'],
     '#limit_validation_errors' => array(),
     '#submit' => array(),
     '#ajax' => array(
       'callback' => 'inline_entity_form_widget_refresh',
-      'wrapper' => $form['#ief_wrapper'],
+      'wrapper' => 'inline-entity-form-' . $entity_form['#ief_parents_key'],
     ),
   );
 
-  // Add the inline_entity_form_process_submit validation callback.
-  // It is special and always needs to be executed last. It calls the callbacks
-  // defined in #element_submit (Drupal core only defines #element_validate).
-  $form['#element_validate'][] = 'inline_entity_form_process_submit';
+  // Add the validation callback that calls the controller's
+  // entityFormValidate() and entityFormSubmit() methods.
+  $entity_form['#element_validate'][] = 'inline_entity_form_process_entity_form';
   // Add the pre_render callback that powers the #fieldset form element key,
   // which moves the element to the specified fieldset without modifying its
   // position in $form_state['values'].
-  $form['#pre_render'][] = 'inline_entity_form_pre_render_add_fieldset_markup';
+  $entity_form['#pre_render'][] = 'inline_entity_form_pre_render_add_fieldset_markup';
 
-  return $form;
+  return $entity_form;
 }
 
 /**
- * Returns the delete form for the passed entity type.
+ * Wraps and returns the delete form provided by the passed-in controller.
  *
- * @param $entity_type
- *   The entity type being managed inline.
- * @param $form
- *   The form array that will receive the delete form.
+ * @param $controller
+ *   The inline entity form controller.
+ * @param $delete_form
+ *   The form array that will receive the entity form.
  * @param $form_state
- *   The form state of the complete IEF widget.
- * @param $type_settings
- *   Type-specific settings specified through the IEF widget settings.
+ *   The form state of the parent form.
  *
  * @return
  *   The form array containing the embedded delete form.
  */
-function inline_entity_form_type_delete_form($entity_type, $form, &$form_state, $type_settings) {
-  $type_info = inline_entity_form_type($entity_type);
-
-  // Store the type settings so that the callbacks can access them.
-  $form['#settings'] = $type_settings;
-  // Retrieve the form provided by the type callback.
-  $form = $type_info['callbacks']['delete form']($form, $form_state);
+function inline_entity_form_delete_form($controller, $delete_form, &$form_state) {
+  // Retrieve the form provided by the controller.
+  $delete_form = $controller->deleteForm($delete_form, $form_state);
 
   // Add the actions
-  $form['actions'] = array(
+  $delete_form['actions'] = array(
     '#type' => 'container',
     '#weight' => 100,
   );
-  $form['actions']['ief_delete_confirm'] = array(
+  $delete_form['actions']['ief_delete_confirm'] = array(
     '#type' => 'submit',
     '#value' => t('Delete'),
-    '#name' => 'ief-delete-confirm-' . $form['#delta'],
+    '#name' => 'ief-delete-confirm-' . $delete_form['#delta'],
     '#limit_validation_errors' => array(),
     '#submit' => array(),
     '#ajax' => array(
       'callback' => 'inline_entity_form_widget_refresh',
-      'wrapper' => $form['#ief_wrapper'],
+      'wrapper' => 'inline-entity-form-' . $delete_form['#ief_parents_key'],
     ),
   );
-  $form['actions']['ief_delete_cancel'] = array(
+  $delete_form['actions']['ief_delete_cancel'] = array(
     '#type' => 'submit',
     '#value' => t('Cancel'),
-    '#name' => 'ief-delete-cancel-' . $form['#delta'],
+    '#name' => 'ief-delete-cancel-' . $delete_form['#delta'],
     '#limit_validation_errors' => array(),
     '#submit' => array(),
     '#ajax' => array(
       'callback' => 'inline_entity_form_widget_refresh',
-      'wrapper' => $form['#ief_wrapper'],
+      'wrapper' => 'inline-entity-form-' . $delete_form['#ief_parents_key'],
     ),
   );
 
-  return $form;
+  return $delete_form;
 }
 
 /**
- * Calls #element_submit callbacks.
+ * Processes an entity form submission.
  *
- * Checks if the previous #element_validate callbacks had set any errors,
- * and if not, proceeds to invoke the #element_submit callbacks.
- * #element_submit callbacks modify the entity stored in $entity_form['#entity']
- * and after they have been executed the entity is considered ready for saving.
+ * The entity form is first validated by its controller.
+ * If no errors were set, the controller's submit method is then called,
+ * which modifies the entity stored in $entity_form['#entity'] and prepares
+ * it for saving.
  *
  * @param $entity_form
  *  The form of the entity being managed inline.
  * @param $form_state
- *   The form state of the complete IEF widget.
+ *   The form state of the parent form.
  */
-function inline_entity_form_process_submit(&$entity_form, &$form_state) {
-  // Abort submission if the form has been rebuilt by an unknown element, such
+function inline_entity_form_process_entity_form(&$entity_form, &$form_state) {
+  // Abort if the form has been rebuilt by an unknown element, such
   // as a field widget.
   $triggering_element_name = end($form_state['triggering_element']['#array_parents']);
   if (!in_array($triggering_element_name, array('ief_add_save', 'ief_edit_save'))) {
     return;
   }
 
-  if (!form_get_errors()) {
-    foreach ($entity_form['#element_submit'] as $function) {
-      $function($entity_form, $form_state);
-    }
-  }
-}
+  $entity_type = $entity_form['#entity_type'];
+  $entity_info = entity_get_info($entity_type);
+  $parents_key = $entity_form['#ief_parents_key'];
+  $type_settings = $form_state['inline_entity_form'][$parents_key]['type_settings'];
 
-/**
- * Cleans up the form state for each field.
- *
- * After field_attach_submit() has run and the entity has been saved, the form
- * state still contains field data in $form_state['field']. Unless that
- * data is removed, the next form with the same #parents (reopened add form,
- * for example) will contain data (i.e. uploaded files) from the previous form.
- *
- * @param $entity_form
- *   The form of the entity being managed inline.
- * @param $form_state
- *   The form state of the complete IEF widget.
- */
-function inline_entity_form_cleanup_field_form_state($entity_form, &$form_state) {
-  list(, , $bundle) = entity_extract_ids($entity_form['#entity_type'], $entity_form['#entity']);
-  $instances = field_info_instances($entity_form['#entity_type'], $bundle);
-  foreach ($instances as $instance) {
-    $field_name = $instance['field_name'];
-    $parents = $entity_form[$field_name]['#parents'];
-    array_pop($parents);
-    $langcode = $entity_form[$field_name]['#language'];
-
-    $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
-    unset($field_state['items']);
-    $field_state['items_count'] = 0;
-    field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
+  // Instantiate the controller and validate the form.
+  $controller = new $entity_info['inline entity form']['controller']($entity_type, $type_settings);
+  $controller->entityFormValidate($entity_form, $form_state);
+  // If validation passed, execute the submission handler.
+  if (!form_get_errors()) {
+    $controller->entityFormSubmit($entity_form, $form_state);
   }
 }
 
@@ -806,10 +692,10 @@ function inline_entity_form_element_validate($element, &$form_state, $form) {
 /**
  * Implements hook_field_attach_submit().
  */
-function inline_entity_form_field_attach_submit($entity_type, $entity, $form, &$form_state) {
-  list(,,$bundle_name) = entity_extract_ids($entity_type, $entity);
+function inline_entity_form_field_attach_submit($parent_entity_type, $parent_entity, $form, &$form_state) {
+  list(, , $bundle_name) = entity_extract_ids($parent_entity_type, $parent_entity);
 
-  foreach (field_info_instances($entity_type, $bundle_name) as $instance_name => $instance) {
+  foreach (field_info_instances($parent_entity_type, $bundle_name) as $instance_name => $instance) {
     if (isset($instance['widget']) && $instance['widget']['type'] == 'inline_entity_form') {
       $field_name = $instance['field_name'];
       $langcode = $form[$field_name]['#language'];
@@ -821,24 +707,31 @@ function inline_entity_form_field_attach_submit($entity_type, $entity, $form, &$
       }
 
       $values = $form_state['inline_entity_form'][$parents_key];
-      $settings = $values['settings'];
+      $entity_type = $values['settings']['entity_type'];
+      $entity_info = entity_get_info($entity_type);
+      $controller = new $entity_info['inline entity form']['controller']($entity_type, $values['type_settings']);
+      $context = array(
+        'parent_entity_type' => $parent_entity_type,
+        'parent_entity' => $parent_entity,
+      );
+
       // Delete any entities staged for deletion.
       if (!empty($values['delete'])) {
-        entity_delete_multiple($settings['entity_type'], array_values($values['delete']));
+        $controller->delete(array_values($values['delete']), $context);
       }
 
       // Respect the entity weights.
       uasort($values['entities'], 'drupal_sort_weight');
-      // Go through the ief data and assemble a list of ids.
+      // Go through the IEF data and assemble a list of ids.
       $entity_ids = array();
       foreach ($values['entities'] as $item) {
         if ($item['needs_save']) {
-          entity_save($settings['entity_type'], $item['data']);
+          $controller->save($item['data'], $context);
         }
-        $entity_ids[] = array($settings['column'] => entity_id($settings['entity_type'], $item['data']));
+        $entity_ids[] = array($values['settings']['column'] => entity_id($entity_type, $item['data']));
       }
       // Set the list of ids as the field value.
-      $entity->{$field_name}[$langcode] = $entity_ids;
+      $parent_entity->{$field_name}[$langcode] = $entity_ids;
     }
   }
 }
