How To Set The Value Of A File Input

It’s always been impossible to set the value of a file input element with JavaScript, but this has changed last year and it’s now broadly supported, let’s see how it’s done.

If you’re interested in why this is useful, read on below, else jump to the solution

Why Is Setting The Value Of A File Input Useful?

Being able to set the value of a file input comes in handy when we’ve edited an image in the browser, or when one or more files have been dropped on the page and we want to upload them to a server.

In both situations we end up with File objects that we can’t leave anywhere but in memory. We can serve the files as downloads, upload them asynchronously, or convert them to base64 strings and store them in a hidden text field, but we couldn’t store them in a file input element.

When we’re in control of the backend we can create an API to handle asynchronous file uploads or convert base64 encoded files back to file ojects. But there are a lot of situations where we don’t control the backend.

In all these situations there’s no way to upload File objects created in the browser.

The moment we can set the value of a file input the backend seizes to be part of the equation, meaning we can finally progressively enhance the file input element.

Updating The File Input Files Property

We’ll set up a file input element and a short script that sets the file input files property.

The script creates a new File object and stores it in myFile

We then use DataTransfer to wrap the file in a FileList which we can assign to the files property of our file input element.

<input type="file" />

<script>
    // Get a reference to our file input
    const fileInput = document.querySelector('input[type="file"]');

    // Create a new File object
    const myFile = new File(['Hello World!'], 'myFile.txt', {
        type: 'text/plain',
        lastModified: new Date(),
    });

    // Now let's create a DataTransfer to get a FileList
    const dataTransfer = new DataTransfer();
    dataTransfer.items.add(myFile);
    fileInput.files = dataTransfer.files;
</script>

And presto! The file input now contains “myFile.txt”

This works on all modern browsers. 🎉

Don’t see any file info? You’re probably browsing the web on Safari for MacOS 👇

But Safari

Safari was the last browser to add support for the DataTransfer constructor, it was added in version 14.1.

While Safari for MacOS detects the field has a value assigned it doesn’t show the file name. For some situations that’s fine, for others that is bad UX.

Interestingly it does work correctly on Safari for iOS 🤷‍♂️

Let’s give Safari for MacOS a little help using this knowledge:

To target Safari we’ll check if webkitEntries has a length. Then we’ll set the current file name to the data-file attribute, finally we’ll add a pseudo-element and set its content property value to the value of the data-file attribute.

<style>
    /* Add pseudo-element only if data attribute is set */
    input[data-file]::after {
        content: attr(data-file);
        margin-left: 0.375em;
    }
</style>

<input type="file" />

<script>
    // Get a reference to our file input
    const fileInput = document.querySelector('input[type="file"]');

    // Create a new File object
    const myFile = new File(['Hello World!'], 'myFile.txt', {
        type: 'text/plain',
        lastModified: new Date(),
    });

    // Now let's create a FileList
    const dataTransfer = new DataTransfer();
    dataTransfer.items.add(myFile);
    fileInput.files = dataTransfer.files;

    // Help Safari out
    if (fileInput.webkitEntries.length) {
        fileInput.dataset.file = `${dataTransfer.files[0].name}`;
    }
</script>

The field below now shows the file name as well.

In a real project we’d probably want a more robust solution.

We could for example dispatch a custom 'change' event and then update the data-file attribute according to the contents of the files property.

Additionally we’d have to deal with multiple files, situations where the field is cleared, or where the user inputs a new file. But that’s for another time.

For now let’s just celebrate the fact that we can finally set the value of a file input element. 😅

Conclusion

We worked around the limitation of FileList not having a constructor by creating a DataTransfer instance, populating it with files, and then getting the files property from it.

To finish things of we helped Safari to correctly show the currently selected file name.

That’s it. It took a while but we finally have a viable solution to set the value of a file input element. I’ve been using this solution with Pintura in production since last year, and it’s been working wondefully.

I share web dev tips on Twitter, if you found this interesting and want to learn more, follow me there

More articles More articles