Skip to content

Commit 3ef7b99

Browse files
fix(openapi-fetch): fix headers for FormData
We need to delete the `Content-Type` header if the serialized body is a `FormData`. This allows the browser to set the `Content-Type` and boundary expression correctly. Resolves #1191
1 parent f259093 commit 3ef7b99

File tree

4 files changed

+22
-4
lines changed

4 files changed

+22
-4
lines changed

.changeset/orange-comics-sip.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-fetch": patch
3+
---
4+
5+
Fix header handling for FormData

packages/openapi-fetch/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,17 @@ const { data, error } = await put("/submit", {
218218
});
219219
```
220220

221+
If your `body` is already a `FormData`, provide an identity function:
222+
223+
```ts
224+
const { data, error } = await put("/submit", {
225+
body: myFormData,
226+
bodySerializer: (body) => body,
227+
});
228+
```
229+
230+
For `multipart/form-data`, [do not set the `Content-Type` header](https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects#sending_files_using_a_formdata_object). The browser will set that for you, along with the boundary expression, which serves as a delimiter for the form fields.
231+
221232
## 🎯 Project Goals
222233

223234
1. Infer types automatically from OpenAPI schemas **without generics** (or, only the absolute minimum needed)

packages/openapi-fetch/src/index.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,6 @@ describe("client", () => {
272272
const client = createClient<paths>();
273273
mockFetchOnce({ status: 200, body: "{}" });
274274
const { data } = await client.put("/contact", {
275-
headers: {
276-
"Content-Type": "multipart/form-data",
277-
},
278275
body: {
279276
name: "John Doe",
280277
@@ -293,6 +290,9 @@ describe("client", () => {
293290
// expect post_id to be encoded properly
294291
const req = fetchMocker.mock.calls[0][1];
295292
expect(req.body).toBeInstanceOf(FormData);
293+
294+
// TODO: `vitest-fetch-mock` does not add the boundary to the Content-Type header like browsers do, so we expect the header to be null instead
295+
expect((req.headers as Headers).get("Content-Type")).toBeNull();
296296
});
297297
});
298298

packages/openapi-fetch/src/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,11 @@ export default function createClient<Paths extends {}>(clientOptions: ClientOpti
122122
redirect: "follow",
123123
...options,
124124
...init,
125-
headers: baseHeaders,
126125
};
127126
if (requestBody) requestInit.body = bodySerializer(requestBody as any);
127+
// remove `Content-Type` if serialized body is FormData; browser will correctly set Content-Type & boundary expression
128+
if (requestInit.body instanceof FormData) baseHeaders.delete("Content-Type");
129+
requestInit.headers = baseHeaders
128130
const response = await fetch(finalURL, requestInit);
129131

130132
// handle empty content

0 commit comments

Comments
 (0)