diff --git a/lib/Drupal/profile2/Profile.php b/lib/Drupal/profile2/Profile.php
new file mode 100644
index 0000000..28c1652
--- /dev/null
+++ b/lib/Drupal/profile2/Profile.php
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\profile2\Profile.
+ */
+
+namespace Drupal\profile2;
+
+use Drupal\entity\Entity;
+
+/**
+ * The class used for profile entities.
+ */
+class Profile extends Entity {
+
+  /**
+   * The profile id.
+   *
+   * @var integer
+   */
+  public $pid;
+
+  /**
+   * The name of the profile type.
+   *
+   * @var string
+   */
+  public $type;
+
+  /**
+   * The profile label.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The user id of the profile owner.
+   *
+   * @var integer
+   */
+  public $uid;
+
+  /**
+   * The Unix timestamp when the profile was created.
+   *
+   * @var integer
+   */
+  public $created;
+
+  /**
+   * The Unix timestamp when the profile was most recently saved.
+   *
+   * @var integer
+   */
+  public $changed;
+
+
+  public function __construct(array $values = array(), $entity_type = 'profile2') {
+    if (isset($values['user'])) {
+      $this->setUser($values['user']);
+      unset($values['user']);
+    }
+    if (isset($values['type']) && is_object($values['type'])) {
+      $values['type'] = $values['type']->type;
+    }
+    if (!isset($values['label']) && isset($values['type']) && $type = profile2_get_types($values['type'])) {
+      // Initialize the label with the type label, so newly created profiles
+      // have that as interim label.
+      $values['label'] = $type->label;
+    }
+    parent::__construct($values, 'profile2');
+  }
+
+  /**
+   * Returns the user owning this profile.
+   */
+  public function user() {
+    return user_load($this->uid);
+  }
+
+  /**
+   * Sets a new user owning this profile.
+   *
+   * @param $account
+   *   The user account object or the user account id (uid).
+   */
+  public function setUser($account) {
+    $this->uid = is_object($account) ? $account->uid : $account;
+  }
+
+  /**
+   * Gets the associated profile type object.
+   *
+   * @return ProfileType
+   */
+  public function type() {
+    return profile2_get_types($this->type);
+  }
+
+  /**
+   * Returns the full url() for the profile.
+   */
+  public function url() {
+    $uri = $this->uri();
+    return url($uri['path'], $uri);
+  }
+
+  /**
+   * Returns the drupal path to this profile.
+   */
+  public function path() {
+    $uri = $this->uri();
+    return $uri['path'];
+  }
+
+  public function defaultUri() {
+    return array(
+      'path' => 'user/' . $this->uid,
+      'options' => array('fragment' => 'profile-' . $this->type),
+    );
+  }
+
+  public function defaultLabel() {
+    if (module_exists('profile2_i18n')) {
+      // Run the label through i18n_string() using the profile2_type label
+      // context, so the default label (= the type's label) gets translated.
+      return entity_i18n_string('profile2:profile2_type:' . $this->type . ':label', $this->label);
+    }
+    return $this->label;
+  }
+
+  public function buildContent($view_mode = 'full', $langcode = NULL) {
+    $content = array();
+    // Assume newly create objects are still empty.
+    if (!empty($this->is_new)) {
+      $content['empty']['#markup'] = '<em class="profile2-no-data">' . t('There is no profile data yet.') . '</em>';
+    }
+    return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode, $content);
+  }
+
+  public function save() {
+    // Care about setting created and changed values. But do not automatically
+    // set a created values for already existing profiles.
+    if (empty($this->created) && (!empty($this->is_new) || !$this->pid)) {
+      $this->created = REQUEST_TIME;
+    }
+    $this->changed = REQUEST_TIME;
+
+    parent::save();
+    // Update the static cache from profile2_load_by_user().
+    $cache = &drupal_static('profile2_load_by_user', array());
+    if (isset($cache[$this->uid])) {
+      $cache[$this->uid][$this->type] = $this->pid;
+    }
+  }
+}
diff --git a/lib/Drupal/profile2/ProfileType.php b/lib/Drupal/profile2/ProfileType.php
new file mode 100644
index 0000000..a11b761
--- /dev/null
+++ b/lib/Drupal/profile2/ProfileType.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\profile2\ProfileType.
+ */
+
+namespace Drupal\profile2;
+
+use Drupal\entity\Entity;
+
+/**
+ * Use a separate class for profile types so we can specify some defaults
+ * modules may alter.
+ */
+class ProfileType extends Entity {
+
+  /**
+   * Whether the profile type appears in the user categories.
+   */
+  public $userCategory = TRUE;
+
+  /**
+   * Whether the profile is displayed on the user account page.
+   */
+  public $userView = TRUE;
+
+  public $type;
+  public $label;
+  public $weight = 0;
+
+  public function __construct(array $values = array(), $entity_type = 'profile2_type') {
+    parent::__construct($values, $entity_type);
+  }
+
+  /**
+   * Returns whether the profile type is locked, thus may not be deleted or renamed.
+   *
+   * Profile types provided in code are automatically treated as locked, as well
+   * as any fixed profile type.
+   *
+   * @todo Remove method or make entity status available.
+   */
+  public function isLocked() {
+    // At the moment there is not status available, so profile type can't be locked.
+    return false;
+  }
+
+  /**
+   * Overridden, to introduce the method for old entity API versions (BC).
+   *
+   * @todo Remove once we bump the required entity API version.
+   */
+  public function getTranslation($property, $langcode = NULL) {
+    if (module_exists('profile2_i18n')) {
+      return parent::getTranslation($property, $langcode);
+    }
+    return $this->$property;
+  }
+}
diff --git a/profile2.admin.inc b/profile2.admin.inc
index f31ce59..5967a3a 100644
--- a/profile2.admin.inc
+++ b/profile2.admin.inc
@@ -8,7 +8,11 @@
 /**
  * Generates the profile type editing form.
  */
