diff --git a/userpoints.field.inc b/userpoints.field.inc
new file mode 100644
index 0000000..426f7b9
--- /dev/null
+++ b/userpoints.field.inc
@@ -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_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_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_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_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_widget_form(&$form, &$form_state, $field, $instance, $langcode, &$items, $delta, $element) {
+  return $element;
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary().
+ */
+function userpoints_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_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_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;
+}
diff --git a/userpoints.install b/userpoints.install
index 990c381..b9b7151 100644
--- a/userpoints.install
+++ b/userpoints.install
@@ -206,6 +206,55 @@ function userpoints_schema() {
 }
 
 /**
+ * Implements hook_field_schema().
+ */
+function userpoints_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,
+        ),  
+      ),  
+    );  
+  }
+}
+
+/**
+ * Implements hook_install().
+ *
+ * @todo
+ *   Taxonomy points field installation.
+ */
+function userpoints_install() {
+
+  // Unfortunately cache must be cleared to install fields defined by module.
+  field_cache_clear();
+
+  $field_info = array(
+    'field_name' => 'field_userpoints',
+    'type' => 'userpoints',
+  );
+
+  $field = field_create_field($field_info);
+
+  $instance_info = array(
+    'field_name' => 'field_userpoints',
+    'entity_type' => 'user',
+    'bundle' => 'user',
+    'label' => t('Points'),
+  );
+
+  // Create user points field instance.
+  $instance = field_create_instance($instance_info);
+}
+
+/**
  * Implements hook_uninstall().
  */
 function userpoints_uninstall() {
@@ -217,6 +266,9 @@ function userpoints_uninstall() {
   if ($vid && function_exists('taxonomy_vocabulary_delete')) {
     taxonomy_vocabulary_delete($vid);
   }
+
+  // Cron needs to run to actually delete the field.
+  field_delete_field('field_userpoints');
 }
 
 /**
@@ -331,4 +383,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..420d700 100644
--- a/userpoints.module
+++ b/userpoints.module
@@ -26,6 +26,8 @@ define('USERPOINTS_CATEGORY_DEFAULT_TID', 'userpoints_category_default_tid');
 define('USERPOINTS_CATEGORY_PROFILE_DISPLAY_TID', 'userpoints_category_profile_display_tid');
 define('USERPOINTS_TRANSACTION_TIMESTAMP', 'userpoints_transaction_timestamp');
 
+module_load_include('inc', 'userpoints', 'userpoints.field');
+
 /**
  * Returns an array of common translation placeholders.
  */
@@ -610,6 +612,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
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(
