diff --git a/userpoints.admin.inc b/userpoints.admin.inc
index 38fd598..1b80eae 100644
--- a/userpoints.admin.inc
+++ b/userpoints.admin.inc
@@ -773,41 +773,58 @@ function userpoints_admin_settings($form, &$form_state) {
   // a requirement for the module.
   if (module_exists('taxonomy')) {
     $group = 'category';
+
     $form[$group] = array(
-        '#type' => 'fieldset',
-        '#collapsible' => TRUE,
-        '#collapsed' => TRUE,
-        '#title' => t('Categorization', userpoints_translation()),
+      '#type' => 'fieldset',
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#title' => t('Categorization', userpoints_translation()),
       '#group' => 'settings',
     );
-    $form[$group][USERPOINTS_CATEGORY_DEFAULT_TID] = array(
+
+    if (userpoints_get_vid()) {
+      $form[$group][USERPOINTS_CATEGORY_DEFAULT_TID] = array(
         '#type' => 'select',
         '#title' => t('Default Category'),
         '#default_value' => variable_get(USERPOINTS_CATEGORY_DEFAULT_TID, NULL),
         '#options' => userpoints_get_categories(),
         '#description' => t('By default all !points are assigned to this category.  You can modify what categories are available by modifying the <a href="!url">Userpoints taxonomy</a>',
                 array_merge(userpoints_translation(), array('!url' =>  url('admin/structure/taxonomy/' . taxonomy_vocabulary_load(variable_get(USERPOINTS_CATEGORY_DEFAULT_VID, ''))->machine_name)))),
-    );
-    $options = userpoints_get_categories(NULL);
-    // 0 can not be used as a checkbox value.
-    $options = array('uncategorized' => $options[0]) + $options + array('all' => t('Total !points in all categories', userpoints_translation()));
-    unset($options[0]);
-    $form[$group][USERPOINTS_CATEGORY_PROFILE_DISPLAY_TID] = array(
+      );
+      $options = userpoints_get_categories(NULL);
+      // 0 can not be used as a checkbox value.
+      $options = array('uncategorized' => $options[0]) + $options + array('all' => t('Total !points in all categories', userpoints_translation()));
+      unset($options[0]);
+      $form[$group][USERPOINTS_CATEGORY_PROFILE_DISPLAY_TID] = array(
         '#type' => 'checkboxes',
         '#title' => t("Categories to display on the user profile page and in the User's !points block", userpoints_translation()),
         '#default_value' => variable_get(USERPOINTS_CATEGORY_PROFILE_DISPLAY_TID, array_keys($options)),
         '#options' => $options,
         '#description' => t('Select the !points categories that should be displayed. Check "Total !points in all categories" to display a sum total of all individual !points categories.', userpoints_translation()),
-    );
-
-    $form[$group][USERPOINTS_TRANS_UNCAT] = array(
-      '#type' => 'textfield',
-      '#title' => t('Word to use for the general category'),
-      '#default_value' => variable_get(USERPOINTS_TRANS_UNCAT, 'General'),
-      '#description' => t("By default, %default is the name used for the module's umbrella category. You may change this here.", array('%default' => t('General'))),
-      '#size' => 20,
-      '#maxlength' => 20,
-    );
+      );
+
+      $form[$group][USERPOINTS_TRANS_UNCAT] = array(
+        '#type' => 'textfield',
+        '#title' => t('Word to use for the general category'),
+        '#default_value' => variable_get(USERPOINTS_TRANS_UNCAT, 'General'),
+        '#description' => t("By default, %default is the name used for the module's umbrella category. You may change this here.", array('%default' => t('General'))),
+        '#size' => 20,
+        '#maxlength' => 20,
+      );
+    }
+    else {
+      // Display message to create vocabulary and taxonomy term reference.
+      $form[$group]['#collapsed'] = FALSE;
+      $form[$group]['message'] = array(
+        '#type' => 'container',
+        '#attributes' => array(
+          'class' => array('messages', 'warning'),
+        ),
+        'message' => array(
+          '#markup' => l(t('Click to create a vocabulary and taxonomy term reference field to use categorization.'), 'admin/config/people/userpoints/settings/categorization'),
+        ),
+      );
+    }
   }
   // New configuration options to override current timestamp.
   $group = "stamping";
@@ -835,3 +852,42 @@ function userpoints_admin_settings($form, &$form_state) {
 
   return system_settings_form($form);
 }
+
+/**
+ * Confirmation form to create vocabulary and taxonomy term reference.
+ */
+function userpoints_admin_categorization($form, &$form_state) {
+  $form['confirm'] = array(
+    '#markup' => t('Are you sure you want to associate userpoints transactions with categorization?'),
+  );
+
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Confirm'),
+  );
+  $form['actions']['cancel'] = array(
+    '#markup' => l(t('Cancel'), 'admin/config/people/userpoints/settings'),
+  );
+
+  return $form;
+}
+
+/**
+ * Submit callback for confirmation form to create vocabulary and taxonomy term
+ * reference.
+ */
+function userpoints_admin_categorization_submit($form, &$form_state) {
+  $vid = userpoints_get_vid();
+
+  if (!$vid) {
+    module_load_include('install', 'userpoints', 'userpoints');
+    
+    userpoints_install_taxonomy();
+  }
+  else {
+    drupal_set_message(t('Userpoints is already associated with categorization.'), 'warning');
+  }
+
+  $form_state['redirect'] = 'admin/config/people/userpoints/settings';
+}
diff --git a/userpoints.info b/userpoints.info
index 9a9331c..e9c7199 100644
--- a/userpoints.info
+++ b/userpoints.info
@@ -9,3 +9,4 @@ files[]=views/userpoints_views_handler_filter_category.inc
 files[]=views/userpoints_views_handler_argument_category.inc
 configure = admin/config/people/userpoints/settings
 dependencies[]=entity
