htmx send files using htmx.ajax() call
By Flavio Copes
Learn how to send files with an htmx.ajax() call by setting hx-encoding to multipart/form-data and passing the source element so the files reach the server.
In a drag and drop file upload workflow I had the need to use htmx.ajax() to fire a network call to upload files, but I had some trouble sending data to the server.
Some context first. A file upload over HTTP needs the multipart/form-data encoding, because the default form encoding cannot carry binary file content. In a normal form, htmx handles this without JavaScript: you set enctype="multipart/form-data" on the form, add a file input, and hx-post does the rest.
Drag and drop is different. The files never pass through a form submission. My drop handler collected them into a FormData object, and I wanted to hand them to htmx programmatically with htmx.ajax(). The request fired, but the files never reached the server.
After some research I found out using htmx.ajax() you cannot (at the time of writing) set the Content-Type header to multipart/form-data, which is what makes file uploads possible in HTTP.
To overcome this, you must set the hx-encoding attribute in the HTML, and use the source property in the context object passed to htmx.ajax():
<div
id="dropzone"
hx-post="/upload"
hx-encoding="multipart/form-data"
hx-trigger="none"
>
hx-trigger="none" means the element never fires a request on its own. Only my drop handler does, with this call:
htmx.ajax('POST',
event.currentTarget.getAttribute('hx-post'), {
values: {
files: formData.getAll('files')
},
source: event.currentTarget,
})
The source property is the missing piece. It tells htmx which element the request belongs to, so the request picks up that element’s attributes, including hx-encoding="multipart/form-data". With the encoding in place, the File objects inside values are sent as real multipart parts, and the server receives them like any other upload.
To verify it worked, open the request in the Network panel. The Content-Type header must read multipart/form-data with a boundary string, and the payload view shows each file as its own part. If you see application/x-www-form-urlencoded instead, htmx did not pick up the encoding: check that source points at the element carrying hx-encoding.
Related posts about htmx: