diff --git a/core/includes/file.inc b/core/includes/file.inc
index ed4bdce..8f838e2 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -1380,6 +1380,8 @@ function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) {
  *
  * @param $source
  *   A string specifying the filepath or URI of the uploaded file to save.
+ * @param $delta
+ *   The delta of the file being uploaded. Used in conjunction with #multiple.
  * @param $validators
  *   An optional, associative array of callback functions used to validate the
  *   file. See file_validate() for a full discussion of the array format.
@@ -1410,52 +1412,52 @@ function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) {
  *   - source: Path to the file before it is moved.
  *   - destination: Path to the file after it is moved (same as 'uri').
  */
-function file_save_upload($source, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
+function file_save_upload($source, $delta = 0, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
   global $user;
   static $upload_cache;
 
   // Return cached objects without processing since the file will have
   // already been processed and the paths in _FILES will be invalid.
-  if (isset($upload_cache[$source])) {
-    return $upload_cache[$source];
+  if (isset($upload_cache[$source][$delta])) {
+    return $upload_cache[$source][$delta];
   }
 
   // Make sure there's an upload to process.
-  if (empty($_FILES['files']['name'][$source])) {
+  if (empty($_FILES['files']['name'][$source][$delta])) {
     return NULL;
   }
 
   // Check for file upload errors and return FALSE if a lower level system
   // error occurred. For a complete list of errors:
   // See http://php.net/manual/features.file-upload.errors.php.
-  switch ($_FILES['files']['error'][$source]) {
+  switch ($_FILES['files']['error'][$source][$delta]) {
     case UPLOAD_ERR_INI_SIZE:
     case UPLOAD_ERR_FORM_SIZE:
-      drupal_set_message(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $_FILES['files']['name'][$source], '%maxsize' => format_size(file_upload_max_size()))), 'error');
+      drupal_set_message(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $_FILES['files']['name'][$source][$delta], '%maxsize' => format_size(file_upload_max_size()))), 'error');
       return FALSE;
 
     case UPLOAD_ERR_PARTIAL:
     case UPLOAD_ERR_NO_FILE:
-      drupal_set_message(t('The file %file could not be saved because the upload did not complete.', array('%file' => $_FILES['files']['name'][$source])), 'error');
+      drupal_set_message(t('The file %file could not be saved because the upload did not complete.', array('%file' => $_FILES['files']['name'][$source][$delta])), 'error');
       return FALSE;
 
     case UPLOAD_ERR_OK:
       // Final check that this is a valid upload, if it isn't, use the
       // default error handler.
-      if (is_uploaded_file($_FILES['files']['tmp_name'][$source])) {
+      if (is_uploaded_file($_FILES['files']['tmp_name'][$source][$delta])) {
         break;
       }
 
     // Unknown error
     default:
-      drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $_FILES['files']['name'][$source])), 'error');
+      drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $_FILES['files']['name'][$source][$delta])), 'error');
       return FALSE;
   }
   // Begin building file entity.
   $values = array(
     'uid' => $user->uid,
     'status' => 0,
-    'filename' => trim(drupal_basename($_FILES['files']['name'][$source]), '.'),
+    'filename' => trim(drupal_basename($_FILES['files']['name'][$source][$delta]), '.'),
     'uri' => $_FILES['files']['tmp_name'][$source],
     'filesize' => $_FILES['files']['size'][$source],
   );
@@ -1553,7 +1555,7 @@ function file_save_upload($source, $validators = array(), $destination = FALSE,
   // directory. This overcomes open_basedir restrictions for future file
   // operations.
   $file->uri = $file->destination;
-  if (!drupal_move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->uri)) {
+  if (!drupal_move_uploaded_file($_FILES['files']['tmp_name'][$source][$delta], $file->uri)) {
     form_set_error($source, t('File upload error. Could not move uploaded file.'));
     watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->uri));
     return FALSE;
