8.3.x file.module file_save_upload($form_field_name, $validators = [], $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME)
8.0.x file.module file_save_upload($form_field_name, $validators = array(), $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME)
8.1.x file.module file_save_upload($form_field_name, $validators = array(), $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME)
8.2.x file.module file_save_upload($form_field_name, $validators = array(), $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME)
8.4.x file.module file_save_upload($form_field_name, $validators = [], $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME)
4.6.x file.inc file_save_upload($source, $dest = 0, $replace = FILE_EXISTS_RENAME)
4.7.x file.inc file_save_upload($source, $dest = false, $replace = FILE_EXISTS_RENAME)
5.x file.inc file_save_upload($source, $dest = FALSE, $replace = FILE_EXISTS_RENAME)
6.x file.inc file_save_upload($source, $validators = array(), $dest = FALSE, $replace = FILE_EXISTS_RENAME)
7.x file.inc file_save_upload($form_field_name, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME)

Saves a file upload to a new location.

The file will be added to the {file_managed} table as a temporary file. Temporary files are periodically cleaned. To make the file a permanent file, assign the status and use file_save() to save the changes.


$form_field_name: A string that is the associative array key of the upload form element in the form array.

$validators: An optional, associative array of callback functions used to validate the file. See file_validate() for a full discussion of the array format. If no extension validator is provided it will default to a limited safe list of extensions which is as follows: "jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp". To allow all extensions you must explicitly set the 'file_validate_extensions' validator to an empty array (Beware: this is not safe and should only be allowed for trusted users, if at all).

$destination: A string containing the URI that the file should be copied to. This must be a stream wrapper URI. If this value is omitted, Drupal's temporary files scheme will be used ("temporary://").

$replace: Replace behavior when the destination file already exists:

Return value

An object containing the file information if the upload succeeded, FALSE in the event of an error, or NULL if no file was uploaded. The documentation for the "File interface" group, which you can find under Related topics, or the header at the top of this file, documents the components of a file object. In addition to the standard components, this function adds:

  • source: Path to the file before it is moved.
  • destination: Path to the file after it is moved (same as 'uri').

Related topics

7 calls to file_save_upload()
aggregator_form_opml_submit in modules/aggregator/aggregator.admin.inc
Form submission handler for aggregator_form_opml().
file_managed_file_save_upload in modules/file/file.module
Saves any files that have been uploaded into a managed_file element.
locale_translate_import_form_submit in modules/locale/locale.admin.inc
Process the locale import form submission.
system_theme_settings_validate in modules/system/system.admin.inc
Validator for the system_theme_settings() form.
update_manager_install_form_submit in modules/update/update.manager.inc
Form submission handler for update_manager_install_form().

... See full list


includes/file.inc, line 1455
API for handling file uploads and server file management.


