diff --git a/userpoints.admin.inc b/userpoints.admin.inc
index 38fd598..ab4c061 100644
--- a/userpoints.admin.inc
+++ b/userpoints.admin.inc
@@ -16,7 +16,49 @@ function userpoints_confirm_approve_submit($form, &$form_state) {
 }
 
 /**
+ * A simple list of links similar to node/add for creating
+ * userpoints transactions.
+ */
+function userpoints_admin_add() {
+
+  $types = userpoints_transaction_get_types();
+
+  if (empty($types)) {
+    // Warn user that module is not configured.
+    drupal_set_message(t('There are no userpoint transaction types.'), 'error');
+    return '';
+  }
+  else if (count($types) == 1) {
+    // Redirect to userpoints form directly.
+    $type = array_shift($types);
+    drupal_goto('admin/config/people/userpoints/add/' . $type->name);
+  }
+
+  $return = array(
+    '#theme' => 'links',
+    '#links' => array(),
+  );
+
+  foreach ($types as $name => $type) {
+    $return['#links'][$name] = array(
+      'title' => check_plain($type->label),
+      'href' => 'admin/config/people/userpoints/add/' . $name,
+      'html' => FALSE,
+    );
+  }
+
+  return $return;
+}
+
+/**
  * Form builder for add/edit userpoints transaction form.
+ *
+ * @param $form
+ * @param &$form_state
+ * @param $mode
+ *   The operation performed: edit or add.
+ * @param $txn
+ *   Either the transaction entity or a transaction type entity.
  */
 function userpoints_admin_txn($form, &$form_state, $mode, $txn = NULL) {
 
@@ -31,10 +73,15 @@ function userpoints_admin_txn($form, &$form_state, $mode, $txn = NULL) {
   }
   elseif ($mode == 'add') {
     drupal_set_title(t('Add !points', userpoints_translation()));
-    if ($txn) {
+    if (isset($txn->type)) {
+      // Transaction entity
       $txn_user = user_load($txn);
     }
-    $txn = new UserpointsTransaction();
+    elseif (isset($txn->name)) {
+      // Transaction type entity
+      $transaction = new UserpointsTransaction(array('type' => $txn->name));
+      $txn = $transaction;
+    }
   }
   $form_state['userpoints_transaction'] = $txn;
 
@@ -381,7 +428,7 @@ function userpoints_admin_txn_submit($form, &$form_state) {
   }
 
   // Attach field information directly to the userpoints transaction object.
-  foreach (field_info_instances('userpoints_transaction', 'userpoints_transaction') as $instance) {
+  foreach (field_info_instances('userpoints_transaction', $transaction->type) as $instance) {
     $field_name = $instance['field_name'];
     $transaction->$field_name = $form_state['values'][$field_name];
   }
@@ -835,3 +882,94 @@ function userpoints_admin_settings($form, &$form_state) {
 
   return system_settings_form($form);
 }
+
+/**
+ * Userpoints transaction type form.
+ *
+ * @param $form
+ *   The form array.
+ * @param &$form_state
+ *   The reference to the form state array.
+ * @param $type
+ *   The userpoints transaction type entity if it exists.
+ * @param $op
+ *   The operation to perform: add, edit, or clone.
+ *
+ * @return Array
+ *   An associative array based on the form API.
+ */
+function userpoints_transaction_type_form($form, &$form_state, $type, $op = 'edit') {
+
+  if ($op == 'clone') {
+    // Prepare a clone of the transaction type entity.
+    unset($type->id);
+    unset($type->module);
+    unset($type->status);
+    $type->name .= '_clone';
+    $type->label .= ' (Clone)';
+  }
+
+  $form['label'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Label'),
+    '#description' => t('Provide a label for the transaction type.'),
+    '#max_length' => 255,
+    '#required' => TRUE,
+    '#default_value' => isset($type) ? check_plain($type->label) : '',
+  );
+
+  $form['name'] = array(
+    '#type' => 'machine_name',
+    '#machine_name' => array(
+      'exists' => 'userpoints_transaction_type_exists',
+      'source' => array('label'),
+    ),
+    '#max_length' => 100,
+    '#required' => TRUE,
+    '#default_value' => isset($type) ? check_plain($type->name) : '',
+  );
+
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+    '#weight' => 42,
+  );
+
+  if (isset($type)) {
+    $form_state['userpoints_transaction_type'] = $type;
+
+    $form['actions']['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete'),
+      '#submit' => array('userpoints_transaction_type_form_delete'),
+      '#weight' => 43,
+    );
+  }
+
+  return $form;
+}
+
+/**
+ * Userpoints transaction type form delete.
+ */
+function userpoints_transaction_type_form_delete($form, &$form_state) {
+  $form_state['redirect'] = array('admin/config/people/userpoints/types/manage/' . $form_state['type']->name . '/delete');
+}
+
+/**
+ * Userpoints transaction type form submit.
+ */
+function userpoints_transaction_type_form_submit($form, &$form_state) {
+  $type = entity_ui_form_submit_build_entity($form, $form_state);
+
+  try {
+    $type->save();
+  }
+  catch (Exception $e) {
+    watchdog_exception('userpoints', $e);
+  }
+
+  drupal_set_message(t('Successfully saved transaction type %label', array('%label' => $type->label)));
+  $form_state['redirect'] = array('admin/config/people/userpoints/types/manage');
+}
diff --git a/userpoints.install b/userpoints.install
index 990c381..9cce133 100644
--- a/userpoints.install
+++ b/userpoints.install
@@ -101,6 +101,12 @@ function userpoints_schema() {
         'type' => 'serial',
         'not null' => TRUE,
       ),
+      'type' => array(
+        'description' => 'Bundle',
+        'type' => 'varchar',
+        'length' => 100,
+        'not null' => TRUE,
+      ),
       'uid' => array(
         'description' => 'User ID',
         'type' => 'int',
@@ -202,10 +208,72 @@ function userpoints_schema() {
       'points' => array('points'),
     )
   );
+
+  $schema['userpoints_txn_type'] = array(
+    'description' => 'Userpoints transaction type',
+    'fields' => array(
+      'id' => array(
+        'description' => 'Transaction type id',
+        'type' => 'serial',
+        'not null' => TRUE,
+      ),
+      'name' => array(
+        'description' => 'Machine name',
+        'type' => 'varchar',
+        'length' => 100,
+        'not null' => TRUE,
+      ),
+      'label' => array(
+        'description' => 'Human-readable name',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+      ),
+      'status' => array(
+        'description' => 'Type status used by Entity API',
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0x01,
+      ),
+      'module' => array(
+        'description' => 'Module owner of type used by Entity API',
+        'type' => 'varchar',
+        'length' => 100,
+        'not null' => FALSE,
+      ),
+    ),
+    'primary key' => array('id'),
+    'unique keys' => array(
+      'name' => array('name'),
+    ),
+  );
+
   return $schema;
 }
 
 /**
+ * Implements hook_install().
+ */
+function userpoints_install() {
+  // Create the default userpoints bundle that is defined in 
+  // userpoints_entity_info().
+  $bundle_info = (object) array(
+    'name' => 'userpoints',
+    'label' => st('Default'),
+    'status' => 0x01,
+    'module' => 'userpoints',
+  );
+
+  try {
+    drupal_write_record('userpoints_txn_type', $bundle_info);
+  }
+  catch (Exception $e) {
+    watchdog_exception('userpoints', $e, 'Failed to create default bundle.', array(), WATCHDOG_CRITICAL);
+  }
+}
+
+/**
  * Implements hook_uninstall().
  */
 function userpoints_uninstall() {
@@ -331,4 +399,4 @@ function userpoints_update_7004(&$sandbox) {
   $sandbox['current_uid'] = $last_uid;
   // Set #finished based on sandbox.
   $sandbox['#finished'] = (empty($sandbox['max']) || $last_uid == 0) ? 1 : ($sandbox['current_uid'] / $sandbox['max']);
-}
\ No newline at end of file
+}
diff --git a/userpoints.module b/userpoints.module
index b37072c..ea83790 100644
--- a/userpoints.module
+++ b/userpoints.module
@@ -158,7 +158,6 @@ function userpoints_menu() {
       'type' => MENU_LOCAL_TASK,
       'weight' => 0,
   );