@@ -1574,7 +1576,7 @@ function file_save_upload($source, $validators = array(), $destination = FALSE,
   // If we made it this far it's safe to record this file in the database.
   $file->save();
   // Add file to the cache.
-  $upload_cache[$source] = $file;
+    $upload_cache[$source][$delta] = $file;
   return $file;
 }
 
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 362d0d5..0a8ca78 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -4356,6 +4356,17 @@ function theme_file($variables) {
 }
 
 /**
+ * Processes a file upload element, make use of #multiple if present.
+ */
+function form_process_file($element) {
+  if ($element['#multiple'] == TRUE) {
+    $element['#attributes'] = array('multiple' => 'multiple');
+  }
+  $element['#name'] .= '[]';
+  return $element;
+}
+
+/**
  * Returns HTML for a form element.
  *
  * Each form element is wrapped in a DIV container having the following CSS
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index d2f6f76..d77479b 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -76,6 +76,7 @@ function file_element_info() {
     '#upload_validators' => array(),
     '#upload_location' => NULL,
     '#size' => 22,
+    '#multiple' => FALSE,
     '#extended' => FALSE,
     '#attached' => array(
       'css' => array($file_path . '/file.admin.css'),
@@ -370,11 +371,14 @@ function file_file_predelete(File $file) {
  * This function is assigned as a #process callback in file_element_info().
  */
 function file_managed_file_process($element, &$form_state, $form) {
-  $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : 0;
+  // This is used some times so let's implode it just once.
+  $parents_prefix = implode('_', $element['#parents']);
+
+  $fids = isset($element['#value']['fids']) ? $element['#value']['fids'] : array();
 
   // Set some default element properties.
   $element['#progress_indicator'] = empty($element['#progress_indicator']) ? 'none' : $element['#progress_indicator'];
-  $element['#file'] = $fid ? file_load($fid) : FALSE;
+  $element['#files'] = !empty($fids) ? file_load_multiple($fids) : FALSE;
   $element['#tree'] = TRUE;
 
   $ajax_settings = array(
@@ -389,7 +393,7 @@ function file_managed_file_process($element, &$form_state, $form) {
 
   // Set up the buttons first since we need to check if they were clicked.
   $element['upload_button'] = array(
-    '#name' => implode('_', $element['#parents']) . '_upload_button',
+    '#name' => $parents_prefix . '_upload_button',
     '#type' => 'submit',
     '#value' => t('Upload'),
     '#validate' => array(),
@@ -398,26 +402,26 @@ function file_managed_file_process($element, &$form_state, $form) {
     '#ajax' => $ajax_settings,
     '#weight' => -5,
   );
-
-  // Force the progress indicator for the remove button to be either 'none' or
-  // 'throbber', even if the upload button is using something else.
-  $ajax_settings['progress']['type'] = ($element['#progress_indicator'] == 'none') ? 'none' : 'throbber';
-  $ajax_settings['progress']['message'] = NULL;
-  $ajax_settings['effect'] = 'none';
   $element['remove_button'] = array(
-    '#name' => implode('_', $element['#parents']) . '_remove_button',
+    '#name' => $parents_prefix . '_remove_button',
     '#type' => 'submit',
-    '#value' => t('Remove'),
+    '#value' => $element['#multiple'] ? t('Remove selected') : t('Remove'),
     '#validate' => array(),
     '#submit' => array('file_managed_file_submit'),
     '#limit_validation_errors' => array($element['#parents']),
     '#ajax' => $ajax_settings,
-    '#weight' => -5,
+    '#weight' => 1,
   );
 
-  $element['fid'] = array(
+  // Force the progress indicator for the remove button to be either 'none' or
+  // 'throbber', even if the upload button is using something else.
+  $ajax_settings['progress']['type'] = ($element['#progress_indicator'] == 'none') ? 'none' : 'throbber';
+  $ajax_settings['progress']['message'] = NULL;
+  $ajax_settings['effect'] = 'none';
+
+  $element['fids'] = array(
     '#type' => 'hidden',
-    '#value' => $fid,
+    '#value' => $fids,
   );
 
   // Add progress bar support to the upload if possible.
@@ -451,21 +455,32 @@ function file_managed_file_process($element, &$form_state, $form) {
 
   // The file upload field itself.
   $element['upload'] = array(
-    '#name' => 'files[' . implode('_', $element['#parents']) . ']',
+    '#name' => 'files[' . $parents_prefix . ']',
     '#type' => 'file',
     '#title' => t('Choose a file'),
     '#title_display' => 'invisible',
     '#size' => $element['#size'],
+    '#multiple' => $element['#multiple'],
     '#theme_wrappers' => array(),
     '#weight' => -10,
   );
 
-  if ($fid && $element['#file']) {
-    $element['filename'] = array(
-      '#type' => 'markup',
-      '#markup' => theme('file_link', array('file' => $element['#file'])) . ' ',
-      '#weight' => -10,
-    );
+  if (!empty($fids) && $element['#files']) {
+    foreach ($element['#files'] as $delta => $file) {
+      if ($element['#multiple']) {
+        $element['file_' . $delta]['selected'] = array(
+          '#type' => 'checkbox',
+          '#title' => theme('file_link', array('file' => $file)) . ' ',
+        );
+      }
+      else {
+        $element['file_' . $delta]['filename'] = array(
+          '#type' => 'markup',
+          '#markup' => theme('file_link', array('file' => $file)) . ' ',
+          '#weight' => -10,
+        );
+      }
+    }
   }
 
   // Add the extension list to the page as JavaScript settings.
@@ -492,28 +507,30 @@ function file_managed_file_process($element, &$form_state, $form) {
  * This function is assigned as a #value_callback in file_element_info().
  */
 function file_managed_file_value(&$element, $input = FALSE, $form_state = NULL) {
-  $fid = 0;
-
-  // Find the current value of this field from the form state.
-  $form_state_fid = $form_state['values'];
-  foreach ($element['#parents'] as $parent) {
-    $form_state_fid = isset($form_state_fid[$parent]) ? $form_state_fid[$parent] : 0;
-  }
-
-  if ($element['#extended'] && isset($form_state_fid['fid'])) {
-    $fid = $form_state_fid['fid'];
-  }
-  elseif (is_numeric($form_state_fid)) {
-    $fid = $form_state_fid;
-  }
+  $fids = array();
+
+  // Find the current value of this field.
+  $fids = !empty($input['fids']) ? explode(' ', $input['fids']) : array();
+  $fids = array_map(
+    function($item) {
+      return (int) $item;
+    },
+    $fids
+  );
+  $input['fids'] = $fids;
 
   // Process any input and save new uploads.
   if ($input !== FALSE) {
     $return = $input;
 
     // Uploads take priority over all other values.
-    if ($file = file_managed_file_save_upload($element)) {
-      $fid = $file->fid;
+    if ($files = file_managed_file_save_upload($element)) {
+      if ($element['#multiple']) {
+        $fids = array_merge($fids, array_keys($files));
+      }
+      else {
+        $fids = array_keys($files);
+      }
     }
     else {
       // Check for #filefield_value_callback values.
@@ -525,9 +542,15 @@ function file_managed_file_value(&$element, $input = FALSE, $form_state = NULL)
           $callback($element, $input, $form_state);
         }
       }
-      // Load file if the FID has changed to confirm it exists.
-      if (isset($input['fid']) && $file = file_load($input['fid'])) {
-        $fid = $file->fid;
+
+      // Load files if the FIDs has changed to confirm they exist.
+      if (!empty($input['fids'])) {
+        $fids = array();
+        foreach ($input['fids'] as $key => $fid) {
+          if ($file = file_load($fid)) {
+            $fids[] = $file->fid;
+          }
+        }
       }
     }
   }
@@ -535,22 +558,26 @@ function file_managed_file_value(&$element, $input = FALSE, $form_state = NULL)
   // If there is no input, set the default value.
   else {
     if ($element['#extended']) {
-      $default_fid = isset($element['#default_value']['fid']) ? $element['#default_value']['fid'] : 0;
-      $return = isset($element['#default_value']) ? $element['#default_value'] : array('fid' => 0);
+      $default_fids = isset($element['#default_value']['fids']) ? $element['#default_value']['fids'] : array();
+      $return = isset($element['#default_value']) ? $element['#default_value'] : array('fids' => $default_fids);
     }
     else {
-      $default_fid = isset($element['#default_value']) ? $element['#default_value'] : 0;
-      $return = array('fid' => 0);
+      $default_fids = isset($element['#default_value']) ? $element['#default_value'] : array();
+      $return = array('fids' => array());
     }
 
     // Confirm that the file exists when used as a default value.
-    if ($default_fid && $file = file_load($default_fid)) {
-      $fid = $file->fid;
+    if (!empty($default_fids)) {
+      $fids = array();
+      foreach ($default_fids as $key => $fid) {
+        if ($file = file_load($input['fid'])) {
+          $fids[] = $file->fid;
+        }
+      }
     }
   }
 
-  $return['fid'] = $fid;
-
+  $return['fids'] = $fids;
   return $return;
 }
 
@@ -565,28 +592,47 @@ function file_managed_file_validate(&$element, &$form_state) {
   // references. This prevents unmanaged files from being deleted if this
   // item were to be deleted.
   $clicked_button = end($form_state['triggering_element']['#parents']);
-  if ($clicked_button != 'remove_button' && !empty($element['fid']['#value'])) {
-    if ($file = file_load($element['fid']['#value'])) {
-      if ($file->status == FILE_STATUS_PERMANENT) {
-        $references = file_usage_list($file);
-        if (empty($references)) {
-          form_error($element, t('The file used in the !name field may not be referenced.', array('!name' => $element['#title'])));
+  if ($clicked_button != 'remove_button' && !empty($element['fids']['#value'])) {
+    $fids = is_array($element['fids']['#value']) ? $element['fids']['#value'] : explode(' ', $element['fids']['#value']);
+    foreach ($fids as $fid) {
+      if ($file = file_load($fid)) {
+        if ($file->status == FILE_STATUS_PERMANENT) {
+          $references = file_usage_list($file);
+          if (empty($references)) {
+            form_error($element, t('The file used in the !name field may not be referenced.', array('!name' => $element['#title'])));
+          }
         }
       }
-    }
-    else {
-      form_error($element, t('The file referenced by the !name field does not exist.', array('!name' => $element['#title'])));
+      else {
+        form_error($element, t('The file referenced by the !name field does not exist.', array('!name' => $element['#title'])));
+      }
     }
   }
 
   // Check required property based on the FID.
-  if ($element['#required'] && empty($element['fid']['#value']) && !in_array($clicked_button, array('upload_button', 'remove_button'))) {
+  if ($element['#required'] && empty($element['fids']['#value']) && !in_array($clicked_button, array('upload_button', 'remove_button'))) {
     form_error($element['upload'], t('!name field is required.', array('!name' => $element['#title'])));
   }
 
-  // Consolidate the array value of this field to a single FID.
+  // Save entire values to storage
+  $values = drupal_array_get_nested_value($form_state['values'], $element['#array_parents']);
+  drupal_array_set_nested_value($form_state['storage']['managed_file_values'], $element['#array_parents'], $values);
+
+  // Add 'fid' to values for backward compatibility when not using #multiple.
+  if (!$element['#multiple']) {
+    $values = drupal_array_get_nested_value($form_state['values'], $element['#array_parents']);
+    $values['fid'] = reset($values['fids']);
+    form_set_value($element, $values, $form_state);
+  }
+
+  // Consolidate the array value of this field to array of fids.
   if (!$element['#extended']) {
-    form_set_value($element, $element['fid']['#value'], $form_state);
+    if ($element['#multiple']) {
+      form_set_value($element, $element['fids']['#value'], $form_state);
+    }
+    else {
+      form_set_value($element, $element['fids']['#value'][0], $form_state);
+    }
   }
 }
 
@@ -607,11 +653,29 @@ function file_managed_file_submit($form, &$form_state) {
   // button was clicked. Action is needed here for the remove button, because we
   // only remove a file in response to its remove button being clicked.
   if ($button_key == 'remove_button') {
-    // If it's a temporary file we can safely remove it immediately, otherwise
-    // it's up to the implementing module to remove usages of files to have them
-    // removed.
-    if ($element['#file'] && $element['#file']->status == 0) {
-      file_delete($element['#file']->fid);
+    // Get files that need to be removed from list.
+    $fids = array();
+    $values = drupal_array_get_nested_value($form_state['storage']['managed_file_values'], $parents);
+    if ($element['#multiple']) {
+      foreach ($values as $name => $value) {
+        if (strpos($name, 'file_') === 0 && $value['selected']) {
+          $fids[] = (int) substr($name, 5);
+        }
+      }
+    }
+    else {
+      $fids = $values['fids'];
+    }
+
+    array_diff($values['fids'], $fids);
+
+    foreach ($fids as $fid) {
+      // If it's a temporary file we can safely remove it immediately, otherwise
+      // it's up to the implementing module to remove usages of files to have them
+      // removed.
+      if ($element['#files'][$fid] && $element['#files'][$fid]->status == 0) {
+        file_delete($element['#files'][$fid]->fid);
+      }
     }
     // Update both $form_state['values'] and $form_state['input'] to reflect
     // that the file has been removed, so that the form is rebuilt correctly.
@@ -620,9 +684,9 @@ function file_managed_file_submit($form, &$form_state) {
     // when the managed_file element is part of a field widget.
     // $form_state['input'] must be updated so that file_managed_file_value()
     // has correct information during the rebuild.
-    $values_element = $element['#extended'] ? $element['fid'] : $element;
-    form_set_value($values_element, NULL, $form_state);
-    drupal_array_set_nested_value($form_state['input'], $values_element['#parents'], NULL);
+    $values_element = $element['#extended'] ? $element['fids'] : $element;
+    form_set_value($values_element, $values['fids'], $form_state);
+    drupal_array_set_nested_value($form_state['input'], $values_element['#parents'], implode(' ', $values['fids']));
   }
 
   // Set the form to rebuild so that $form is correctly updated in response to
@@ -657,13 +721,19 @@ function file_managed_file_save_upload($element) {
     return FALSE;
   }
 
-  if (!$file = file_save_upload($upload_name, $element['#upload_validators'], $destination)) {
-    watchdog('file', 'The file upload failed. %upload', array('%upload' => $upload_name));
-    form_set_error($upload_name, t('The file in the !name field was unable to be uploaded.', array('!name' => $element['#title'])));
-    return FALSE;
+  // Loop through any attached files and save them to the database.
+  $files = array();
+  $file_count = count(array_filter($_FILES['files']['name'][$upload_name]));
+  while ($file_count > 0) {
+    $file_count--;
+    if (!$file = file_save_upload($upload_name, $file_count, $element['#upload_validators'], $destination)) {
+      watchdog('file', 'The file upload failed. %upload', array('%upload' => $upload_name));
+      form_set_error($upload_name, t('The file in the !name field was unable to be uploaded.', array('!name' => $element['#title'])));
+    }
+    $files[$file->fid] = $file;
   }
 
-  return $file;
+  return $files;
 }
 
 /**
@@ -718,9 +788,11 @@ function theme_file_managed_file($variables) {
  */
 function file_managed_file_pre_render($element) {
   // If we already have a file, we don't want to show the upload controls.
-  if (!empty($element['#value']['fid'])) {
-    $element['upload']['#access'] = FALSE;
-    $element['upload_button']['#access'] = FALSE;
+  if (!empty($element['#value']['fids'])) {
+    if (!$element['#multiple']) {
+      $element['upload']['#access'] = FALSE;
+      $element['upload_button']['#access'] = FALSE;
+    }
   }
   // If we don't already have a file, there is nothing to remove.
   else {
diff --git a/core/modules/file/tests/file_module_test.module b/core/modules/file/tests/file_module_test.module
index f61d67d..52369ce 100644
--- a/core/modules/file/tests/file_module_test.module
+++ b/core/modules/file/tests/file_module_test.module
@@ -31,7 +31,7 @@ function file_module_test_menu() {
  * @see file_module_test_form_submit()
  * @ingroup forms
  */
-function file_module_test_form($form, &$form_state, $tree = TRUE, $extended = FALSE, $default_fid = NULL) {
+function file_module_test_form($form, &$form_state, $tree = TRUE, $extended = TRUE, $default_fids = NULL) {
   $form['#tree'] = (bool) $tree;
 
   $form['nested']['file'] = array(
@@ -41,9 +41,11 @@ function file_module_test_form($form, &$form_state, $tree = TRUE, $extended = FA
     '#progress_message' => t('Please wait...'),
     '#extended' => (bool) $extended,
     '#size' => 13,
+    '#multiple' => TRUE,
   );
-  if ($default_fid) {
-    $form['nested']['file']['#default_value'] = $extended ? array('fid' => $default_fid) : $default_fid;
+  if ($default_fids) {
+    $default_fids = explode(',', $default_fids);
+    $form['nested']['file']['#default_value'] = $extended ? array('fid' => $default_fids) : $default_fids;
   }
 
   $form['textfield'] = array(
@@ -64,12 +66,28 @@ function file_module_test_form($form, &$form_state, $tree = TRUE, $extended = FA
  */
 function file_module_test_form_submit($form, &$form_state) {
   if ($form['#tree']) {
-    $fid = $form['nested']['file']['#extended'] ? $form_state['values']['nested']['file']['fid'] : $form_state['values']['nested']['file'];
+    if ($form['nested']['file']['#extended']) {
+      $fids = array();
+      foreach ($form_state['values']['nested']['file'] as $fid) {
+        $fids[] = $fid;
+      }
+    }
+    else {
+      $fids = $form_state['values']['nested']['file'];
+    }
   }
   else {
-    $fid = $form['nested']['file']['#extended'] ? $form_state['values']['file']['fid'] : $form_state['values']['file'];
+    if ($form['nested']['file']['#extended']) {
+      $fids = array();
+      foreach ($form_state['values']['file'] as $file) {
+        $fids[] = $file['fid'];
+      }
+    }
+    else {
+      $fids = $form_state['values']['file'];
+    }
   }
-  drupal_set_message(t('The file id is %fid.', array('%fid' => $fid)));
+  drupal_set_message(t('The file ids are %fid.', array('%fid' => $fid)));
 }
 
 /**
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index de7241f..6b3a02c 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -523,6 +523,8 @@ function system_element_info() {
   );
   $types['file'] = array(
     '#input' => TRUE,
+    '#multiple' => FALSE,
+    '#process' => array('form_process_file'),
     '#size' => 60,
     '#theme' => 'file',
     '#theme_wrappers' => array('form_element'),
