Last blog post I shared how to implement rich text in a custom Drupal 9 form, I faced similar issue with files in OpenLucius: couldn't find native, clean and complete working example for multiple files upload. So I figured it out, here is the example code:
Previously I mostly relied on contrib modules for multiple file uploads in Drupal forms, like Plupload. First of all, at the moment that module isn't available in Drupal 9.
Furthermore, such contrib modules are great: they gave me a kick-start. But to get to 100% of requirements was always kinda hard and with upgrades they can be a showstopper, I'd like to stay as lean and native as possible.
So the more native, the better. I was really glad I found the native form api element '#type' => 'managed_file'
and its option to upload multiple files '#multiple' => TRUE
, which basically provides this Drupal AJAX form element:

End-user functions
When implemented correctly this form element generates an ajax-based form in which end-user can:
- upload files without page refresh;
- have a preview of which files will be saved;
- even delete them before submitting the form.
The Drupal code
For a fully working version, please check the ol_messages module in the Drupal social intranet distro OpenLucius.
So, extracted from OpenLucius, here is the example Drupal code. Also see code explanation underneath.
Explaining most important Drupal code for multiple files upload in custom form
Load Services via Dependency injection

This is an example on how to inject Services via Dependency Injection in a Drupal Form, OpenLucius uses these Services to facilitate everything. In this gist I added the needed code from those services in helper functions.
Build the multiple files upload Drupal form

- Set default form values. For the multiple files upload we need
$hdd_file_location
, which is pretty self explanatory, no? :) - You can also facilitate the edit mode within same form, I stubbed some code for that here.
- Facilitate message_id, also needed for edit mode.
- Define the name field (title) for the message.
- Implement a rich text editor, more details here.
- Here is where the multiple-files-upload-magic starts: define a field with
'#type' => 'managed_file'
- Define the required
'#upload_location'
- Essential: define
'#multiple' => TRUE
- Also required to make sure no harmful files can be uploaded, not even in temporary file directory. This is facilitated in a helper function in this gist, as said normally done via a Drupal Service.
Facilitate submitted values in submitForm()

- Get name (title) of the message, with an extra XSS security check. More on Drupal security
- Get the body value of the rich text editor, check this blog for detailed info.
- Security check for body value.
- Here is where the multiple files code starts: put the submitted files data in
$files
variable. - Save the files via a helper function, which most of the times lives in a Service: since multiple modules probably want to save files. And you want it to be testable and overrideable. Want a working example? Check ol_messages module code in OpenLucius distro.
Save uploaded files permanent and attach them to an entity

- Drupal will provide an array with file id's via the
formSubmit()
, we loop through those here. - Set all individual files to Permanent with Drupal core function
setPermanent()
. (!) If you don't do this, the uploaded files will stay in temporary file directory and will be deleted on ~next cron run! - Get the file name via Drupal core function
getFilename()
. - Now the file is 'physically' uploaded to the disk, you need to do something with it, else it just sits there like an orphan. In OpenLucius we implemented a custom entity (ol_file) which we use to facilitate files in all kinds of ways in different groups: as an attachment, as a chat item, as a file repository item, as a comment attachment and more.
- This bulky code facilitates a nice message for end-user. I put some effort in here so user will get most accurate feedback as possible after uploading a file.
Lastly, some helper functions

As said, these normally live in Services. But to keep this example compact I put them in this gist.
- To facilitate edit mode: get current message data.
- Build file location. In OpenLucius we have some extra subfolders like group id and user id, to keep file directories a clean as possible.
- Allowed file extensions, you could load this from Drupal core settings. But this gives more flexibility: you can change allowed extensions per file upload field.
Wrap up
OK, that's it for now. I hope this will kick-start a multiple files upload implementation in your Drupal project in a native, clean way. So it's flexible, scalable and future-proof!
