Triggers, forms, and feedback

Upload a file

Use multipart form encoding and a real file input so the server receives binary data with the other form values.

File content cannot travel in the default form encoding. Uploads need multipart/form-data, the HTTP encoding that carries binary parts alongside ordinary fields.

A real upload form declares it explicitly:

<form action="/attachments" method="post"
  enctype="multipart/form-data"
  hx-post="/attachments"
  hx-target="#upload-result">
  <label>
    Attachment
    <input type="file" name="attachment" accept="image/*" required>
  </label>
  <button>Upload</button>
</form>

<div id="upload-result"></div>

Without JavaScript, the browser posts the file to /attachments and navigates. With HTMX loaded, the same submission happens in the background and the server’s response lands in #upload-result. The file arrives under the field name attachment, next to any other named controls in the form.

You can set hx-encoding="multipart/form-data" on the HTMX request context instead. Keep the native enctype when the form must work without JavaScript.

hx-encoding becomes essential when the upload does not flow through a form at all. In a drag-and-drop workflow, your code collects files and starts the request with htmx.ajax():

htmx.ajax('POST', '/attachments', {
  values: { attachment: formData.getAll('attachment') },
  source: dropzone,
})

Put hx-encoding="multipart/form-data" on the source element. The request inherits its attributes, so files are sent as real multipart parts. Confirm in the Network panel: Content-Type must be multipart/form-data with a boundary, not application/x-www-form-urlencoded.

HTMX uses XMLHttpRequest, so htmx:xhr:progress can drive a progress display for large uploads. Treat it as feedback, not proof the server stored the file.

The server carries the real safety duties: size limits, content inspection, safe storage names, authorization, and keeping uploads outside executable paths. accept="image/*" only filters the file chooser. Anyone can POST any bytes directly.

Test an oversized file, a renamed non-image, a lost connection, and a valid upload. Every path needs a recoverable response someone can act on. Return the same error form fragment on validation failure whether the request came from HTMX or a full-page post.

Lesson completed