# File Upload

<div id="demo-content" class="not-prose demo-container flex items-center justify-center min-h-[460px]"></div>

## Basic usage

On the client, set `hx-encoding` to `multipart/form-data`:

```html
<form hx-post="/upload"
      hx-encoding="multipart/form-data">
    <input type="file" name="file">
    <input type="text" name="name">
    <button>Submit</button>
</form>
```

- [`hx-encoding`](https://four.htmx.org/reference/attributes/hx-encoding)=`"multipart/form-data"` sends the form as [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData), required for file uploads.
- [`hx-post`](https://four.htmx.org/reference/attributes/hx-post) submits the form to `/upload`.

On the server, respond with a success or error message:

```html
<p>File uploaded successfully.</p>
```

## Preserving file selection on errors

When a form re-renders with validation errors, file inputs lose their selection. Add [`hx-preserve`](https://four.htmx.org/reference/attributes/hx-preserve) to keep it:

```html
<form hx-post="..."
      hx-swap="outerHTML"
      hx-encoding="multipart/form-data">
    <input hx-preserve type="file" name="file">
    <input type="text" name="name">
    <button>Submit</button>
</form>
```

Try it in the demo: select a file, then submit with empty fields. The form re-renders with errors, but your file selection stays.

Alternatively, place the file input outside the swap target using the [`form` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#form):

```html
<input form="my-form" type="file" name="file">

<form id="my-form" hx-post="..."
      hx-encoding="multipart/form-data">
    <button>Submit</button>
</form>
```

The input is outside the form element, so it is never replaced during swaps.

## Upload progress

htmx 4.x uses the native [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API. `fetch` supports [upload progress monitoring](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#monitoring_upload_progress) in some browsers, but cross-browser support is limited. For reliable progress tracking, use [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/upload) directly:

```html
<form id="upload-form" enctype="multipart/form-data">
  <input type="file" name="file">
  <button>Upload</button>
  <progress id="progress" value="0" max="100"></progress>
</form>

<script>
  document.querySelector('#upload-form').addEventListener('submit', (e) => {
    e.preventDefault();
    const xhr = new XMLHttpRequest();
    xhr.upload.addEventListener('progress', (evt) => {
      document.querySelector('#progress').value = (evt.loaded / evt.total) * 100;
    });
    xhr.open('POST', '/upload');
    xhr.send(new FormData(e.target));
  });
</script>
```