-function profile2_type_form($form, &$form_state, $profile_type, $op = 'edit') {
+function profile2_type_form($form, &$form_state, $profile_type = NULL, $op = 'edit') {
+  if (!isset($profile_type)) {
+    $profile_type = entity_create('profile2_type', array());
+  }
+  $form_state['profile_type'] = $profile_type;
 
   if ($op == 'clone') {
     $profile_type->label .= ' (cloned)';
@@ -18,7 +22,7 @@ function profile2_type_form($form, &$form_state, $profile_type, $op = 'edit') {
   $form['label'] = array(
     '#title' => t('Label'),
     '#type' => 'textfield',
-    '#default_value' => $profile_type->label,
+    '#default_value' => $profile_type->label(),
     '#description' => t('The human-readable name of this profile type.'),
     '#required' => TRUE,
     '#size' => 30,
@@ -26,7 +30,7 @@ function profile2_type_form($form, &$form_state, $profile_type, $op = 'edit') {
   // Machine-readable type name.
   $form['type'] = array(
     '#type' => 'machine_name',
-    '#default_value' => isset($profile_type->type) ? $profile_type->type : '',
+    '#default_value' => $profile_type->get('type'),
     '#maxlength' => 32,
     '#disabled' => $profile_type->isLocked() && $op != 'clone',
     '#machine_name' => array(
@@ -36,11 +40,12 @@ function profile2_type_form($form, &$form_state, $profile_type, $op = 'edit') {
     '#description' => t('A unique machine-readable name for this profile type. It must only contain lowercase letters, numbers, and underscores.'),
   );
 
+  $data = unserialize($profile_type->get('data'));
   $form['data']['#tree'] = TRUE;
   $form['data']['registration'] = array(
     '#type' => 'checkbox',
     '#title' => t('Show during user account registration.'),
-    '#default_value' => !empty($profile_type->data['registration']),
+    '#default_value' => $data['registration'],
   );
 
   $form['actions'] = array('#type' => 'actions');
@@ -66,9 +71,24 @@ function profile2_type_form($form, &$form_state, $profile_type, $op = 'edit') {
  * Form API submit callback for the type form.
  */
 function profile2_type_form_submit(&$form, &$form_state) {
-  $profile_type = entity_ui_form_submit_build_entity($form, $form_state);
-  // Save and go back.
+  form_state_values_clean($form_state);
+
+  $profile_type = $form_state['profile_type'];
+
+  foreach ($form_state['values'] as $key => $value) {
+    $a .= $key.' '.$value;
+    $profile_type->set($key, $value);
+  }
+
   $profile_type->save();
+
+  if (!empty($profile_type->original)) {
+    drupal_set_message(format_string('%label configuration has been updated.', array('%label' => $profile_type->label())));
+  }
+  else {
+    drupal_set_message(format_string('%label configuration has been created.', array('%label' => $profile_type->label())));
+  }
+
   $form_state['redirect'] = 'admin/structure/profiles';
 }
 
@@ -76,5 +96,31 @@ function profile2_type_form_submit(&$form, &$form_state) {
  * Form API submit callback for the delete button.
  */
 function profile2_type_form_submit_delete(&$form, &$form_state) {
-  $form_state['redirect'] = 'admin/structure/profiles/manage/' . $form_state['profile2_type']->type . '/delete';
+  $form_state['redirect'] = 'admin/structure/profiles/manage/' . $form_state['profile_type']->type . '/delete';
+}
+
+/**
+ * Form constructor to delete a ProfileType object.
+ *
+ * @param Drupal\profile2\ProfileType $profile_type
+ *   The ProfileType object to delete.
+ */
+function profile2_type_delete_form($form, &$form_state, ProfileType $profile_type) {
+  $form_state['profile_type'] = $profile_type;
+
+  $form['id'] = array('#type' => 'value', '#value' => $profile_type->id());
+  return confirm_form($form,
+    format_string('Are you sure you want to delete %label', array('%label' => $profile_type->label())),
+    'admin/structure/profiles',
+    NULL,
+    'Delete'
+  );
+}
+
+/**
+ * Form submission handler for profile_type_delete_form().
+ */
+function profile2_type_delete_form_submit($form, &$form_state) {
+  $form_state['profile_type']->delete();
+  $form_state['redirect'] = 'admin/structure/profiles';
 }
diff --git a/profile2.module b/profile2.module
index 3ab04bc..0e5b695 100644
--- a/profile2.module
+++ b/profile2.module
@@ -12,53 +12,30 @@ function profile2_entity_info() {
   $return = array(
     'profile2' => array(
       'label' => t('Profile'),
-      'plural label' => t('Profiles'),
-      'description' => t('Profile2 user profiles.'),
-      'entity class' => 'Profile',
-      'controller class' => 'EntityAPIController',
+      'entity class' => 'Drupal\profile2\Profile',
+      'controller class' => 'Drupal\node\NodeStorageController',
       'base table' => 'profile',
+      'uri callback' => 'profile2_profile_uri',
       'fieldable' => TRUE,
-      'view modes' => array(
-        'account' => array(
-          'label' => t('User account'),
-          'custom settings' => FALSE,
-        ),
-      ),
       'entity keys' => array(
         'id' => 'pid',
         'bundle' => 'type',
         'label' => 'label',
       ),
-      'bundles' => array(),
       'bundle keys' => array(
         'bundle' => 'type',
       ),
-      'label callback' => 'entity_class_label',
-      'uri callback' => 'entity_class_uri',
+      'bundles' => array(),
+      'view modes' => array(
+        'account' => array(
+          'label' => t('User account'),
+          'custom settings' => FALSE,
+        ),
+      ),
       'access callback' => 'profile2_access',
-      'module' => 'profile2',
-      'metadata controller class' => 'Drupal\profile2\MetadataController',
     ),
   );
 
-  // Add bundle info but bypass entity_load() as we cannot use it here.
-  $types = db_select('profile_type', 'p')
-    ->fields('p')
-    ->execute()
-    ->fetchAllAssoc('type');
-
-  foreach ($types as $type => $info) {
-    $return['profile2']['bundles'][$type] = array(
-      'label' => $info->label,
-      'admin' => array(
-        'path' => 'admin/structure/profiles/manage/%profile2_type',
-        'real path' => 'admin/structure/profiles/manage/' . $type,
-        'bundle argument' => 4,
-        'access arguments' => array('administer profiles'),
-      ),
-    );
-  }
-
   // Support entity cache module.
   if (module_exists('entitycache')) {
     $return['profile2']['field cache'] = FALSE;
@@ -67,14 +44,13 @@ function profile2_entity_info() {
 
   $return['profile2_type'] = array(
     'label' => t('Profile type'),
-    'plural label' => t('Profile types'),
-    'description' => t('Profiles types of Profile2 user profiles.'),
-    'entity class' => 'ProfileType',
-    'controller class' => 'EntityAPIControllerExportable',
+    'entity class' => 'Drupal\profile2\ProfileType',
+    'controller class' => 'Drupal\entity\DatabaseStorageController',
     'base table' => 'profile_type',
     'fieldable' => FALSE,
     'bundle of' => 'profile2',
     'exportable' => TRUE,
+    'uri callback' => 'profile2_profile_type_uri',
     'entity keys' => array(
       'id' => 'id',
       'name' => 'type',
@@ -82,17 +58,148 @@ function profile2_entity_info() {
     ),
     'access callback' => 'profile2_type_access',
     'module' => 'profile2',
-    // Enable the entity API's admin UI.
-    'admin ui' => array(
-      'path' => 'admin/structure/profiles',
-      'controller class' => 'Drupal\profile2\TypeUIController',
-    ),
   );
 
   return $return;
 }
 
 /**
+ * Entity uri callback.
+ *
+ * @param Drupal\profile2\Profile $profile
+ *   A Profile entity.
+ */
+function profile2_profile_uri(Profile $profile) {
+  return array(
+    'path' => 'admin/structure/profiles',
+  );
+}
+
+/**
+ * Entity uri callback.
+ *
+ * @param Drupal\profile2\ProfileType $profile_type
+ *   A ProfileType entity.
+ */
+function profile2_profile_type_uri(ProfileType $profile) {
+  return array(
+    'path' => 'admin/structure/profiles/manage',
+  );
+}
+
+/**
+ * Implements hook_menu.
+ */
+function profile2_menu() {
+  $items = array();
+  $items['admin/structure/profiles'] = array(
+    'title' => 'Profile types',
+    'page callback' => 'profile2_type_list_page',
+    'access callback' => TRUE,
+  );
+  $items['admin/structure/profiles/list'] = array(
+    'title' => 'Profile types',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/structure/profiles/add'] = array(
+    'title' => 'Add profile type',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('profile2_type_form'),
+    'access callback' => TRUE,
+    'type' => MENU_LOCAL_ACTION,
+  );
+  $items['admin/structure/profiles/manage/%profile2_type'] = array(
+    'title' => 'Edit profile type',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('profile2_type_form', 4),
+    'access callback' => TRUE,
+    'file' => 'profile2.admin.inc',
+  );
+  $items['admin/structure/profiles/manage/%profile2_type/edit'] = array(
+    'title' => 'Edit',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/structure/profiles/manage/%profile2_type/delete'] = array(
+    'title' => 'Delete profile type',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('profile2_type_delete_form', 4),
+    'access callback' => TRUE,
+    'file' => 'profile2.admin.inc',
+    'type' => MENU_LOCAL_ACTION
+  );
+  $items['admin/structure/profiles/manage/%profile2_type/clone'] = array(
+    'title' => 'Clone profile type',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('profile2_type_form', 4, 'clone'),
+    'access callback' => TRUE,
+    'file' => 'profile2.admin.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Page callback; Lists available Profile objects.
+ */
+function profile2_type_list_page() {
+  $rows = array();
+  foreach (entity_load_multiple('profile2_type', FALSE) as $profile) {
+    $uri = $profile->uri();
+    $row = array();
+    $row[] = $profile->label();
+    $row[] = '-';
+    $row[]['data'] = array(
+      '#type' => 'link',
+      '#title' => t('edit'),
+      '#href' => $uri['path'] . '/' . $profile->type,
+      '#options' => $uri['options'],
+    );
+    $row[]['data'] = array(
+      '#type' => 'link',
+      '#title' => t('manage fields'),
+      '#href' => $uri['path'] . '/' . $profile->type . '/fields',
+      '#options' => $uri['options'],
+    );
+    $row[]['data'] = array(
+      '#type' => 'link',
+      '#title' => t('manage display'),
+      '#href' => $uri['path'] . '/' . $profile->type . '/display',
+      '#options' => $uri['options'],
+    );
+    $row[]['data'] = array(
+      '#type' => 'link',
+      '#title' => t('clone'),
+      '#href' => $uri['path'] . '/' . $profile->type . '/clone',
+      '#options' => $uri['options'],
+    );
+    $row[]['data'] = array(
+      '#type' => 'link',
+      '#title' => t('delete'),
+      '#href' => $uri['path'] . '/' . $profile->type . '/delete',
+      '#options' => array_merge($uri['options'], array('query' => drupal_get_destination())),
+    );
+    $row[]['data'] = array(
+      '#type' => 'link',
+      '#title' => t('export'),
+      '#href' => $uri['path'] . '/' . $profile->type . '/export',
+      '#options' => $uri['options'],
+    );
+    $rows[] = $row;
+  }
+  $build = array(
+    '#theme' => 'table',
+    '#header' => array('Name', 'Status', array('data' => 'Operations', 'colspan' => 6)),
+    '#rows' => $rows,
+    '#empty' => format_string('No profile type defined. <a href="@add-url">Add some</a>', array(
+      '@add-url' => url('admin/structure/profiles/add'),
+    )),
+  );
+  return $build;
+}
+
+/**
  * Menu argument loader; Load a profile type by string.
  *
  * @param $type
@@ -148,7 +255,12 @@ function profile2_permission() {
  *   Depending whether $type isset, an array of profile types or a single one.
  */
 function profile2_get_types($type_name = NULL) {
-  $types = entity_load_multiple_by_name('profile2_type', isset($type_name) ? array($type_name) : FALSE);
+  $types = entity_load_multiple('profile2_type');
+  foreach ($types as $key => $type) {
+    if ($type->get('type') != $type_name) {
+      unset($types[$key]);
+    }
+  }
   return isset($type_name) ? reset($types) : $types;
 }
 
@@ -307,19 +419,6 @@ function profile2_type_delete(ProfileType $type) {
 }
 
 /**
-* Implements hook_profile2_type_delete()
-*/
-function profile2_profile2_type_delete(ProfileType $type) {
-  // Delete all profiles of this type but only if this is not a revert.
-  if (!$type->hasStatus(ENTITY_IN_CODE)) {
-    $pids = array_keys(profile2_load_multiple(FALSE, array('type' => $type->type)));
-    if ($pids) {
-      profile2_delete_multiple($pids);
-    }
-  }
-}
-
-/**
  * Implements hook_user_view().
  */
 function profile2_user_view($account, $view_mode, $langcode) {
@@ -587,201 +686,6 @@ function profile2_theme() {
 }
 
 /**
- * The class used for profile entities.
- */
-class Profile extends Entity {
-
-  /**
-   * The profile id.
-   *
-   * @var integer
-   */
-  public $pid;
-
-  /**
-   * The name of the profile type.
-   *
-   * @var string
-   */
-  public $type;
-
-  /**
-   * The profile label.
-   *
-   * @var string
-   */
-  public $label;
-
-  /**
-   * The user id of the profile owner.
-   *
-   * @var integer
-   */
-  public $uid;
-
-  /**
-   * The Unix timestamp when the profile was created.
-   *
-   * @var integer
-   */
-  public $created;
-
-  /**
-   * The Unix timestamp when the profile was most recently saved.
-   *
-   * @var integer
-   */
-  public $changed;
-
-
-  public function __construct($values = array()) {
-    if (isset($values['user'])) {
-      $this->setUser($values['user']);
-      unset($values['user']);
-    }
-    if (isset($values['type']) && is_object($values['type'])) {
-      $values['type'] = $values['type']->type;
-    }
-    if (!isset($values['label']) && isset($values['type']) && $type = profile2_get_types($values['type'])) {
-      // Initialize the label with the type label, so newly created profiles
-      // have that as interim label.
-      $values['label'] = $type->label;
-    }
-    parent::__construct($values, 'profile2');
-  }
-
-  /**
-   * Returns the user owning this profile.
-   */
-  public function user() {
-    return user_load($this->uid);
-  }
-
-  /**
-   * Sets a new user owning this profile.
-   *
-   * @param $account
-   *   The user account object or the user account id (uid).
-   */
-  public function setUser($account) {
-    $this->uid = is_object($account) ? $account->uid : $account;
-  }
-
-  /**
-   * Gets the associated profile type object.
-   *
-   * @return ProfileType
-   */
-  public function type() {
-    return profile2_get_types($this->type);
-  }
-
-  /**
-   * Returns the full url() for the profile.
-   */
-  public function url() {
-    $uri = $this->uri();
-    return url($uri['path'], $uri);
-  }
-
-  /**
-   * Returns the drupal path to this profile.
-   */
-  public function path() {
-    $uri = $this->uri();
-    return $uri['path'];
-  }
-
-  public function defaultUri() {
-    return array(
-      'path' => 'user/' . $this->uid,
-      'options' => array('fragment' => 'profile-' . $this->type),
-    );
-  }
-
-  public function defaultLabel() {
-    if (module_exists('profile2_i18n')) {
-      // Run the label through i18n_string() using the profile2_type label
-      // context, so the default label (= the type's label) gets translated.
-      return entity_i18n_string('profile2:profile2_type:' . $this->type . ':label', $this->label);
-    }
-    return $this->label;
-  }
-
-  public function buildContent($view_mode = 'full', $langcode = NULL) {
-    $content = array();
-    // Assume newly create objects are still empty.
-    if (!empty($this->is_new)) {
-      $content['empty']['#markup'] = '<em class="profile2-no-data">' . t('There is no profile data yet.') . '</em>';
-    }
-    return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode, $content);
-  }
-
-  public function save() {
-    // Care about setting created and changed values. But do not automatically
-    // set a created values for already existing profiles.
-    if (empty($this->created) && (!empty($this->is_new) || !$this->pid)) {
-      $this->created = REQUEST_TIME;
-    }
-    $this->changed = REQUEST_TIME;
-
-    parent::save();
-    // Update the static cache from profile2_load_by_user().
-    $cache = &drupal_static('profile2_load_by_user', array());
-    if (isset($cache[$this->uid])) {
-      $cache[$this->uid][$this->type] = $this->pid;
-    }
-  }
-}
-
-/**
- * Use a separate class for profile types so we can specify some defaults
- * modules may alter.
- */
-class ProfileType extends Entity {
-
-  /**
-   * Whether the profile type appears in the user categories.
-   */
-  public $userCategory = TRUE;
-
-  /**
-   * Whether the profile is displayed on the user account page.
-   */
-  public $userView = TRUE;
-
-  public $type;
-  public $label;
-  public $weight = 0;
-
-  public function __construct($values = array()) {
-    parent::__construct($values, 'profile2_type');
-  }
-
-  /**
-   * Returns whether the profile type is locked, thus may not be deleted or renamed.
-   *
-   * Profile types provided in code are automatically treated as locked, as well
-   * as any fixed profile type.
-   */
-  public function isLocked() {
-    return isset($this->status) && empty($this->is_new) && (($this->status & ENTITY_IN_CODE) || ($this->status & ENTITY_FIXED));
-  }
-
-  /**
-   * Overridden, to introduce the method for old entity API versions (BC).
-   *
-   * @todo Remove once we bump the required entity API version.
-   */
-  public function getTranslation($property, $langcode = NULL) {
-    if (module_exists('profile2_i18n')) {
-      return parent::getTranslation($property, $langcode);
-    }
-    return $this->$property;
-  }
-}
-
-/**
  * View a profile.
  *
  * @see Profile::view()
