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 file uploads to a new location.

The files will be added to the {file_managed} table as temporary files. Temporary files are periodically cleaned. Use the 'file.usage' service to register the usage of the file which will automatically mark it as permanent.


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

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

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

null|int $delta: (optional) The delta of the file to return the file entity. Defaults to NULL.

int $replace: (optional) The replace behavior when the destination file already exists. Possible values include:

Return value

array|\Drupal\file\FileInterface|null|false An array of file entities or a single file entity if $delta != NULL. Each array element contains the file entity if the upload succeeded or FALSE if there was an error. Function returns NULL if no file was uploaded.

7 calls to file_save_upload()
FileTestForm::submitForm in core/modules/file/tests/file_test/src/Form/FileTestForm.php
Form submission handler.
file_managed_file_save_upload in core/modules/file/file.module
Saves any files that have been uploaded into a managed_file element.
ImportForm::validateForm in core/modules/locale/src/Form/ImportForm.php
Form validation handler.
OpmlFeedAdd::submitForm in core/modules/aggregator/src/Form/OpmlFeedAdd.php
Form submission handler.
QuickEditImageController::upload in core/modules/image/src/Controller/QuickEditImageController.php
Returns JSON representing the new file upload, or validation errors.

... See full list


core/modules/file/file.module, line 714
Defines a "managed_file" Form API field and a "file" field for Field module.


function file_save_upload($form_field_name, $validators = [], $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME) {
  $user = \Drupal::currentUser();
  static $upload_cache;

  $all_files = \Drupal::request()->files->get('files', []);
  // Make sure there's an upload to process.
  if (empty($all_files[$form_field_name])) {
    return NULL;
  $file_upload = $all_files[$form_field_name];

  // 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])) {
    if (isset($delta)) {
      return $upload_cache[$form_field_name][$delta];
    return $upload_cache[$form_field_name];

  // Prepare uploaded files info. Representation is slightly different
  // for multiple uploads and we fix that here.
  $uploaded_files = $file_upload;
  if (!is_array($file_upload)) {
    $uploaded_files = [$file_upload];

  $files = [];
  foreach ($uploaded_files as $i => $file_info) {
    // Check for file upload errors and return FALSE for this file if a lower
    // level system error occurred. For a complete list of errors:
    // See http://php.net/manual/features.file-upload.errors.php.
    switch ($file_info->getError()) {
        drupal_set_message(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', ['%file' => $file_info->getFilename(), '%maxsize' => format_size(file_upload_max_size())]), 'error');
        $files[$i] = FALSE;

      case UPLOAD_ERR_NO_FILE:
        drupal_set_message(t('The file %file could not be saved because the upload did not complete.', ['%file' => $file_info->getFilename()]), 'error');
        $files[$i] = 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($file_info->getRealPath())) {

        // Unknown error
        drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', ['%file' => $file_info->getFilename()]), 'error');
        $files[$i] = FALSE;

    // Begin building file entity.
    $values = [
      'uid' => $user->id(),
      'status' => 0,
      'filename' => $file_info->getClientOriginalName(),
      'uri' => $file_info->getRealPath(),
      'filesize' => $file_info->getSize(),
    $values['filemime'] = \Drupal::service('file.mime_type.guesser')->guess($values['filename']);
    $file = File::create($values);

    $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'] = [];
      $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->setFilename(file_munge_filename($file->getFilename(), $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 (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) {
      // The destination filename will also later be used to create the URI.
      $file->setFilename($file->getFilename() . '.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.', ['%filename' => $file->getFilename()]));

    // 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 (!file_stream_wrapper_valid_scheme($destination_scheme)) {
      drupal_set_message(t('The file could not be uploaded because the destination %destination is invalid.', ['%destination' => $destination]), 'error');
      $files[$i] = FALSE;

    $file->source = $form_field_name;
    // A file URI may already have a trailing slash or look like "public://".
    if (substr($destination, -1) != '/') {
      $destination .= '/';
    $file->destination = file_destination($destination . $file->getFilename(), $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.', ['%source' => $form_field_name, '%directory' => $destination]), 'error');
      $files[$i] = FALSE;

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

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

    // Check for errors.
    if (!empty($errors)) {
      $message = [
        'error' => [
          '#markup' => t('The specified file %name could not be uploaded.', ['%name' => $file->getFilename()]),
        'item_list' => [
          '#theme' => 'item_list',
          '#items' => $errors,
      // @todo Add support for render arrays in drupal_set_message()? See
      //  https://www.drupal.org/node/2505497.
      drupal_set_message(\Drupal::service('renderer')->renderPlain($message), 'error');
      $files[$i] = FALSE;

    // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
    // directory. This overcomes open_basedir restrictions for future file
    // operations.
    if (!drupal_move_uploaded_file($file_info->getRealPath(), $file->getFileUri())) {
      drupal_set_message(t('File upload error. Could not move uploaded file.'), 'error');
      \Drupal::logger('file')->notice('Upload error. Could not move uploaded file %file to destination %destination.', ['%file' => $file->getFilename(), '%destination' => $file->getFileUri()]);
      $files[$i] = FALSE;

    // Set the permissions on the new file.

    // If we are replacing an existing file re-use its database record.
    // @todo Do not create a new entity in order to update it. See
    //   https://www.drupal.org/node/2241865.
    if ($replace == FILE_EXISTS_REPLACE) {
      $existing_files = entity_load_multiple_by_properties('file', ['uri' => $file->getFileUri()]);
      if (count($existing_files)) {
        $existing = reset($existing_files);
        $file->fid = $existing->id();

    // If we made it this far it's safe to record this file in the database.
    $files[$i] = $file;
    // Allow an anonymous user who creates a non-public file to see it. See
    // \Drupal\file\FileAccessControlHandler::checkAccess().
    if ($user->isAnonymous() && $destination_scheme !== 'public') {
      $session = \Drupal::request()->getSession();
      $allowed_temp_files = $session->get('anonymous_allowed_file_ids', []);
      $allowed_temp_files[$file->id()] = $file->id();
      $session->set('anonymous_allowed_file_ids', $allowed_temp_files);

  // Add files to the cache.
  $upload_cache[$form_field_name] = $files;

  return isset($delta) ? $files[$delta] : $files;