diff --git a/userpoints.admin.inc b/userpoints.admin.inc
index 38fd598..d9e13ff 100644
--- a/userpoints.admin.inc
+++ b/userpoints.admin.inc
@@ -16,7 +16,44 @@ 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 '';
+  }
+
+  $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 +68,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 +423,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 +877,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..1465a8a 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,6 +208,47 @@ 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;
 }
 
@@ -331,4 +378,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..f5e8874 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',
@@ -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);
@@ -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)
@@ -1310,11 +1324,66 @@ function userpoints_transaction_load($txn_id, $reset = FALSE) {
   return $temp ? reset($temp) : FALSE;
 }
 
+/**
+ * Load multiple userpoints transactions.
+ */
 function userpoints_transaction_load_multiple(array $txn_ids, $conditions = array(), $reset = FALSE) {
   return entity_load('userpoints_transaction', $txn_ids, $conditions, $reset);
 }
 
 /**
+ * Load 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);
+}
+
+/**
+ * Check if the userpoints transaction type exists.
+ *
+ * @param $name
+ *   Userpoints transaction bundle name.
+ *
+ * @return Boolean
+ */
+function userpoints_transaction_type_exists($name) {
+  $type = userpoints_transaction_type_load($name);
+
+  if ($type) {
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/**
+ * Load 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,16 +1511,12 @@ function userpoints_entity_info() {
       'fieldable' => TRUE,
       'entity keys' => array(
         'id' => 'txn_id',
+        'bundle' => 'type',
       ),
-      'bundles' => array(
-        'userpoints_transaction' => array(
-          'label' => t('Default bundle'),
-          'admin' => array(
-            'path' => 'admin/config/people/userpoints',
-            'access arguments' => array('administer userpoints'),
-          ),
-        ),
+      'bundle keys' => array(
+        'bundle' => 'name',
       ),
+      'bundles' => array(),
       'view modes' => array(
         'full' => array(
           'label' => t('Full content'),
@@ -1458,6 +1524,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/manage/%userpoints_transaction_type',
+          'real path' => 'admin/config/people/userpoints/types/manage/' . $name,
+          'bundle argument' => 6,
+          'access callback' => 'userpoints_admin_access',
+          'access arguments' => array('administer userpoints'),
+        ),
+      );
+    }
+  }
+}
diff --git a/userpoints.test b/userpoints.test
index 20ea947..45847b0 100644
--- a/userpoints.test
+++ b/userpoints.test
@@ -13,6 +13,8 @@
  */
 class UserpointsBaseTestCase extends DrupalWebTestCase {
 
+  public $txn_type;
+
   /**
    * Add points through the admin form.
    *
@@ -34,7 +36,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/' . $this->txn_type->name, $edit, t('Save'));
     if ($total !== NULL) {
       $categories = userpoints_get_categories();
       $tid = userpoints_get_default_tid();
@@ -76,6 +78,16 @@ class UserpointsBaseTestCase extends DrupalWebTestCase {
       $this->assertEqual($max, $api_max, t('Max points for tid %tid are correct (expected: %expected, actual: %actual).', array('%expected' => $max, '%actual' => $api_max, '%tid' => $tid)));
     }
   }
+
+  function setUp() {
+    parent::setUp(array('userpoints'));
+
+    // Create a transaction bundle.
+    $this->txn_type = new UserpointsTransactionType();
+    $this->txn_type->label = $this->randomName();
+    $this->txn_type->name = strtolower($this->txn_type->label);
+    $this->txn_type->save();
+  }
 }
 
 /**
@@ -183,7 +195,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, $this->txn_type->name, $this->non_admin_user->uid)
         ->setExpiryDate($bad_time)
         ->save();
 
@@ -193,7 +205,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, $this->txn_type->name, $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"));
@@ -221,12 +233,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, $this->txn_type->name, $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, $this->txn_type->name, $this->non_admin_user->uid)
       ->setExpiryDate(REQUEST_TIME + 10000);
     $transaction->save();
     $this->assertTrue((bool)$transaction->getTxnId(), t("API succesfully added points for expiration"));
@@ -597,16 +609,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, $this->txn_type->name, $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, $this->txn_type->name, $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, $this->txn_type->name, $this->non_admin_user->uid)
       ->pending();
     $transaction->save();
     $this->verifyPoints($this->non_admin_user->uid, 5, 10);
@@ -629,7 +641,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, $this->txn_type->name, $this->non_admin_user->uid)
       ->pending();
     $transaction->save();
     $this->verifyPoints($this->non_admin_user->uid, 12, 12);
@@ -687,7 +699,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/manage/' . $this->txn_type->name . '/fields', $edit, t('Save'));
     $this->drupalPost(NULL, array(), t('Save field settings'));
     $this->drupalPost(NULL, array(), t('Save settings'));
 
@@ -697,7 +709,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/' . $this->txn_type->name, $message, t('Save'));
 
     // Check message.
     $this->drupalGet('myuserpoints');
diff --git a/userpoints.transaction.inc b/userpoints.transaction.inc
index 6535d89..09539d9 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;
@@ -1308,6 +1310,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 +1438,34 @@ 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 {
+
+  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;
+    return $items;
+  }
+
+}