+dependencies[]=userpoints_field
diff --git a/userpoints.install b/userpoints.install
index 990c381..1d07e2c 100644
--- a/userpoints.install
+++ b/userpoints.install
@@ -206,6 +206,76 @@ function userpoints_schema() {
 }
 
 /**
+ * Implements hook_install().
+ */
+function userpoints_install() {
+
+  field_cache_clear();
+
+  // Create userpoints field.
+  $field_info = array(
+    'field_name' => 'field_userpoints',
+    'type' => 'userpoints',
+  );  
+  $field = field_create_field($field_info);
+
+  // Create userpoints field instance.
+  $instance_info = array(
+    'field_name' => 'field_userpoints',
+    'entity_type' => 'user',
+    'bundle' => 'user',
+    'label' => st('Points'),
+  );
+  $instance = field_create_instance($instance_info);
+
+  drupal_set_message(t('Created field %field_name.', array('%field_name' => 'field_userpoints')));
+
+  if (module_exists('taxonomy')) {
+    userpoints_install_taxonomy();
+  }
+}
+
+/**
+ * Try to create vocabulary and taxonomy term reference field. This function may
+ * be executed at install or during runtime.
+ */
+function userpoints_install_taxonomy() {
+  // Only try categorization if taxonomy is enabled.
+  $vid = userpoints_get_vid();
+
+  if (!$vid) {
+    // Create vocabulary or taxonomy category.
+    $vid = userpoints_create_vocabulary();
+    variable_set(USERPOINTS_CATEGORY_DEFAULT_VID, $vid);
+    drupal_set_message(t('Created userpoints vocabulary.'));
+  }
+
+  // Create or re-use taxonomy term reference field and instance.
+  $field = field_read_field('field_userpoints_category');
+  try {
+    $field = userpoints_create_term_reference_field($field);
+  }
+  catch (Exception $e) {
+    watchdog_exception('userpoints', $e, 'Unable to create taxonomy term reference field: %info', array('%info' => $e->message));
+  }
+
+  try {
+    $instance = field_read_instance('userpoints', 'field_userpoints_category', 'userpoints');
+    $instance = userpoints_create_term_reference_instance($instance);
+  }
+  catch (Exception $e) {
+    watchdog_exception('userpoints', $e, 'Unable to create taxonomy term reference field instance: %info', array('%info' => $e->message));
+  }
+
+  if ($instance) {
+    drupal_set_message(t('Created userpoints transaction taxonomy term reference field.'));
+  }
+  else {
+    drupal_set_message(t('Unable to create userpoints transaction taxonomy term reference field!'), 'error');
+  }
+}
+
+/**
  * Implements hook_uninstall().
  */
 function userpoints_uninstall() {
@@ -217,6 +287,8 @@ function userpoints_uninstall() {
   if ($vid && function_exists('taxonomy_vocabulary_delete')) {
     taxonomy_vocabulary_delete($vid);
   }
+
+  field_delete_field('field_userpoints');
 }
 
 /**
@@ -331,4 +403,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..efc0fe4 100644
--- a/userpoints.module
+++ b/userpoints.module
@@ -197,6 +197,18 @@ function userpoints_menu() {
       'weight' => 10,
   );
 
+  $items['admin/config/people/userpoints/settings/categorization'] = array(
+    'title' => 'Create !Points categorization',
+    'title arguments' => userpoints_translation(),
+    'description' => 'Create or re-use taxonomy on userpoints transactions.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('userpoints_admin_categorization'),
+    'access callback' => 'userpoints_admin_access',
+    'access arguments' => array('administer'),
+    'file' => 'userpoints.admin.inc',
+    'type' => MENU_CALLBACK,
+  );
+
   $items['userpoints'] = array(
       'title' => 'Users by !points',
       'title arguments' => userpoints_translation(),
@@ -610,6 +622,67 @@ function userpoints_token_info() {
 }
 
 /**
+ * Implements hook_userpoints_transaction_update().
+ *
+ * @todo
+ *   Categorization.
+ */
+function userpoints_userpoints_transaction_update($entity) {
+  $wrapper = entity_metadata_wrapper('userpoints_transaction', $entity);
+  $account = $wrapper->user->value();
+
+  $account->field_userpoints['und'][0]['points'] = userpoints_transaction_get_points($account->uid);
+  user_save($account);
+}
+
+/**
+ * Implements hook_userpoints_transaction_insert().
+ *
+ * This supports retroactive points if there are transactions for a user and no
+ * points field value yet.
+ */
+function userpoints_userpoints_transaction_insert($entity) {
+  $wrapper = entity_metadata_wrapper('userpoints_transaction', $entity);
+  $account = $wrapper->user->value();
+
+  $account->field_userpoints['und'][0]['points'] = userpoints_transaction_get_points($account->uid);
+  user_save($account);
+}
+
+/**
+ * Get sum of points from transactions. This is an aggregate function.
+ *
+ * @param $uid
+ *   An optional user ID to filter by.
+ * @param $tid
+ *   An optional taxonomy term ID to get points for.
+ * @return
+ *   Number of points given parameters.
+ *
+ * @todo
+ *   Categorization
+ *
+ * @ingroup userpoints_api
+ */
+function userpoints_transaction_get_points($uid = NULL, $tid = NULL) {
+  $query = db_select('userpoints_txn');
+  $query->addExpression('SUM(points)', 'points_sum');
+
+  if (isset($uid)) {
+    $query->condition('uid', $uid);
+  }
+
+  if (isset($tid)) {
+    $query->condition('tid', $tid);
+  }
+
+  $points = $query->execute()->fetchField();
+
+  // Possible non-numeric value returned from fetchField.
+  return ($points) ? $points : 0;
+}
+
+/**
  * Get current points of a user.
  *
  * @param $uid
@@ -1164,41 +1237,140 @@ function userpoints_cron() {
 
 
 /**
- *  Returns the Vocabulary ID (vid) used by userpoints for categorization.
+ * Returns the Vocabulary ID (vid) used by userpoints for categorization. This
+ * function no longer creates the vocabulary.
+ *
+ * @see userpoints_create_vocabulary()
  *
- *  If no vocab exists it will create one.
+ * @return
+ *   $vid The Vocabulary ID or FALSE.
  */
 function userpoints_get_vid() {
   if (!module_exists('taxonomy')) {
     return FALSE;
   }
-  // Code lovingly inspired by the image.module w/ code by drewish.
+
   $vid = variable_get(USERPOINTS_CATEGORY_DEFAULT_VID, '');
   if (empty($vid) || !taxonomy_vocabulary_load($vid)) {
-    $sql = "SELECT vid FROM {taxonomy_vocabulary} WHERE module='userpoints'";
-    $vid = db_query($sql)->fetchField();
-    if (!$vid) {
-      drupal_set_message(t("Created Userpoints vocabulary"));
-      // No vocabulary exists, we'll create one.
-      $vocab = (object) array(
-          'name' => USERPOINTS_CATEGORY_NAME,
-          'description' => t('Automatically created by the userpoints module'),
-          'machine_name' => 'userpoints',
-          'multiple' => '0',
-          'required' => '0',
-          'hierarchy' => '1',
-          'relations' => '0',
-          'module' => 'userpoints',
+    // Try to load the vocabulary by its default machine name.
+    $vocab = taxonomy_vocabulary_machine_name_load('userpoints');
+    $vid = isset($vocab->vid) ? $vocab->vid : FALSE;
+  }
+
+  return $vid;
+}
+
+/**
+ * Create taxonomy vocabulary. This may be run at install or runtime. This is
+ * a rewrite of a similar function to be more test-friendly.
+ *
+ * @return
+ *   $vid The vocabulary id or FALSE if unable to create.
+ */
+function userpoints_create_vocabulary() {
+  $vocab = (object) array(
+    'name' => USERPOINTS_CATEGORY_NAME,
+    'description' => t('Automatically created by the userpoints module'),
+    'machine_name' => 'userpoints',
+    'multiple' => '0',
+    'required' => '0',
+    'hierarchy' => '1',
+    'relations' => '0',
+    'module' => 'userpoints',
+  );
+
+  try {
+    taxonomy_vocabulary_save($vocab);
+  }
+  catch (Exception $e) {
+    watchdog_exception('userpoints', $e, 'Unable to create vocabulary.');
+  }
+
+  return isset($vocab->vid) ? $vocab->vid : FALSE;
+}
+
+/**
+ * Create a taxonomy term reference field.
+ *
+ * @param
+ *   $field An optional field info array to use if the field already exists.
+ *
+ * @return
+ *   Returns a taxonomy term reference field instance or FALSE.
+ */
+function userpoints_create_term_reference_field($field = FALSE) {
+
+  if ($field === FALSE) {
+    // Field does not exist.
+    $field_info = array(
+      'field_name' => 'field_userpoints_category',
+      'type' => 'taxonomy_term_reference',
+      'settings' => array(
+        'allowed_values' => array(
+          array(
+            'vocabulary' => 'userpoints',
+            'parent' => 0,
+          ),
+        ),
+      ),
+    );
+
+    $field = field_create_field($field_info);
+  }
+  elseif ($field['type'] != 'taxonomy_term_reference') {
+    // Throwing an exception here probably is a bad idea.
+    watchdog('userpoints', 'field_userpoints_category is of type %type, not taxonomy term reference. %blah', array('%type' => $field['type'], '%blah' => print_r($field, TRUE)), WATCHDOG_CRITICAL);
+    return FALSE;
+  }
+  else {
+    // Check if the vocabulary machine name exists in allowed values.
+
+    $has_vocab = FALSE;
+    foreach ($field['settings']['allowed_values'] as $item) {
+      // @todo Use an array_reduce anonymous function instead?
+      if ($item['vocabulary'] == 'userpoints') {
+        $has_vocab = TRUE;
+      }
+    }
+
+    if (!$has_vocab) {
+      $field['settings']['allowed_values'][] = array(
+        'vocabulary' => 'userpoints',
+        'parent' => 0,
       );
-      taxonomy_vocabulary_save($vocab);
-      $vid = $vocab->vid;
+      $field = field_update_field($field);
     }
-    variable_set(USERPOINTS_CATEGORY_DEFAULT_VID, $vid);
   }
-  if (!is_numeric($vid)) {
-    watchdog('userpoints', 'userpoints module was unable to select or create a vocabulary. !Points will be uncategorized', array(), WATCHDOG_ERROR);
+
+  return $field;
+}
+
+/**
+ * Create a taxonomy term reference field instance.
+ *
+ * @param
+ *   $instance An optional field instance to use.
+ *
+ * @return
+ *   $instance The taxonomy term reference field instance or FALSE.
+ */
+function userpoints_create_term_reference_instance($instance = FALSE) {
+  if ($instance === FALSE) {
+    // Create a field instance if it does not exist.
+    $instance_info = array(
+      'field_name' => 'field_userpoints_category',
+      'bundle' => 'userpoints_transaction',
+      'entity_type' => 'userpoints_transaction',
+      'label' => t('Category'),
+      'widget' => array(
+        'type' => 'options_select',
+      ),
+    );
+
+    $instance = field_create_instance($instance_info);
   }
-  return $vid;
+
+  return $instance;
 }
 
 /**
@@ -1220,7 +1392,7 @@ function userpoints_get_categories($account = NULL) {
         if ($account) {
           $query = db_select('taxonomy_term_data', 't')
             ->fields('t', array('tid', 'name'))
-            ->condition('t.vid', userpoints_get_vid())
+            ->condition('t.vid', $vid)
             ->groupBy('t.tid')
             ->groupBy('t.name')
             ->groupBy('t.weight')
diff --git a/userpoints.transaction.inc b/userpoints.transaction.inc
index 6535d89..2b0c6a5 100644
--- a/userpoints.transaction.inc
+++ b/userpoints.transaction.inc
@@ -1369,6 +1369,7 @@ class UserpointsTransactionMetadataController extends EntityDefaultMetadataContr
     $properties['uid'] = array(
       'label' => t('User ID'),
       'description' => t('ID of the user who received the point.'),
+      'type' => 'user',
     ) + $properties['uid'];
 
     $properties['approver_uid'] = array(
diff --git a/userpoints_field/userpoints_field.info b/userpoints_field/userpoints_field.info
new file mode 100644
index 0000000..d6bc972
--- /dev/null
+++ b/userpoints_field/userpoints_field.info
@@ -0,0 +1,5 @@
+name = Userpoints Field
+description = Provides a points field and widget for user points as well as taxonomy term.
+package=Userpoints
+core = 7.x
+dependencies[] = field
diff --git a/userpoints_field/userpoints_field.install b/userpoints_field/userpoints_field.install
new file mode 100644
index 0000000..96787f4
--- /dev/null
+++ b/userpoints_field/userpoints_field.install
@@ -0,0 +1,26 @@
+<?php
+/**
+ * @file
+ *   Userpoints field schema and installation.
+ */
+
+/**
+ * Implements hook_field_schema().
+ */
+function userpoints_field_field_schema($field) {
+  if ($field['type'] == 'userpoints') {
+    return array(
+      'columns' => array(
+        'points' => array(
+          'type' => 'numeric',
+          'not null' => TRUE,
+        ),  
+        'max_points' => array(
+          'type' => 'numeric',
+          'not null' => TRUE,
+        ),  
+      ),  
+    );  
+  }
+}
+
diff --git a/userpoints_field/userpoints_field.module b/userpoints_field/userpoints_field.module
new file mode 100644
index 0000000..6b341ff
--- /dev/null
+++ b/userpoints_field/userpoints_field.module
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * @file
+ *   Provides denormalized/aggregated points field implementation.
+ */
+
+
+/**
+ * Implements hook_field_info().
+ *
+ * @todo Remove no_ui flag when this becomes feasible for all entities.
+ */
+function userpoints_field_field_info() {
+  return array(
+    'userpoints' => array(
+      'label' => t('Points'),
+      'description' => t('The total number of points associated with an entity (User).'),
+      'default_widget' => 'userpoints_hidden',
+      'default_formatter' => 'userpoints_default',
+      'no_ui' => TRUE,
+    ),
+  );
+}
+
+/**
+ * Implements hook_field_widget_info().
+ */
+function userpoints_field_field_widget_info() {
+  return array(
+    'userpoints_hidden' => array(
+      'label' => t('Points (hidden)'),
+      'description' => t('A dummy widget that displays the current point value associated with the entity.'),
+      'field types' => array('userpoints'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+        'default value' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+  );
+}
+
+/**
+ * Implements hook_field_formatter_info().
+ */
+function userpoints_field_field_formatter_info() {
+  return array(
+    'userpoints_default' => array(
+      'label' => t('Default'),
+      'field types' => array('userpoints'),
+      'settings' => array(
+        'display' => 'points',
+      ),
+    ),
+  );
+}
+
+/**
+ * Implements hook_field_update().
+ */
+function userpoints_field_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  if (!empty($items)) {
+    foreach ($items as $delta => $item) {
+      // There should always be cardinality 1 for this field, but don't assume.
+
+      if (!isset($item['max_points']) || $item['points'] > $item['max_points']) {
+        // Set maximum points only if current points is greater.
+        $items[$delta]['max_points'] = $item['points'];
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_field_widget_form().
+ *
+ * Do not display any widget. All points will be saved via the userpoints transaction system.
+ */
+function userpoints_field_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, &$items, $delta, $element) {
+  return $element;
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary().
+ */
+function userpoints_field_field_formatter_settings_summary($field, $instance, $view_mode) {
+  $display = $instance['display'][$view_mode];
+
+  $option = ($display['settings']['display'] == 'points') ? 'Points' : 'Max Points';
+  
+  return t('Display @option', array('@option' => $option));
+}
+
+/**
+ * Implements hook_field_formatter_settings_form().
+ */
+function userpoints_field_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
+  $display = $instance['display'][$view_mode];
+  $settings = $display['settings'];
+
+  $form['display'] = array(
+    '#type' => 'select',
+    '#title' => t('Display'),
+    '#description' => t('Select what to display.'),
+    '#options' => array('points' => t('Points'), 'max_points' => t('Max Points')),
+    '#default_value' => $settings['display'],
+  );
+
+  return $form;
+}
+
+/**
+ * Implements hook_field_formatter_view().
+ *
+ * @todo Replace the logic in this with a better theme_userpoints_points().
+ */
+function userpoints_field_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+  $element = array();
+
+  foreach ($items as $delta => $item) {
+    $element[$delta] = array(
+      '#theme' => 'userpoints_points',
+      '#points' => ($display['settings']['display'] == 'points') ? $item['points'] : $item['max_points'],
+    );
+  }
+
+  return $element;
+}
