Checking file types and sizes with #upload_validatorsfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Letting visitors upload files is one thing; making sure they can't upload a 500MB video to your "profile photo" field, or a disguised executable renamed to .jpg, is another. File uploads get their own dedicated validation mechanism in Drupal — #upload_validators — rather than reusing the generic #element_validate pattern.

What you'll learn in this lesson

  • How #upload_validators attaches file-specific validation constraints declaratively
  • The built-in validators for file extension and file size
  • Why file validation is checked differently from a plain text field's validation
  • How to combine multiple upload validators on the same field

Declaring upload validators

$form['photo'] = [
  '#type' => 'managed_file',
  '#title' => $this->t('Profile photo'),
  '#upload_location' => 'public://profile-photos',
  '#upload_validators' => [
    'FileExtension' => ['extensions' => 'jpg jpeg png'],
    'FileSizeLimit' => ['fileLimit' => 2 * 1024 * 1024],
  ],
];

#upload_validators is an associative array where each key names a validation plugin and each value is the array of arguments that plugin expects. FileExtension restricts uploads to a space-separated list of allowed extensions; FileSizeLimit caps the file size in bytes (here, 2MB, expressed as 2 * 1024 * 1024 for readability rather than the raw number). Both run automatically the moment a file is uploaded — before the rest of the form is even submitted.

Why this differs from a normal field's validation

A managed_file or file element uploads and validates its file over its own AJAX request, separate from the form's main submission — that's how you get instant feedback the moment a bad file is selected, rather than waiting for the whole form to be submitted first. #upload_validators plugs into that upload-time check specifically, which is why file validation uses this dedicated property instead of #element_validate or validateForm().

Stacking multiple constraints

'#upload_validators' => [
  'FileExtension' => ['extensions' => 'pdf doc docx'],
  'FileSizeLimit' => ['fileLimit' => 10 * 1024 * 1024],
  'FileImageDimensions' => ['maxDimensions' => '0x0', 'minDimensions' => '100x100'],
],

Every entry in the array is checked; a file must pass all of them to be accepted. Drupal core ships several built-in validator plugins beyond extension and size — FileImageDimensions for minimum/maximum pixel dimensions, for instance — and contrib modules can add their own for cases core doesn't cover.

Extension checking isn't a security boundary by itself. FileExtension checks the filename's extension, not the file's actual binary content — a malicious file renamed to end in .jpg would pass this specific check. Drupal's file system does additional MIME-type sniffing separately, but if you're accepting uploads from untrusted users, treat extension validation as one layer of defense, not the whole story.

See it for yourself

Add a managed_file field with FileExtension restricted to jpg jpeg png and a 2MB FileSizeLimit. Try uploading a .txt file and confirm it's rejected immediately, before you even submit the rest of the form.

Quick check: you set 'FileSizeLimit' => ['fileLimit' => 2 * 1024 * 1024]. Why write the number as an expression instead of just the literal 2097152? (Answer: pure readability — PHP evaluates the expression at parse time either way, so there's no performance difference, but 2 * 1024 * 1024 immediately communicates "2 megabytes" to anyone reading the code, while the raw byte count doesn't.)

Key takeaways

  • #upload_validators is a dedicated property for file-specific validation, separate from #element_validate/validateForm().
  • Each key in the array names a validator plugin (FileExtension, FileSizeLimit, FileImageDimensions, and more); each value is that plugin's argument array.
  • File validation runs at upload time via the element's own AJAX request, giving instant feedback before the rest of the form is submitted.
  • Extension checking alone isn't a full security guarantee — it checks the filename, not the file's actual binary contents.

Coming up next

You've now seen every mechanism for detecting invalid input — element-level, form-level, required-field, and file-specific. Next: how those detected errors actually get displayed to the person filling out the form, and what control you have over that presentation.