I'm creating a custom form with a file upload field that will have its contents read, processed then sent to an external API. The problem is whenever I upload the file the field for it appears to be empty in the submit handler and I cannot see why.

In this example I'm using it as an unmanaged file as I only need to process it then remove it but I have tried it as a managed file which does upload (I can see it in the directory) and has the file added to the file_managed database table. I'm not sure where unmanaged file are meant to go to check for their upload.

public function buildForm(array $form, FormStateInterface $form_state) {
    $form = [
      '#attributes' => ['enctype' => 'multipart/form-data'],

    $form['file_upload_details'] = [
      '#markup' => "<strong>" . $this->t('The KML File') . "</strong>",

    $form['my_file'] = [
      '#type' => 'file',
      '#description' => $this->t('KML format only'),
      '#upload_location' => 'public://my_files/',
      '#upload_validators' => [
        'file_validate_extensions' => ['kml'],

    $form_build = parent::buildForm($form, $form_state);

    return $form_build;
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $messenger = parent::messenger();

  $form_state->getStorage(), // Empty array
  $form_state->getValue('my_file') // Empty string
  • Have you tried managed_file instead of file?
    – Kartagis
    Oct 25 '19 at 8:13
  • 1
    I know the answer to this, but I learned it from @leymannx, so I'll let him answer it ;-) PS: It's normal for un-managed file to be empty on $form_state
    – No Sssweat
    Oct 25 '19 at 11:24
  • 1
    @NoSssweat – What answer are you referring to? I couldn't remember that myself and had to dig really deep to come up with a new answer. 😅
    – leymannx
    Oct 25 '19 at 11:56
  • 1
    @leymannx from this batch file upload
    – No Sssweat
    Oct 25 '19 at 12:17
  • 1
    @NoSssweat – Oh Jesus, you are so right! And I forgot to merge this PR where I just reminded myself on that: github.com/xurizaemon/csvimport/pull/12/… 🤣
    – leymannx
    Oct 25 '19 at 12:30

All what '#type' => 'file' form elements do is to provide a bare <input type="file"> element. You get a prompt to select a file from your machine and on form submit the browser sends a request to your server, server forwards it to PHP, PHP extracts the file from the request, PHP stores it to the server's temp dir and when the script ends it deletes it again. Not much Drupal involved.

If you need to handle the file somehow and be it just the filename you need to take care of it yourself. And that's what file_save_upload() is for. It expects the $form_field_name as first parameter. And it will return you the file information. When scanning core for '#type' => 'file' you will often see this function being used in the form validation handler. Where the return value is being passed back to $form_state. For example in \Drupal\system\Form\ThemeSettingsForm::validateForm(). Just this time the function is wrapped from _file_save_upload_from_form() to allow correct error handling in forms.

// Check for a new uploaded logo.
if (isset($form['logo'])) {
  $file = _file_save_upload_from_form($form['logo']['settings']['logo_upload'], $form_state, 0);
  if ($file) {
    // Put the temporary file in form_values so we can save it on submit.
    $form_state->setValue('logo_upload', $file);

I have a custom module to import CSV. This works for me.

$form['#attributes'] = [
  'enctype' => 'multipart/form-data',
  'class' => ['csv-import-form', 'mb-50'],

$form['csv_file'] = [
  '#type' => 'file',
  '#title' => t('CSV File'),
  '#description' => $this->t('Upload file here.'),
  '#element_validate' => ['::csv_import_validate_file_upload'],

public static function csv_import_validate_file_upload(&$element, FormStateInterface $form_state, &$complete_form) {

$validators = [
  'file_validate_extensions' => ['csv CSV'],

if ($file = file_save_upload('csv_file', $validators, FALSE, 0, \Drupal\Core\File\FileSystemInterface::EXISTS_REPLACE)) {

  $csv_dir          = 'public://my_files/';
  $directory_exists = \Drupal::service('file_system')->prepareDirectory($csv_dir);

    \Drupal::service('file_system')->prepareDirectory($csv_dir, \Drupal\Core\File\FileSystemInterface::CREATE_DIRECTORY);

  if ($directory_exists) {
    $destination = $csv_dir . '/' . $file->getFilename();

    if (file_copy($file, $destination, \Drupal\Core\File\FileSystemInterface::EXISTS_REPLACE)) {
      $form_state->setValue('csv_file', $destination);
    else {
      $form_state->setErrorByName('csv_file', t('Unable to copy upload file to @dest', ['@dest' => $destination]));

public function submitForm(array &$form, FormStateInterface $form_state) {
    $csv_uri = $form_state->getValue('csv_upload', FALSE);

if ($csv_uri) {
  $csv_data = processCsv($csv_uri);

  if (count($csv_data)) {
    $batch = $this->generateBatchImport($csv_data);


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.