···123123 const applyRequestInterceptors = async (
124124 request: Request,
125125 opts: ResolvedRequestOptions,
126126+ body: BodyInit | null | undefined,
126127 ) => {
127128 for (const fn of interceptors.request.fns) {
128129 if (fn) {
···136137 // body comes only from getValidRequestBody(options)
137138 // reflect signal if present
138139 opts.signal = (request as any).signal as AbortSignal | undefined;
140140+141141+ // When body is FormData, remove Content-Type header to avoid boundary mismatch.
142142+ // The Request constructor auto-generates a boundary and sets Content-Type, but
143143+ // we pass the original FormData to ofetch which will generate its own boundary.
144144+ // If we keep the Request's Content-Type, the boundary in the header won't match
145145+ // the boundary in the actual request body sent by ofetch.
146146+ if (typeof FormData !== 'undefined' && body instanceof FormData) {
147147+ opts.headers.delete('Content-Type');
148148+ }
149149+139150 return request;
140151 };
141152···174185 };
175186 let request = new Request(url, requestInit);
176187177177- request = await applyRequestInterceptors(request, opts);
188188+ request = await applyRequestInterceptors(request, opts, networkBody);
178189 const finalUrl = request.url;
179190180191 // build ofetch options and perform the request (.raw keeps the Response)
···233244 method,
234245 onRequest: async (url, init) => {
235246 let request = new Request(url, init);
236236- request = await applyRequestInterceptors(request, opts);
247247+ request = await applyRequestInterceptors(request, opts, networkBody);
237248 return request;
238249 },
239250 serializedBody: networkBody as BodyInit | null | undefined,