> ## Documentation Index
> Fetch the complete documentation index at: https://mediadrop.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# xhr-upload

> The reference createXhrUploadTransport transport

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { createXhrUploadTransport } from "react-mediadrop/xhr-upload";
```

The reference `UploadTransport` — sends a file with `XMLHttpRequest`, not
`fetch`, specifically because `fetch` still has no cross-browser
upload-progress API while `XMLHttpRequest.upload.onprogress` does.

## Quickstart

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
import { useMediaDrop } from "react-mediadrop";
import { createXhrUploadTransport } from "react-mediadrop/xhr-upload";

const transport = createXhrUploadTransport({
	endpoint: "/api/upload", // or (file) => `/api/upload/${file.id}` for a per-file URL
	fields: { folder: "avatars" }, // extra multipart fields
});

const { files } = useMediaDrop({ transport, concurrency: 3, retries: 2 });
```

## Options

| Option            | Type                         | Default        | Notes                                                                                                                                                                                                                      |
| ----------------- | ---------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `endpoint`        | `string \| (file) => string` | — required     | Computed per file, e.g. for a per-file presigned URL you already fetched.                                                                                                                                                  |
| `method`          | `string`                     | `"POST"`       |                                                                                                                                                                                                                            |
| `fieldName`       | `string`                     | `"file"`       | Ignored when `formData: false`.                                                                                                                                                                                            |
| `fields`          | `object \| (file) => object` | —              | Extra multipart fields. Ignored when `formData: false`.                                                                                                                                                                    |
| `headers`         | `object \| (file) => object` | —              |                                                                                                                                                                                                                            |
| `withCredentials` | `boolean`                    | `false`        |                                                                                                                                                                                                                            |
| `formData`        | `boolean`                    | `true`         | `false` sends the raw file body.                                                                                                                                                                                           |
| `isSuccessStatus` | `(status) => boolean`        | `200–299`      |                                                                                                                                                                                                                            |
| `stallTimeoutMs`  | `number`                     | `0` (disabled) | Abort and reject if no upload progress happens for this long — a *stall* timeout (reset on every progress tick), not a flat total-duration one, so a large file on a slow-but-healthy connection is never falsely aborted. |

## What this is for, and what it isn't

* Use it for a generic REST-ish endpoint you control — a single request,
  the whole file, `multipart/form-data` by default (`formData: false`
  sends the raw bytes, e.g. for a presigned PUT URL that isn't S3's).
* Its only dependency is the underlying engine itself; it has **no
  retry loop** and **no concurrency control** of its own — both are the
  queue's job (see [Upload](/guides/upload)). Don't add retry logic here
  if asked to "make uploads more resilient"; point at
  `retries`/`retryDelays` on `useMediaDrop` instead.
* It has **no flat request timeout** by design — `stallTimeoutMs`
  aborts on *no progress*, not on total duration. It's disabled (`0`) by
  default; set it if a task needs "don't hang forever on a dead
  connection."
* It has **no resumability** — a failed or canceled upload restarts from
  byte zero. Resumable, multi-request transports (S3 multipart, tus)
  aren't part of this codebase — see [Roadmap](/roadmap).
* `formData: false` still sends one request, one body — it does not
  split a file into parts.

Need something else — a provider SDK, a resumable protocol, a test
double? See [Writing a custom transport](/guides/custom-transport).

<CardGroup cols={2}>
  <Card title="Upload" icon="cloud-arrow-up" href="/guides/upload">
    The queue, concurrency, retry, and cancel
  </Card>

  <Card title="Writing a custom transport" icon="plug" href="/guides/custom-transport">
    Implement the transport contract yourself
  </Card>
</CardGroup>
