# Upload files via HTTP using FormData (/docs/guides/http/file-uploads)

<!-- agent-signals: reading_time_min: 2 · est_tokens: 718 · updated: 2026-07-28 -->
Related: [Common HTTP server usage](/docs/guides/http/server.md), [Write a simple HTTP server](/docs/guides/http/simple.md), [Send an HTTP request using fetch](/docs/guides/http/fetch.md), [Hot reload an HTTP server](/docs/guides/http/hot.md), [Start a cluster of HTTP servers](/docs/guides/http/cluster.md), [Configure TLS on an HTTP server](/docs/guides/http/tls.md)

To upload files over HTTP with Bun, use the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) API. Start with an HTTP server that serves an HTML form.

```ts index.ts icon="/icons/typescript.svg"
const server = Bun.serve({
  port: 4000,
  async fetch(req) {
    const url = new URL(req.url);

    // return index.html for root path
    if (url.pathname === "/")
      return new Response(Bun.file("index.html"), {
        headers: {
          "Content-Type": "text/html",
        },
      });

    return new Response("Not Found", { status: 404 });
  },
});

console.log(`Listening on http://localhost:${server.port}`);
```

***

Define the HTML form in another file, `index.html`.

```html index.html icon="file-code"
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Form</title>
  </head>
  <body>
    <form action="/action" method="post" enctype="multipart/form-data">
      <input type="text" name="name" placeholder="Name" />
      <input type="file" name="profilePicture" />
      <input type="submit" value="Submit" />
    </form>
  </body>
</html>
```

***

Run the server and visit [`localhost:4000`](http://localhost:4000) to see the form.

```bash
bun run index.ts
Listening on http://localhost:4000
```

***

The form sends a `POST` request with the form data to the `/action` endpoint. Handle that request in the server.

First, call [`.formData()`](https://developer.mozilla.org/en-US/docs/Web/API/Request/formData) on the incoming `Request` to asynchronously parse its contents into a `FormData` instance. Then use [`.get()`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/get) to extract the `name` and `profilePicture` fields; `name` is a `string` and `profilePicture` is a `Blob`.

Finally, write the `Blob` to disk with [`Bun.write()`](/runtime/file-io#writing-files-bun-write).

{/* prettier-ignore */}

```ts index.ts icon="/icons/typescript.svg"
const server = Bun.serve({
  port: 4000,
  async fetch(req) {
    const url = new URL(req.url);

    // return index.html for root path
    if (url.pathname === "/")
      return new Response(Bun.file("index.html"), {
        headers: {
          "Content-Type": "text/html",
        },
      });

    // parse formdata at /action // [!code ++]
    if (url.pathname === "/action") { // [!code ++]
      const formdata = await req.formData(); // [!code ++]
      const name = formdata.get("name"); // [!code ++]
      const profilePicture = formdata.get("profilePicture"); // [!code ++]
      if (!profilePicture) throw new Error("Must upload a profile picture."); // [!code ++]
      // write profilePicture to disk // [!code ++]
      await Bun.write("profilePicture.png", profilePicture); // [!code ++]
      return new Response("Success"); // [!code ++]
    } // [!code ++]

    return new Response("Not Found", { status: 404 });
  },
});
```