function file_save_upload($form_field_name, $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[$form_field_name])) {
    return $upload_cache[$form_field_name];

  // Make sure there's an upload to process.
  if (empty($_FILES['files']['name'][$form_field_name])) {
    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'][$form_field_name]) {
      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'][$form_field_name], '%maxsize' => format_size(file_upload_max_size()))), 'error');
      return FALSE;

      drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => $_FILES['files']['name'][$form_field_name])), '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'][$form_field_name])) {

      // Unknown error
      drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $_FILES['files']['name'][$form_field_name])), 'error');
      return FALSE;

  // Begin building file object.
  $file = new stdClass();
  $file->uid = $user->uid;
  $file->status = 0;
  $file->filename = trim(drupal_basename($_FILES['files']['name'][$form_field_name]), '.');
  $file->uri = $_FILES['files']['tmp_name'][$form_field_name];
  $file->filemime = file_get_mimetype($file->filename);
  $file->filesize = $_FILES['files']['size'][$form_field_name];

  $extensions = '';
  if (isset($validators['file_validate_extensions'])) {
    if (isset($validators['file_validate_extensions'][0])) {
      // Build the list of non-munged extensions if the caller provided them.
      $extensions = $validators['file_validate_extensions'][0];
    else {
      // If 'file_validate_extensions' is set and the list is empty then the
      // caller wants to allow any extension. In this case we have to remove the
      // validator or else it will reject all extensions.
  else {
    // No validator was provided, so add one using the default list.
    // Build a default non-munged safe list for file_munge_filename().
    $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
    $validators['file_validate_extensions'] = array();
    $validators['file_validate_extensions'][0] = $extensions;

  if (!empty($extensions)) {
    // Munge the filename to protect against possible malicious extension hiding
    // within an unknown file type (ie: filename.html.foo).
    $file->filename = file_munge_filename($file->filename, $extensions);

  // Rename potentially executable files, to help prevent exploits (i.e. will
  // rename filename.php.foo and filename.php to filename.php.foo.txt and
  // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
  // evaluates to TRUE.
  if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
    $file->filemime = 'text/plain';
    $file->uri .= '.txt';
    $file->filename .= '.txt';
    // The .txt extension may not be in the allowed list of extensions. We have
    // to add it here or else the file upload will fail.
    if (!empty($extensions)) {
      $validators['file_validate_extensions'][0] .= ' txt';
      drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->filename)));

  // If the destination is not provided, use the temporary directory.
  if (empty($destination)) {
    $destination = 'temporary://';

  // Assert that the destination contains a valid stream.
  $destination_scheme = file_uri_scheme($destination);
  if (!$destination_scheme || !file_stream_wrapper_valid_scheme($destination_scheme)) {
    drupal_set_message(t('The file could not be uploaded, because the destination %destination is invalid.', array('%destination' => $destination)), 'error');
    return FALSE;

  $file->source = $form_field_name;
  // A URI may already have a trailing slash or look like "public://".
  if (substr($destination, -1) != '/') {
    $destination .= '/';
  $file->destination = file_destination($destination . $file->filename, $replace);
  // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and
  // there's an existing file so we need to bail.
  if ($file->destination === FALSE) {
    drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $form_field_name, '%directory' => $destination)), 'error');
    return FALSE;

  // Add in our check of the file name length.
  $validators['file_validate_name_length'] = array();

  // Call the validation functions specified by this function's caller.
  $errors = file_validate($file, $validators);

  // Check for errors.
  if (!empty($errors)) {
    $message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename));
    if (count($errors) > 1) {
      $message .= theme('item_list', array('items' => $errors));
    else {
      $message .= ' ' . array_pop($errors);
    form_set_error($form_field_name, $message);
    return FALSE;

  // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
  // directory. This overcomes open_basedir restrictions for future file
  // operations.
  $file->uri = $file->destination;
  if (!drupal_move_uploaded_file($_FILES['files']['tmp_name'][$form_field_name], $file->uri)) {
    form_set_error($form_field_name, 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;

  // Set the permissions on the new file.

  // If we are replacing an existing file re-use its database record.
  if ($replace == FILE_EXISTS_REPLACE) {
    $existing_files = file_load_multiple(array(), array('uri' => $file->uri));
    if (count($existing_files)) {
      $existing = reset($existing_files);
      $file->fid = $existing->fid;

  // If we made it this far it's safe to record this file in the database.
  if ($file = file_save($file)) {
    // Track non-public files in the session if they were uploaded by an
    // anonymous user. This allows modules such as the File module to only
    // grant view access to the specific anonymous user who uploaded the file.
    // See file_file_download().
    // The 'file_public_schema' variable is used to allow other publicly
    // accessible file schemes to be treated the same as the public:// scheme
    // provided by Drupal core and to avoid adding unnecessary data to the
    // session (and the resulting bypass of the page cache) in those cases. For
    // security reasons, only schemes that are completely publicly accessible,
    // with no download restrictions, should be added to this variable. See
    // file_managed_file_value().
    if (!$user->uid && !in_array($destination_scheme, variable_get('file_public_schema', array('public')))) {
      $_SESSION['anonymous_allowed_file_ids'][$file->fid] = $file->fid;
    // Add file to the cache.
    $upload_cache[$form_field_name] = $file;
    return $file;
  return FALSE;


wheelercreek’s picture

In case this is driving anyone else crazy, I realized in order for this function to return the object, you need to name your file field using associative array syntax, like so:

$form['img1'] = array(
    '#name' => 'files[img_1]',
    '#type' => 'file', 
    '#title' => t('Choose a picture'),
    '#size' => 22,
    '#description' => t('Upload a small image'), 

Then in your form processing function you would send the file name from within the array brackets, like so:

//handle file uploading
$validators = array(
   'file_validate_is_image' => array(),
   'file_validate_image_resolution' => array(variable_get('btn_dimensions', '124x124')), 
   'file_validate_size' => array(variable_get('btn_file_size', '5') * 1024),
//save file as temporary
$file = file_save_upload('img_1', $validators);  
JurgenR’s picture

Thnx! This was driving me crazy.

curiosity26’s picture

OMG! WHO THOUGHT TO DO IT THAT WAY!!! I was literally ripping my hair out. PHP shoots it up as $_FILES['field_name'] and file_save_upload is looking for $_FILES['files']['name']['field_name']. I was about to lose it.

Marko B’s picture

Had the same problem. Wouldn't expect this to work. Especially as I had it working in one case without this Name part

  $form['new_image']['file']= array(
    '#type' => 'file',
    '#title' => t('Image'),
    '#description' => t('Upload a file, allowed extensions: jpg, jpeg, png, gif'),

  $file = file_save_upload('file', array(
      'file_validate_is_image' => array(), // Validates file is really an image.
      'file_validate_extensions' => array('png gif jpg jpeg'), // Validate extensions.

and then in other form this doesn't work, even the form example module has an example without the name parametar. (form_example_tutorial_10)

thanx for this!

Marko B’s picture

Actually You don't need to do what was done above with #name => files[somevar]

It is enough to do the following

    $form['item']['file']= array(
      //'#name' => 'files[advert]',   
      '#type' => 'file',
      '#title' => '<h3>'.t('Add New Image').'</h3>',
      '#description' => t('Upload a file, allowed extensions: jpg, jpeg, png, gif'),

and then on submit or validate just add ITEM to file_save_upload like below

  $file = file_save_upload('item', array(
mikeo26’s picture

Also doesn't work for me...

My file is in $form['replace_textfield']['edit']['app_icon']

And I tried like everything but nothing works to really get the file... The only way I got it to work was by using $form['FILE_HERE']. But in this case I really need to upload it with a form with was generated with ajax

Anyone has a solution for this?

dx007’s picture

Work better like this:

  $file = file_save_upload('file', array()
Beau Townsend’s picture

+1 this helped me

miqmago’s picture

It works ok for $destination='private://' but not when $destination='private://subfolder'
(Tried with admin user)

move_uploaded_file(private://subfolder/img.gif) [function.move-uploaded-file]: failed to open stream: "DrupalPrivateStreamWrapper::stream_open" call failed en drupal_move_uploaded_file() (línea 1607 de ...\file.inc).

After checking drupal_realpath($uri) it returns '' in line 1603.

Subfolder exists in private://

miqmago’s picture

Solved: subfolder existet, but 'private://' was defined as '/private_html/subfolder', so the move_uploaded_file() didn't find it (it was looking for '/private_html/subfolder/subfolder')

Suggestion: could maybe the returned error be more specific? Sorry anyway for silly issue...

thedotwriter’s picture

Here's an example on how to set the status of a file to "permanent".

// Upload the file as temporary
$file = file_save_upload('myfile'); 

// Change the status
$file->status = 1;
// Update the file status into the database

carlovdb’s picture

I have created my own module based on example 10 of examples module.

How can I achieve that this overwrites the filename if he already exists instead of creating filename_0.extension

My code for this is:

function uploadcsv_validate($form, &$form_state) {
  $file = file_save_upload('file', array(
    'file_validate_extensions' => array('csv'), // Validate extensions.
  // If the file passed validation:
  if ($file) {
    // Move the file, into the Drupal file system
    if ($file = file_move($file, 'public://')) {
      // Save the file for use in the submit handler.
      $form_state['storage']['file'] = $file;
    else {
      form_set_error('file', t('Failed to write the uploaded file the site\'s file folder.'));
  else {
    form_set_error('file', t('No file was uploaded.'));
mattman’s picture

I'm posting this here not only as a reminder for myself, but for anyone else who might be storing certain images, such as logo.png or favicon.ico, within nodes. The goal is to always have the same filename within the OS. Therefore, you need FILE_EXISTS_REPLACE on file_save_upload.

In D7 we don't have a field level control in order to determine if a managed file should be replaced or incremented. This is probably an edge case, but the code may be useful to someone else.

 * Implements hook_file_validate().
 * Replacing a managed file with the same-named file.
 * This is a total hack since there is no hook_presave_upload.
 * I'm basically preempting drupal_move_uploaded_file() by moving the
 * file from /tmp to the same target destination as an existing file.
 * This is done before drupal_move_uploaded_file is called within the
 * first call to file_save_upload(). This only happens based on known
 * files (by 'filename') and if they exist within the file_managed table.
 * Note to self: It would be cool if the $file object had a new property.
 * Such as $file->replace = FILE_EXISTS_REPLACE therefore, each file
 * would be in control of whether it should replace or not and modules
 * could modify this at various stages.
function my_module_file_validate($file) {
  // Because you can upload images to a node and reference them within your theme!
  $files_to_watch = array(

  if (in_array($file->filename, $files_to_watch)) {
    // Retain the source of the file from temp location
    $source = $file->uri;

    // Get file from file_mangaged if it already exists.
    $file_managed = db_query("SELECT fid, uri FROM {file_managed} WHERE uri LIKE :filename", array(':filename' => '%' . $file->filename))->fetchAssoc();
    if (!empty($file_managed)) {
      // Setting the file id is necessary so a new file record won't be created
      $file->fid = $file_managed['fid'];
      $file->uri = $file_managed['uri'];
      $file->destination = $file->uri;
      $swap_file = file_save_upload($source, array(), $file->uri, FILE_EXISTS_REPLACE);
Wolf_22’s picture

Your code doesn't seem to work--unless I'm doing something wrong here.

1.) Is $files_to_watch used to prevent files with those names to be uploaded (attached) to a node?

2.) Every single time I try your code, my files that I try to attach keep getting numbers attached to them (i.e. - "_0"). How do I prevent this? I swear I've tried everything to no avail and really need to stop it for a particular content type.

MaudeH’s picture

I use PLupload in a custom module to upload multiple files for a gallery.

Here is an extract of my $form_state array :

[input] => Array(
    [create] => Array(
        [author] => Firstname Lastname
        [photos] => 

    [edit-create-photos_0_tmpname] => p17g2t4fvrgq3iqi1avpuqc8nk4.jpg
    [edit-create-photos_0_name] => photo_1.jpg
    [edit-create-photos_0_status] => done
    [edit-create-photos_1_tmpname] => p17g2t4fvs11b3cufd2450ge4b5.jpg
    [edit-create-photos_1_name] => photo_2.jpg
    [edit-create-photos_1_status] => done
    [edit-create-photos_count] => 2
    [op] => ...

[values] => Array(
    [create] => Array(
        [author] => Firstname Lastname
        [photos] => Array(
            [0] => Array(
                [tmppath] => temporary://p17g2t4fvrgq3iqi1avpuqc8nk4.tmp
                [tmpname] => p17g2t4fvrgq3iqi1avpuqc8nk4.tmp
                [name] => photo_1.jpg
                [status] => done

            [1] => Array(
                [tmppath] => temporary://p17g2t4fvs11b3cufd2450ge4b5.tmp
                [tmpname] => p17g2t4fvs11b3cufd2450ge4b5.tmp
                [name] => photo_2.jpg
                [status] => done


    [submit] => ...

How do I save my photos using file_save_upload() (or an other function) in my public folder with the good name (photo_1.jpg) ?

Thanks for your help,

Dave Hirschman’s picture

It is useful to think of the file_save_upload function as part of form processing.

The $source argument is NOT "A string specifying the filepath or URI of the uploaded file to save." It is actually the name of the File field in the form ('#type' => 'file',) that was used to upload the file. For example, you can see in the source code for file_save_upload that it is used to call form_set_error.

Unlike other form fields, a 'file' field does not have a value that can be accessed via $form_state['values'] in the form validation and submit functions.

I use code such as the following:

 * Generate form to upload file.
function mymodule_upload($form_state) {
  $form['upload'] = array(
    '#type' => 'file',
    '#title' => t('Upload file'),
    '#description' => t('Choose the file to upload.'),
  $form['actions'] = array('#type' => 'actions');
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Upload'),

  return $form;

 * Form validation function for upload form.
function mymodule_validate($form, &$form_state) {
  // @TODO restrict file types with something like 
  //   array('file_validate_extensions' => array('txt csv tsv')) in second argument.
  $file = file_save_upload('upload', array(), FALSE, FILE_EXISTS_REPLACE);
  if ($file) {
    // Make it a permanent file so it doesn't get deleted by cron.
    $file->status = 1;

    // Save file name for submit handler.
    $form_state['storage']['filename'] = $file->uri;
    // @TODO check file content, and if no good 
    //    form_set_error('upload', t('File content is invalid.'));
  else {
    form_set_error('upload', t('File upload failed.'));

 * Form submit function for _upload form
function mymodule_upload_submit($form, &$form_state) {
  // Get name of my file that was left by form validate function.
  $file_name = $form_state['storage']['filename'];
  // Get all the lines in the file.
  $lines = file($filename, FILE_IGNORE_NEW_LINES);

  // @TODO do something with my uploaded data.
  // Done - delete the uploaded file.
  unlink ($file_name);
Dave Hirschman’s picture

And I also see I'm supposed to file an issue about the documentation inaccuracy - will do.

guruparthi’s picture

But it gives error
"The specified file temporary://vector-illustration-of-glossy_small_5.jpg could not be moved, because the destination is invalid. More information is available in the system log."

My Code is below
function learning_upload_form($form, &$form_state) {
$form = array();
$form['file'] = array(
'#type' => 'file',
'#description' => t('upload files'),
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Upload'),
return $form;

* learning_upload_form_validate()
function learning_upload_form_validate($form, &$form_state) {
global $base_url;
$file = file_save_upload('file');
$dest = 'Public://';
if ($file) {
if ($file = file_move($file, $dest)) {
$form_state['values']['file'] = $file;
else {
form_set_error('file', t('Failed to upload files'));
else {
form_set_error('file', t('File can\' uploaded'));

* learning_upload_form_submit()
function learning_upload_form_submit($form, &$form_state) {
$file = $form_state['values']['file'];
$file->status = FILE_STATUS_PERMAMENT;
drupal_set_message(t('@file is uploaded', array('@file' => $file->filename)));

Mon28’s picture

Any way we can overwrite the destination directory such that files are uploaded on another server??? Tell me any other way to do that if you know, like a module where i could paste the URL directly.

nano_monkey’s picture

This might be what you need? You get another temp stream wrapper out of the box (you can always add more) and you can configure it to point to a remote file system https://www.drupal.org/project/alt_stream_wrappers

SeanA’s picture

About the 'status' field: "A bitmapped field indicating the status of the file. The first 8 bits are reserved for Drupal core. The least significant bit indicates temporary (0) or permanent (1). Temporary files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during cron runs." (from includes/file.inc)

Max temp file age is defined as 6 hours in modules/system/system.module:

digital_dope’s picture

If you can't find $form_field_name, what is ESSENTIAL to make this function work, just dump the $_FILES variable and you will find the correct key!

It took me some hours to find it. I hope it helps someone...