diff --git a/includes/project_issue_node_type.inc b/includes/project_issue_node_type.inc
index 2cc7674..9f4d4ee 100644
--- a/includes/project_issue_node_type.inc
+++ b/includes/project_issue_node_type.inc
@@ -575,10 +575,10 @@ function _project_issue_create_issue_node_type() {
     'display' => array(
       'default' => array(
         'label' => 'above',
-        'type' => 'file_default',
+        'type' => 'issue_files_summary_table',
         'settings' => array(
         ),
-        'module' => 'file',
+        'module' => 'project_issue',
         'weight' => 4,
       ),
       'teaser' => array(
diff --git a/project_issue.api.php b/project_issue.api.php
index f75be55..1630002 100644
--- a/project_issue.api.php
+++ b/project_issue.api.php
@@ -63,3 +63,43 @@ function hook_project_issue_assignees_alter(&$assignees, $issue, $project) {
     unset($assignees[$user_id]);
   }
 }
+
+/**
+ * Alter the table of files attached to an issue.
+ *
+ * The $rows and $header arrays will be passed to theme_table() for final
+ * rendering into HTML, so the alter hook will get data in a format compatible
+ * with theme_table() and must only alter these two arrays in a way that
+ * theme_table() supports.
+ *
+ * This alter hook depends on the relelvant file field being configured to use
+ * the 'Table of files and issue metadata' field formatter.
+ *
+ * @param array $rows
+ *  Reference to a nested array of rows to render in the issue files
+ *  table.
+ * @param array $header
+ *   Reference to an array describing the header to use for the table.
+ * @param array $context
+ *  Associative array of context for the table of files being altered with
+ *  the following keys:
+ *  - items: Array of file items included in the given file field.
+ *  - field: The field definition array.
+ *  - instance: The field instance definition array.
+ *  - entity: An object representing the entity the file field is attachted to.
+ *  - entity_type: String with the type of entity the field is attached to.
+ *
+ * @see theme_table()
+ * @see drupal_alter()
+ */
+function hook_project_issue_files_summary_table_items_alter(&$rows, &$header, $context) {
+  // Add another column to the table.
+  $header[] = array(
+    'data' => t('Fancy'),
+    'name' => 'issue-file-summary-table-header-fancy',
+    'class' => array('issue-file-summary-table-header'),
+  );
+  foreach ($context['items'] as $item) {
+    // Do something. How do we find the right row in $rows? Ugh.
+  }
+}
diff --git a/project_issue.module b/project_issue.module
index 71b29dd..7eebeac 100644
--- a/project_issue.module
+++ b/project_issue.module
@@ -1703,8 +1703,11 @@ function project_issue_nodereference_autocomplete_validate($element, &$form_stat
 /**
  * Implements hook_field_formatter_info().
  *
- * Provides a formatter for nodereference fields that uses
- * theme_project_issue_issue_link().
+ * Provides:
+ * 1) Two formatters for nodereference fields, which use
+ *    theme_project_issue_issue_link().
+ * 2) A formatter for file fields which renders a table of files with
+ *    associated issue  metadata.
  */
 function project_issue_field_formatter_info() {
   return array(
@@ -1718,16 +1721,189 @@ function project_issue_field_formatter_info() {
       'field types' => array('entityreference'),
       'multiple values' => FIELD_BEHAVIOR_DEFAULT,
     ),
+    'issue_files_summary_table' => array(
+      'label' => t('Table of files with issue metadata'),
+      'field types' => array('file'),
+      'settings' => array(
+        // Restrict initial display to certain file types.
+        'filetypes' => 'all',
+        // Comma-separated list of extensions to display.
+        'extensions' => 'patch,diff',
+        // Number of items to display.
+        'limitdisplay' => 'all',
+        'numitems' => 10,
+        // Metadata columns to display.
+        'columns' => array('cid', 'filename', 'filesize', 'uid'),
+        // Show Hidden: checkbox determining whether to include 'hidden' files
+        // 'showhidden' => FALSE, // TODO: not yet implemented.
+        // Sort By: default property to use for sorting.
+        'sortby' => 'cid',
+        // Sort Order: ASC or DESC.
+        'sortorder' => 'asc',
+        // Group by: none, extension - render individual table by group.
+        // 'groupby' => 'extension', // TODO: not yet implemented
+        // Allow alter: Allow other modules to alter data before rendering.
+        'allow_alter' => TRUE,
+        // Display Filter: Output the dynamic display filter, allowing users to
+        // filter based on file types.
+        'displayfilter' => FALSE,
+      ),
+    ),
   );
 }
 
 /**
+ * Implements hook_field_formatter_settings_form().
+ *
+ * Contains the settings form for the issue_files_summary_table formatter.
+ */
+function project_issue_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
+  if ($instance['display'][$view_mode]['type'] == 'issue_files_summary_table') {
+    $display = $instance['display'][$view_mode];
+    $settings = $display['settings'];
+    $widget_settings = $field['settings'];
+
+    $options = array(
+      'all' => t('Include all files'),
+      'bytype' => t('Only include files matching certain file extensions'),
+    );
+    $form['filetypes'] = array(
+      '#type' => 'radios',
+      '#title' => t('Files to include:'),
+      '#options' => $options,
+      '#default_value' => $settings['filetypes'],
+    );
+
+    $form['extensions'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Extension list:'),
+      '#description' => t('Comma separated list of file extensions to include.'),
+      '#default_value' => $settings['extensions'],
+      '#states' => array(
+        'visible' => array(
+          ':input[name="fields[field_issue_files][settings_edit_form][settings][filetypes]"]' => array('value' => 'bytype'),
+         ),
+       ),
+    );
+
+    // TODO: The number of items to display code is working, but I don't like
+    // the overlap between 'show hidden files' and 'restrict table to x rows'
+    // functionality.  Commenting out for now.
+    /*
+    // Number of items to display
+    $options = array('limit' => t('Limit display to a maximum number of items'), 'all' => t('Display all items'));
+    $form['limitdisplay'] = array(
+      '#type' => 'radios',
+      '#title' => 'Number of items to display:',
+      '#options' => $options,
+      '#default_value' => $settings['limitdisplay'],
+    );
+    $form['numitems'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Number of items to display:'),
+      '#default_value' => $settings['numitems'],
+      '#states' => array(
+        'visible' => array(
+          ':input[name="fields[field_issue_files][settings_edit_form][settings][limitdisplay]"]' => array('value' => 'limit'),
+        ),
+      ),
+    );
+    */
+    $options = project_issue_field_formatter_view_files_metadata();
+    // Display columns checkboxes
+    $form['columns'] = array(
+      '#type' => 'select',
+      '#title' => t('Display columns:'),
+      '#options' => $options,
+      '#default_value' => $settings['columns'],
+      '#size' => min(6, count($options)),
+      '#multiple' => TRUE,
+    );
+    $form['showhidden'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Display files which have been marked as "hidden"'),
+      '#default_value' => $settings['showhidden'],
+    );
+    $form['sortby'] = array(
+      '#type' => 'select',
+      '#title' => t('Sort by:'),
+      // Remove 'display' and 'status' from the sort options.
+      '#options' => array_diff_key($options, array_flip(array('display', 'status'))),
+      '#default_value' => $settings['sortby'],
+    );
+    $form['sortorder'] = array(
+      '#type' => 'radios',
+      '#title' => t('Sort order:'),
+      '#options' => array('asc' => t('Ascending'), 'desc' => t('Descending')),
+      '#default_value' => $settings['sortorder'],
+    );
+    // TODO: Do we want to be able to display patches and screenshots
+    // separately? Or is this 'feature overkill'?  The same could be
+    // accomplished with a send instance of the field on the node.
+    // $form['groupby'] = array(
+    //   '#type' => 'checkbox',
+    //   '#title' => t('Group into separate tables by type.'),
+    //   '#default_value' => $settings['groupby'],
+    // );
+    $form['allow_alter'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Allow other modules to alter data prior to rendering the table.'),
+      '#default_value' => $settings['allow_alter'],
+    );
+  }
+  return $form;
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary().
+ *
+ * Contains the settings summary for the issue_files_summary_table formatter.
+ */
+function project_issue_field_formatter_settings_summary($field, $instance, $view_mode) {
+  $metadata = project_issue_field_formatter_view_files_metadata();
+  $display = $instance['display'][$view_mode];
+  $settings = $display['settings'];
+  $summary = '';
+  if ($display['type'] == 'issue_files_summary_table') {
+    if ($settings['filetypes'] === 'all') {
+      $summary = t('Listing all file types ');
+    }
+    else {
+      $summary = t("Listing files with extension (@extensions) ", array('@extensions' => $settings['extensions']));
+    }
+    $summary .= t("sorted by @sorttype ", array('@sorttype' => strtolower($metadata[$settings['sortby']])));
+    $summary .= $settings['sortorder'] === 'desc' ? t("descending") : t("ascending");
+    $summary .= '<br />';
+    $summary .= t("Columns: @columns", array('@columns' => implode(',', array_intersect_key($metadata, array_flip($settings['columns'])))));
+  }
+  return $summary;
+}
+
+/**
  * Implements hook_field_formatter_view().
  */
 function project_issue_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
-  $elements = array();
+  switch ($display['type']) {
+    case 'issue_id':
+    case 'issue_id_assigned':
+      $elements = project_issue_field_formatter_view_issue($entity_type, $entity, $field, $instance, $langcode, $items, $display);
+      break;
+
+    case 'issue_files_summary_table':
+      $elements = project_issue_field_formatter_view_files($entity_type, $entity, $field, $instance, $langcode, $items, $display);
+      break;
 
+  }
+  return $elements;
+}
+
+/**
+ * Renders the issue_id formatters for project_issue_field_formatter_view().
+ */
+function project_issue_field_formatter_view_issue($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+  $elements = array();
   $settings = $display['settings'];
+
   $include_assigned = $display['type'] == 'issue_id_assigned' ? TRUE : FALSE;
 
   $target_type = $field['settings']['target_type'];
@@ -1784,11 +1960,234 @@ function project_issue_field_formatter_view($entity_type, $entity, $field, $inst
       '#markup' => $output,
     );
   }
-
   return $elements;
 }
 
 /**
+ * Renders the file table formatter for project_issue_field_formatter_view().
+ */
+function project_issue_field_formatter_view_files($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+
+  $settings = $display['settings'];
+  $metadata = project_issue_field_formatter_view_files_metadata();
+
+  // Retrieve the file -> cid mappings for the node.
+  $file_cids = project_issue_get_file_cids($entity->nid);
+
+  // If filtering by extension, retrieve the allowed extension types.
+  $filter = FALSE;
+  if ($settings['filetypes'] == 'bytype') {
+    $filter = array_filter(array_map('trim', explode(',', $settings['extensions'])));
+  }
+
+  foreach ($items as $key => $value) {
+    // Add an 'extension' property to the array.
+    $items[$key]['extension'] = pathinfo($value['filename'], PATHINFO_EXTENSION);
+    // If filtering by extension, exclude items which do not meet the criteria.
+    if ($filter && !in_array($items[$key]['extension'], $filter)) {
+      $items[$key]['exclude'] = TRUE;
+    }
+    // Add the comment id associated with each file to the $items array.
+    // If no comment id is found for a given file, we set it to '0'.
+    $items[$key]['cid'] = isset($file_cids[$value['fid']]) ? $file_cids[$value['fid']] : 0;
+  }
+
+  // Sort the $items array.
+  project_issue_field_formatter_view_files_sort($items, $settings['sortby'], $settings['sortorder']);
+
+  // Assemble the default (i.e. pre drupal_alter()) table header.
+  foreach ($settings['columns'] as $key => $column) {
+    $header[$column] = array(
+      'data' => $metadata[$column],
+      'name' => 'issue-file-summary-table-header-' . $column,
+      'class' => array('issue-file-summary-table-header'),
+    );
+  }
+
+  // Assemble the default (i.e. pre drupal_alter()) table rows.
+  $rows = array();
+  foreach ($items as $item) {
+    // Assemble the individual cells.
+    $row = array();
+    foreach ($settings['columns'] as $column) {
+      switch ($column) {
+        case 'cid':
+          if ($item['cid'] === 0) {
+            $data = l('none', 'node/' . $entity->nid, array('fragment' => 'content'));
+          }
+          else {
+            $data = l('#' . comment_get_display_ordinal($item['cid'], $entity->type), 'node/' . $entity->nid, array('fragment' => 'comment-' . $item['cid']));
+          }
+          break;
+
+        case 'filename':
+          $data = theme('file_link', array('file' => (object) $item));
+          break;
+
+        case 'filesize':
+          $data = format_size($item['filesize']);
+          break;
+
+        case 'uid':
+          $data = theme('username', array('account' => user_load($item['uid'])));
+          break;
+
+        default:
+          $data = check_plain($item[$column]);
+          break;
+
+      }
+      $row[$column] = array(
+        'data' => $data,
+        'name' => 'issue-file-summary-table-' . $column . '-' . $item['fid'],
+        'class' => 'issue-file-summary-table-' . $column,
+      );
+    }
+
+    $class = array('issue-file-summary-table-row');
+
+    // Hide the row if necessary:
+    // Enforce the file's display property.
+    if (empty($item['display'])) {
+      $class[] = 'hidden-property';
+      if (!$settings['showhidden']) {
+        $class[] = 'element-hidden';
+      }
+    }
+    // Check for excluded file extensions (if configured).
+    if (!empty($item['exclude'])) {
+      $class[] = 'hidden-extension element-hidden';
+    }
+
+    // Add the row
+    $rows[] = array(
+      'data' => $row,
+      'class' => $class,
+      'name' => 'issue-file-summary-table-row-' . $item['fid'],
+    );
+  }
+
+  // Allow other modules to alter the table.
+  if ($settings['allow_alter']) {
+    $context = array(
+      'items' => $items,
+      'field' => $field,
+      'instance' => $instance,
+      'entity' => $entity,
+      'entity_type' => $entity_type,
+    );
+    drupal_alter('project_issue_files_summary_table_items', $rows, $header, $context);
+  }
+
+  $element[] = array(
+    '#theme' => 'table',
+    '#header' => $header,
+    '#rows' => $rows,
+  );
+
+  return $element;
+}
+
+/**
+ * Sorts the field formatter's items array.
+ */
+function project_issue_field_formatter_view_files_sort(&$items, $on, $order = 'ASC') {
+  if (in_array($on, array('cid', 'fid', 'filesize', 'timestamp'))) {
+    $function = "(\$a['{$on}'] > \$b['{$on}']);";
+  }
+  else {
+    $function = "strcmp(\$a['{$on}'],\$b['{$on}']);";
+  }
+  $comparer = ($order === 'desc') ? "return -" . $function : "return " . $function;
+  usort($items, create_function('$a,$b', $comparer));
+}
+
+/**
+ * Returns default available issue_files_summary_table formatter metadata.
+ */
+function project_issue_field_formatter_view_files_metadata() {
+  return array(
+    'cid' => t('Comment ID'),
+    'fid' => t('File ID'),
+    'filename' => t('File Name'),
+    'description' => t('File Description'),
+    'extension' => t('File Extension'),
+    'filesize' => t('File Size'),
+    'timestamp' => t('Creation timestamp'),
+    'uid' => t('File Author'),
+    'uri' => t('File URI'),
+    'filemime' => t('File Mime Type'),
+    'display' => t('File Display'),
+    'status' => t('File Status'),
+  );
+}
+
+/**
+ * Assemble an array of comment ids for each $node->file_issue_files file.
+ *
+ * @param integer $nid
+ *   The node ID to inspect for files.
+ *
+ * @return array $cids
+ *   An array of files found in the node's field_issue_files field, keyed by
+ *   the file ID (fid), where the value is the associated comment generated
+ *   when that file was added to the node.
+ */
+function project_issue_get_file_cids($nid) {
+
+  // Obtain list of cids for this node.
+  $query = db_select('comment', 'c');
+  $query->addField('c', 'cid');
+  $query->condition('c.nid', $nid)
+        ->addTag('node_access')
+        ->addTag('comment_filter')
+        ->orderBy('c.cid', 'ASC');
+  $cids = $query->execute()->fetchCol();
+
+  // Load each comment which contains a change to the field_issue_files field.
+  $query = new EntityFieldQuery();
+  $query->entityCondition('entity_type', 'comment')
+    ->entityCondition('bundle', 'comment_node_project_issue')
+    ->entityCondition('entity_id', $cids, 'IN')
+    ->propertyCondition('nid', $nid)
+    ->propertyCondition('status', 1)
+    ->fieldCondition('field_issue_changes', 'field_name', 'field_issue_files', '=')
+    ->propertyOrderBy('cid', 'ASC');
+  $result = $query->execute();
+
+  $comments = array();
+  if (isset($result['comment'])) {
+    $cids = array_keys($result['comment']);
+    $comments = entity_load('comment', $cids);
+  }
+
+  // Initialize an array for the results.
+  $cids = array();
+  // Iterate over the comments
+  foreach ($comments as $comment) {
+    $language = !empty($comment->language) ? $comment->language : 'und';
+    // Iterate over the changes, isolating those related to field_issue_files.
+    foreach ($comment->field_issue_changes[$language] as $change) {
+      if ($change['field_name'] == 'field_issue_files') {
+        // Create array of 'fid' => 'cid' for all file values which existed
+        // after this particular change.
+        $added = array();
+        foreach ($change['new_value'] as $file) {
+          $added[$file['fid']] = $comment->cid;
+        }
+        // Remove any array values for file entries which existed before this
+        // particular change.
+        foreach ($change['old_value'] as $file) {
+          unset($added[$file['fid']]);
+        }
+        $cids = $cids + $added;
+      }
+    }
+  }
+  return $cids;
+}
+
+/**
  * Find the flag name for following issues for this issue type (if any).
  *
  * If the flag tracker module (http://drupal.org/project/flag_tracker) is enabled,