-
   $items['admin/config/people/userpoints/moderate'] = array(
       'title' => 'Moderation',
       'title arguments' => userpoints_translation(),
@@ -175,14 +174,24 @@ function userpoints_menu() {
       'title' => 'Add !points transaction',
       'title arguments' => userpoints_translation(),
       'description' => 'Admin add/delete userpoints',
-      'page callback' => 'drupal_get_form',
-      'page arguments' => array('userpoints_admin_txn', 4),
+      'page callback' => 'userpoints_admin_add',
       'access callback' => 'userpoints_admin_access',
       'access arguments' => array('add'),
       'file' => 'userpoints.admin.inc',
       'type' => MENU_LOCAL_ACTION,
       'weight' => 0,
   );
+  $items['admin/config/people/userpoints/add/%userpoints_transaction_type'] = array(
+      'title' => 'Add !points transaction',
+      'title arguments' => userpoints_translation(),
+      'description' => 'Admin add/delete userpoints',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('userpoints_admin_txn', 4, 5),
+      'access callback' => 'userpoints_admin_access',
+      'access arguments' => array('add'),
+      'file' => 'userpoints.admin.inc',
+      'type' => MENU_CALLBACK,
+  );
 
   $items['admin/config/people/userpoints/settings'] = array(
       'title' => '!Points settings',
@@ -322,7 +331,7 @@ function userpoints_menu_local_tasks_alter(&$data, $router_item, $root_path) {
 
     // Don't display the action link on some pages like settings and
     // approve or decline confirmation forms.
-    $blacklist = array('settings', 'approve', 'decline', 'add', 'edit');
+    $blacklist = array('settings', 'approve', 'decline', 'add', 'edit', 'types');
     foreach ($blacklist as $blacklisted_path) {
       if (strpos($root_path, $blacklisted_path) !== FALSE) {
         return;
@@ -657,20 +666,20 @@ function userpoints_get_current_points($uid = NULL, $tid = NULL) {
 function userpoints_get_max_points($uid = NULL, $tid = NULL) {
   $max = drupal_static(__FUNCTION__, array());
 
-  // Check if uid is passed as a parameter.
+  // Checks if uid is passed as a parameter.
   if (!$uid) {
     // It is not, so we use the currently logged in user's uid.
     global $user;
     $uid = $user->uid;
   }
 
-  // Check if a term id is passed as a parameter.
+  // Checks if a term id is passed as a parameter.
   if (!isset($tid)) {
     // It is not, so get the default term id.
     $tid = userpoints_get_default_tid();
   }
 
-  // Check if we have already cached the maximum for the user/term combination on previous calls.
+  // Checks if we have already cached the maximum for the user/term combination on previous calls.
   if (!isset($max[$uid][$tid])) {
     // We did not cache it.
     if ($tid === 'all') {
@@ -689,8 +698,9 @@ function userpoints_get_max_points($uid = NULL, $tid = NULL) {
 /**
  * Grant a user points.
  *
- * The function excpects two required parameters, an operation string
- * and the amount of points. Optionally, a user id can be passed.
+ * The function expects three required parameters, an operation string, the
+ * amount of points, and the transaction bundle. Optionally, a user id may be
+ * passed.
  *
  * The function then returns a UserpointsTransaction object, which provides
  * methods to add further details to the transaction. New transactions are saved
@@ -700,11 +710,13 @@ function userpoints_get_max_points($uid = NULL, $tid = NULL) {
  * Basic usage examples:
  * @code
  * // Adding points to the current user, relying on automatic saving.
- * userpoints_grant_points('mymodule_type_action', $points);
+ * userpoints_grant_points('mymodule_type_action', $points, 
+ *                         'my_userpoints_type');
  *
  * // Grant points to another, add a entity reference to a node and save
  * // explicitly.
- * userpoints_grant_points('mymodule_type_otheraction', $points, $account->uid)
+ * userpoints_grant_points('mymodule_type_otheraction', $points, 
+ *                         'my_userpoints_type', $account->uid)
  *   ->setEntity('node', $node->nid)
  *   ->save();
  * @endcode
@@ -718,6 +730,8 @@ function userpoints_get_max_points($uid = NULL, $tid = NULL) {
  *   transaction listings. See hook_userpoints_info().
  * @param $points
  *   A positive or negative point amount that should be assigned to the user.
+ * @param $type
+ *   The userpoints transaction bundle to create.
  * @param $uid
  *   UID of the user that should be granted points. Optional, defaults to the
  *   current user.
@@ -726,7 +740,7 @@ function userpoints_get_max_points($uid = NULL, $tid = NULL) {
  *
  * @ingroup userpoints_api
  */
-function userpoints_grant_points($operation, $points, $uid = NULL) {
+function userpoints_grant_points($operation, $points, $type, $uid = NULL) {
   global $user;
 
   // Default to the current user if not set.
@@ -734,7 +748,7 @@ function userpoints_grant_points($operation, $points, $uid = NULL) {
     $uid = $user->uid;
   }
 
-  $transaction = new UserpointsTransaction();
+  $transaction = new UserpointsTransaction(array('type' => $type));
   return $transaction->setOperation($operation)
     ->setPoints($points)
     ->setUid($uid);
@@ -984,7 +998,7 @@ function userpoints_filter_parse_input($form_state, $tid = NULL) {
  * Filter a query according to the selected filters.
  */
 function userpoints_filter_query(SelectQueryInterface $query, $values) {
-    // Check for filtering. isset() is used because 0 is a valid value
+    // Checks for filtering. isset() is used because 0 is a valid value
   // (Uncategorized).
   if (isset($values['tid']) && $values['tid'] != 'all') {
     // If a category is selected, limit both the default query and the query
@@ -1140,7 +1154,7 @@ function userpoints_expire_transactions() {
     ));
     $description = strtr(variable_get(USERPOINTS_EXPIRY_DESCRIPTION, NULL), $arguments);
 
-    userpoints_grant_points('expiry', -$transaction->points, $transaction->uid)
+    userpoints_grant_points('expiry', -$transaction->points, $transaction->type, $transaction->uid)
       ->setDescription($description)
       ->setParent($transaction->txn_id)
       ->setTid($transaction->tid)
@@ -1296,7 +1310,7 @@ function userpoints_userpoints_info() {
 }
 
 /**
- * Load a userpoints transaction.
+ * Loads a userpoints transaction.
  *
  * @param $txn_id
  *   Userpoints transaction Id.
@@ -1310,11 +1324,66 @@ function userpoints_transaction_load($txn_id, $reset = FALSE) {
   return $temp ? reset($temp) : FALSE;
 }
 
+/**
+ * Loads multiple userpoints transactions.
+ */
 function userpoints_transaction_load_multiple(array $txn_ids, $conditions = array(), $reset = FALSE) {
   return entity_load('userpoints_transaction', $txn_ids, $conditions, $reset);
 }
 
 /**
+ * Loads userpoints transaction type.
+ *
+ * @param $name
+ *   Userpoints transaction bundle name.
+ *
+ * @return UserpointsTransactionType
+ *   A userpoints transaction type entity.
+ */
+function userpoints_transaction_type_load($name) {
+  return userpoints_transaction_get_types($name);
+}
+
+/**
+ * Checks if the userpoints transaction type exists.
+ *
+ * @param $name
+ *   Userpoints transaction bundle name.
+ *
+ * @return bool
+ */
+function userpoints_transaction_type_exists($name) {
+  $type = userpoints_transaction_type_load($name);
+
+  if ($type) {
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/**
+ * Loads userpoints transaction types.
+ *
+ * @param $name
+ *   An optional userpoints transaction bundle name.
+ *
+ * @return Mixed
+ *   An array of userpoints transaction types or a UserpointsTransactionType.
+ */
+function userpoints_transaction_get_types($name = NULL) {
+  $types = entity_load_multiple_by_name('userpoints_transaction_type', isset($name) ? array($name) : FALSE);
+  return isset($name) ? reset($types) : $types;
+}
+
+/**
+ * Access callback for administration access to transaction type ui.
+ */
+function userpoints_transaction_type_access($op, $type) {
+  return user_access('administer userpoints');
+}
+
+/**
  * Returns a table header for a transaction listing.
  *
  * @param $settings
@@ -1431,6 +1500,7 @@ function userpoints_transaction_get_points_absolute($userpoints_transaction, arr
 function userpoints_entity_info() {
   $return = array(
     'userpoints_transaction' => array(
+      'module' => 'userpoints',
       'label' => t('Userpoints Transaction'),
       'entity class' => 'UserpointsTransaction',
       'controller class' => 'UserpointsTransactionController',
@@ -1441,12 +1511,19 @@ function userpoints_entity_info() {
       'fieldable' => TRUE,
       'entity keys' => array(
         'id' => 'txn_id',
+        'bundle' => 'type',
+      ),
+      'bundle keys' => array(
+        'bundle' => 'name',
       ),
       'bundles' => array(
-        'userpoints_transaction' => array(
-          'label' => t('Default bundle'),
+        'userpoints' => array(
+          'label' => t('Default'),
           'admin' => array(
-            'path' => 'admin/config/people/userpoints',
+            'path' => 'admin/config/people/userpoints/types/%userpoints_transaction_type',
+            'real path' => 'admin/config/people/userpoints/types/userpoints',
+            'bundle argument' => 5,
+            'access callback' => 'userpoints_admin_access',
             'access arguments' => array('administer userpoints'),
           ),
         ),
@@ -1458,6 +1535,48 @@ function userpoints_entity_info() {
         ),
       ),
     ),
+    'userpoints_transaction_type' => array(
+      'module' => 'userpoints',
+      'label' => t('Userpoints Transaction Type'),
+      'entity class' => 'UserpointsTransactionType',
+      'controller class' => 'EntityAPIControllerExportable',
+      'base table' => 'userpoints_txn_type',
+      'entity keys' => array(
+        'id' => 'id',
+        'name' => 'name',
+        'label' => 'label',
+      ),
+      'fieldable' => FALSE,
+      'bundle of' => 'userpoints_transaction',
+      'access callback' => 'userpoints_transaction_type_access',
+      'admin ui' => array(
+        'path' => 'admin/config/people/userpoints/types',
+        'file' => 'userpoints.admin.inc',
+        'controller class' => 'UserpointsTransactionTypeUIController',
+      ),   
+    ),
   );
   return $return;
 }
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function userpoints_entity_info_alter(&$info) {
+  $types = userpoints_transaction_get_types();
+
+  if (!empty($types)) {
+    foreach ($types as $name => $type) {
+      $info['userpoints_transaction']['bundles'][$name] = array(
+        'label' => check_plain($type->label),
+        'admin' => array(
+          'path' => 'admin/config/people/userpoints/types/%userpoints_transaction_type',
+          'real path' => 'admin/config/people/userpoints/types/' . $name,
+          'bundle argument' => 5,
+          'access callback' => 'userpoints_admin_access',
+          'access arguments' => array('administer userpoints'),
+        ),
+      );
+    }
+  }
+}
diff --git a/userpoints.test b/userpoints.test
index 20ea947..5d6e071 100644
--- a/userpoints.test
+++ b/userpoints.test
@@ -34,7 +34,7 @@ class UserpointsBaseTestCase extends DrupalWebTestCase {
       'txn_user' => $user->name,
       'points' => $points,
     ) + $additional;
-    $this->drupalPost('admin/config/people/userpoints/add', $edit, t('Save'));
+    $this->drupalPost('admin/config/people/userpoints/add/userpoints', $edit, t('Save'));
     if ($total !== NULL) {
       $categories = userpoints_get_categories();
       $tid = userpoints_get_default_tid();
@@ -89,7 +89,7 @@ class UserpointsAPITestCase extends UserpointsBaseTestCase {
   /**
    * Implements getInfo().
    */
-  function getInfo() {
+  static public function getInfo() {
     return array(
         'name' => t('Userpoints API'),
         'description' => t('Tests the core API for proper inserts & updates to the database tables,
@@ -183,7 +183,7 @@ class UserpointsAPITestCase extends UserpointsBaseTestCase {
     $bad_time = 'test string';
     // First lets check to make sure it is blocking bad times.
     try {
-      userpoints_grant_points('bad_time', $points, $this->non_admin_user->uid)
+      userpoints_grant_points('bad_time', $points, 'userpoints', $this->non_admin_user->uid)
         ->setExpiryDate($bad_time)
         ->save();
 
@@ -193,7 +193,7 @@ class UserpointsAPITestCase extends UserpointsBaseTestCase {
     }
 
     foreach ($times as $key => $time) {
-      $transaction = userpoints_grant_points($key, $points, $this->non_admin_user->uid)
+      $transaction = userpoints_grant_points($key, $points, 'userpoints', $this->non_admin_user->uid)
         ->setExpiryDate($time['time']);
       $transaction->save();
       $this->assertTrue((bool)$transaction->getTxnId(), t($time['string'] . " API responded with a successful grant of points"));
@@ -204,7 +204,7 @@ class UserpointsAPITestCase extends UserpointsBaseTestCase {
 
       $sum_points += $points;
 
-      // Check update point to 'userpoints' table.
+      // Check update point to userpoints table.
       $this->assertEqual($this->getPoints($this->non_admin_user->uid), $sum_points, t($time['string'] . "Successfully verified that the summary table was updated"));
     }
 
@@ -221,12 +221,12 @@ class UserpointsAPITestCase extends UserpointsBaseTestCase {
     $keep_points = rand(1, 100);
     $expire_points = rand(1, 100);
 
-    $transaction = userpoints_grant_points('must_expire', $expire_points, $this->non_admin_user->uid)
+    $transaction = userpoints_grant_points('must_expire', $expire_points, 'userpoints', $this->non_admin_user->uid)
       ->setExpiryDate(REQUEST_TIME - 100);
     $transaction->save();
     $this->assertTrue((bool)$transaction->getTxnId(), t("API succesfully added points for expiration"));
 
-    $transaction = userpoints_grant_points('must_not_expire', $keep_points, $this->non_admin_user->uid)
+    $transaction = userpoints_grant_points('must_not_expire', $keep_points, 'userpoints', $this->non_admin_user->uid)
       ->setExpiryDate(REQUEST_TIME + 10000);
     $transaction->save();
     $this->assertTrue((bool)$transaction->getTxnId(), t("API succesfully added points for expiration"));
@@ -471,7 +471,7 @@ class UserpointsAdminTestCase extends UserpointsBaseTestCase {
   /**
    * Implements getInfo().
    */
-  function getInfo() {
+  static public function getInfo() {
     return array(
         'name' => t('Userpoints Admin'),
         'description' => t('Test various userpoints administration forms and listings.'),
@@ -570,7 +570,7 @@ class UserpointsGrantPointsTestCase extends UserpointsBaseTestCase {
   /**
    * Implements getInfo().
    */
-  function getInfo() {
+  static public function getInfo() {
     return array(
       'name' => t('Userpoints grant points'),
       'description' => t('Tests the core API for proper inserts & updates to the database tables.'),
@@ -597,16 +597,16 @@ class UserpointsGrantPointsTestCase extends UserpointsBaseTestCase {
    */
   function testGrantPoints() {
     // Most basic usage, with automated saving.
-    userpoints_grant_points('test', 10, $this->non_admin_user->uid)->save();
+    userpoints_grant_points('test', 10, 'userpoints', $this->non_admin_user->uid)->save();
     $this->verifyPoints($this->non_admin_user->uid, 10, 10);
 
     // Negative points, use of save().
-    userpoints_grant_points('test', -5, $this->non_admin_user->uid)
+    userpoints_grant_points('test', -5, 'userpoints', $this->non_admin_user->uid)
       ->save();
     $this->verifyPoints($this->non_admin_user->uid, 5, 10);
 
     // Verify that pending points are not added to the total.
-    $transaction = userpoints_grant_points('test', 7, $this->non_admin_user->uid)
+    $transaction = userpoints_grant_points('test', 7, 'userpoints', $this->non_admin_user->uid)
       ->pending();
     $transaction->save();
     $this->verifyPoints($this->non_admin_user->uid, 5, 10);
@@ -629,7 +629,7 @@ class UserpointsGrantPointsTestCase extends UserpointsBaseTestCase {
       $this->pass(t('Changing a approved transaction was denied.'));
     }
 
-    $transaction = userpoints_grant_points('test', 19, $this->non_admin_user->uid)
+    $transaction = userpoints_grant_points('test', 19, 'userpoints', $this->non_admin_user->uid)
       ->pending();
     $transaction->save();
     $this->verifyPoints($this->non_admin_user->uid, 12, 12);
@@ -687,7 +687,7 @@ class UserpointsFieldsTestCase extends UserpointsBaseTestCase {
       'fields[_add_new_field][type]' => 'text',
       'fields[_add_new_field][widget_type]' => 'text_textfield',
     );
-    $this->drupalPost('admin/config/people/userpoints/fields', $edit, t('Save'));
+    $this->drupalPost('admin/config/people/userpoints/types/userpoints/fields', $edit, t('Save'));
     $this->drupalPost(NULL, array(), t('Save field settings'));
     $this->drupalPost(NULL, array(), t('Save settings'));
 
@@ -697,7 +697,7 @@ class UserpointsFieldsTestCase extends UserpointsBaseTestCase {
       'points' => 10,
       'field_' . $name . '[' . LANGUAGE_NONE . '][0][value]' => $this->randomName(50),
     );
-    $this->drupalPost('admin/config/people/userpoints/add', $message, t('Save'));
+    $this->drupalPost('admin/config/people/userpoints/add/userpoints', $message, t('Save'));
 
     // Check message.
     $this->drupalGet('myuserpoints');
diff --git a/userpoints.transaction.inc b/userpoints.transaction.inc
index 6535d89..07e4c3a 100644
--- a/userpoints.transaction.inc
+++ b/userpoints.transaction.inc
@@ -32,6 +32,8 @@ class UserpointsTransaction extends Entity {
    */
   public $txn_id = NULL;
 
+  public $type;
+
   public $uid = 0;
 
   public $points;
@@ -465,7 +467,9 @@ class UserpointsTransaction extends Entity {
    */
   function getEntity() {
     if (!empty($this->entity_id) && !empty($this->entity_type) && entity_get_info($this->entity_type)) {
-      return array_shift(entity_load($this->entity_type, array($this->entity_id)));
+      // Create an array because array_shift passes in by reference.
+      $entities = entity_load($this->entity_type, array($this->entity_id));
+      return array_shift($entities);
     }
   }
 
@@ -1308,6 +1312,12 @@ class UserpointsTransactionMetadataController extends EntityDefaultMetadataContr
     $properties['points']['label'] = t('!Points', userpoints_translation());
     $properties['points']['description'] = t('Amount of !points to give or take.', userpoints_translation());
 
+    $properties['type'] = array(
+      'label' => t('!Points transaction type', userpoints_translation()),
+      'description' => t('The bundle entity for !points transactions.', userpoints_translation()),
+      'type' => 'userpoints_transaction_type',
+    );
+
     $properties['points_abs'] = array(
       'label' => t('!Points absolute', userpoints_translation()),
       'description' => t('The absolute (positive) amount of !points of this transaction.', userpoints_translation()),
@@ -1430,3 +1440,126 @@ class UserpointsTransactionMetadataController extends EntityDefaultMetadataContr
     return $info;
   }
 }
+
+/**
+ * Transaction type.
+ *
+ * @ingroup userpoints_api 
+ */
+class UserpointsTransactionType extends Entity {
+
+  function __construct($values = array()) {
+    parent::__construct($values, 'userpoints_transaction_type');
+  }
+
+}
+
+/**
+ * Transaction type UI.
+ *
+ * @ingroup userpoints_api
+ */
+class UserpointsTransactionTypeUIController extends EntityDefaultUIController {
+
+  /**
+   * Implements EntityDefaultUIController::hook_menu().
+   *
+   * Make transaction type UI fit into userpoints menu structure.
+   */
+  public function hook_menu() {
+    $items = parent::hook_menu();
+    $items[$this->path]['title'] = 'Transaction Types';
+    $items[$this->path]['description'] = strtr('Manage !Points transaction types.', userpoints_translation());
+    $items[$this->path]['type'] = MENU_LOCAL_TASK;
+    $items[$this->path]['weight'] = 10;
+
+    foreach ($items as $path => $item) {
+      if (substr($path, strlen($this->path) + 1, 6) == 'manage') {
+        // Field UI doesn't render local tasks well when the main ui is a local
+        // task itself. This places all admin interfaces directly under types
+        // instead.
+        $new_path = preg_replace('/\/manage/', '', $path);
+        $items[$new_path] = $items[$path];
+        unset($items[$path]);
+      }
+    }
+
+    return $items;
+  }
+
+  /**
+   * Implements EntityDefaultUIController::overviewTableRow().
+   *
+   * This is pretty much a straight rip from Entity API except the links are
+   * generated without 'manage' in the path.
+   */
+  protected function overviewTableRow($conditions, $id, $entity, $additional_cols = array()) {
+    $entity_uri = entity_uri($this->entityType, $entity);
+
+    $row[] = array(
+      'data' => array(
+        '#theme' => 'entity_ui_overview_item', 
+        '#label' => entity_label($this->entityType, $entity), 
+        '#name' => !empty($this->entityInfo['exportable']) ? entity_id($this->entityType, $entity) : FALSE, 
+        '#url' => $entity_uri ? $entity_uri : FALSE, 
+        '#entity_type' => $this->entityType,
+      ),
+    );
+
+    // Add in any passed additional cols.
+    foreach ($additional_cols as $col) {
+      $row[] = $col;
+    }
+
+    // Add a row for the exportable status.
+    if (!empty($this->entityInfo['exportable'])) {
+      $row[] = array('data' => array(
+          '#theme' => 'entity_status', 
+          '#status' => $entity->{$this->statusKey},
+        ));
+    }
+    // In case this is a bundle, we add links to the field ui tabs.
+    $field_ui = !empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of']) && module_exists('field_ui');
+    // For exportable entities we add an export link.
+    $exportable = !empty($this->entityInfo['exportable']);
+    // If i18n integration is enabled, add a link to the translate tab.
+    $i18n = !empty($this->entityInfo['i18n controller class']);
+
+    // Add operations depending on the status.
+    if (entity_has_status($this->entityType, $entity, ENTITY_FIXED)) {
+      $row[] = array(
+        'data' => l(t('clone'), $this->path . '/' . $id . '/clone'),
+        'colspan' => $this->operationCount(),
+      );
+    }
+    else {
+      $row[] = l(t('edit'), $this->path . '/' . $id);
+
+      if ($field_ui) {
+        $row[] = l(t('manage fields'), $this->path . '/' . $id . '/fields');
+        $row[] = l(t('manage display'), $this->path . '/' . $id . '/display');
+      }
+      if ($i18n) {
+        $row[] = l(t('translate'), $this->path . '/' . $id . '/translate');
+      }
+      if ($exportable) {
+        $row[] = l(t('clone'), $this->path . '/' . $id . '/clone');
+      }
+
+      if (empty($this->entityInfo['exportable']) || !entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) {
+        $row[] = l(t('delete'), $this->path . '/' . $id . '/delete', array('query' => drupal_get_destination()));
+      }
+      elseif (entity_has_status($this->entityType, $entity, ENTITY_OVERRIDDEN)) {
+        $row[] = l(t('revert'), $this->path . '/' . $id . '/revert', array('query' => drupal_get_destination()));
+      }
+      else {
+        $row[] = '';
+      }
+    }
+    if ($exportable) {
+      $row[] = l(t('export'), $this->path . '/' . $id . '/export');
+    }
+    return $row;
+  }
+
+}
diff --git a/userpoints_rules/userpoints_rules.rules.inc b/userpoints_rules/userpoints_rules.rules.inc
index 87ccf1d..ec203c3 100644
--- a/userpoints_rules/userpoints_rules.rules.inc
+++ b/userpoints_rules/userpoints_rules.rules.inc
@@ -14,6 +14,11 @@ function userpoints_rules_rules_action_info() {
       'label' => t('Grant !points to a user', userpoints_translation()),
       'named parameter' => TRUE,
       'parameter' => array(
+        'type' => array(
+          'type' => 'userpoints_transaction_type',
+          'label' => t('Userpoints Transaction Type'),
+          'description' => t('Please choose the userpoints transaction type.'),
+        ),
         'user' => array(
           'type' => 'user',
           'label' => t('User'),
@@ -155,7 +160,7 @@ function userpoints_action_grant_points($params) {
   $state = $params['state'];
   $entity = $state->currentArguments['entity'];
 
-  $transaction = userpoints_grant_points($params['operation'], $params['points'])
+  $transaction = userpoints_grant_points($params['operation'], $params['points'], $params['type']->name)
     // User id might be a int or a EntityValueWrapper.
     ->setUid(is_object($params['user']->uid) ? $params['user']->getIdentifier() : $params['user']->uid)
     ->setTid($params['tid'])
diff --git a/userpoints_rules/userpoints_rules.test b/userpoints_rules/userpoints_rules.test
index 6bdd9ca..aa8fd83 100644
--- a/userpoints_rules/userpoints_rules.test
+++ b/userpoints_rules/userpoints_rules.test
@@ -63,7 +63,7 @@ class UserpointsRulesTestCase extends DrupalWebTestCase {
 
     $this->createEventRules();
 
-    $transaction = userpoints_grant_points('userpoints_rules_trigger_before_rule', 10, $user->uid);
+    $transaction = userpoints_grant_points('userpoints_rules_trigger_before_rule', 10, 'userpoints', $user->uid);
     $transaction->save();
 
     // Verify that the changes defined in the rules have been done.
@@ -75,7 +75,7 @@ class UserpointsRulesTestCase extends DrupalWebTestCase {
     $this->assertTrue($transaction->isDeclined(), t('Transaction has been marked as declined.'));
     $this->assertEqual('Transaction was declined through rules.', $transaction->getDescription(), t('Transaction description has been set.'));
 
-    $transaction = userpoints_grant_points('userpoints_rules_trigger_after_rule', 15, $user->uid)
+    $transaction = userpoints_grant_points('userpoints_rules_trigger_after_rule', 15, 'userpoints', $user->uid)
       ->pending();
     $transaction->save();
 
@@ -89,7 +89,7 @@ class UserpointsRulesTestCase extends DrupalWebTestCase {
    * Set up the rules required for the tests.
    */
   protected function createEventRules() {
-        $before_rule = '{ "rules_userpoints_transaction_before_test_rule" : {
+    $before_rule = '{ "rules_userpoints_transaction_before_test_rule" : {
         "LABEL" : "Userpoints Transaction Before Test rule",
         "PLUGIN" : "reaction rule",
         "REQUIRES" : [ "rules", "userpoints_rules" ],
@@ -153,6 +153,7 @@ class UserpointsRulesTestCase extends DrupalWebTestCase {
         "ON" : [ "user_login" ],
         "DO" : [
           { "userpoints_action_grant_points" : {
+              "type" : "userpoints",
               "user" : [ "account" ],
               "points" : "10",
               "tid" : "0",
