Using the File Element for Raw File Uploadsfor Drupal 11 , 10 , 9 , and 8

Last updated :  

Every element so far has submitted a value Drupal stores directly. Files are different — a browser upload arrives as a raw, temporary upload, and the plain file element gives you that raw upload with almost no Drupal-managed handling on top. It's the low-level building block; the next lesson's managed_file is the version you'll actually use in most real modules.

What you'll learn in this lesson

  • The basic file element and why forms with it need a special attribute
  • Restricting allowed extensions with #upload_validators
  • Why the raw upload is temporary until your submit handler explicitly saves it

A basic file element

$form['#attributes']['enctype'] = 'multipart/form-data';

$form['document'] = [
  '#type' => 'file',
  '#title' => $this->t('Upload a document'),
];

That enctype attribute on $form itself is easy to forget and breaks uploads silently when it's missing — file data can't travel over a normal URL-encoded POST body, so the form's encoding type must explicitly switch to multipart/form-data whenever it contains a file element.

Restricting what can be uploaded

'#upload_validators' => [
  'file_validate_extensions' => ['pdf doc docx'],
  'file_validate_size' => [5 * 1024 * 1024], // 5MB
],

#upload_validators is a keyed array of validator function names mapped to their arguments — Drupal runs each one automatically during form validation, before your own submitForm() even runs. Always set at least an extension whitelist here; an upload field with no restrictions is an easy way to end up with executable files or unexpected content types sitting in your files directory.

The upload is temporary until you save it

Here's the part that surprises people coming from other frameworks: submitting the form doesn't automatically save the file anywhere permanent. PHP places the uploaded file in a temporary location, and it's your submit handler's job to move it somewhere real using Drupal's file-saving APIs — otherwise it's cleaned up as garbage the moment the request ends. You'll see exactly how that save step works in the next lesson.

The plain file type has no built-in AJAX preview or progress bar — that's one of the main reasons most real Drupal modules reach for managed_file instead, which wraps this same underlying mechanism with automatic saving, a file preview, and a remove button. Treat file as the raw building block, not the element you'll typically place directly in a form.

Quick check: your form has a file element but you forgot to set $form['#attributes']['enctype']. What happens when a user tries to upload? (The upload silently fails — a normal URL-encoded POST body can't carry file data at all, so without the correct encoding type, the file never actually reaches the server no matter what the user selects.)

Key takeaways

  • Any form with a file element needs $form['#attributes']['enctype'] = 'multipart/form-data' or uploads silently fail.
  • Always set #upload_validators to restrict extensions and size — an unrestricted upload field is a real security risk.
  • The raw upload is temporary — nothing is permanently saved until your submit handler explicitly moves it, which is exactly what the next lesson's managed_file handles for you automatically.

Coming up next

You've seen the raw file upload mechanism. Next, we cover managed_file — the version that actually saves uploads as real, Drupal-tracked file entities with almost no extra code from you.