fix: remove forced multipart header to fix Android postForm/patchForm…#7243
fix: remove forced multipart header to fix Android postForm/patchForm…#7243neel3o115 wants to merge 1 commit intoaxios:v1.xfrom
Conversation
This comment was marked as spam.
This comment was marked as spam.
1 similar comment
This comment was marked as spam.
This comment was marked as spam.
jasonsaayman
left a comment
There was a problem hiding this comment.
Thanks @neel3o115, and apologies on the wait. The diagnosis is right, Android's RN networking layer rejects bare multipart/form-data without a boundary, iOS doesn't, and the gap exists because RN is neither hasStandardBrowserEnv (axios deliberately excludes RN from that flag in resolveConfig) nor uses Node's form-data package. The placeholder header reaches the wire untouched on RN, and Android complains. Worth fixing.
Three asks before this can merge.
First, drop the toFormData.js changes. Function('return this')() is eval in a different shape — it trips CSP without unsafe-eval, which covers most production browsers behind a CSP and several RN setups. globalThis is universally available everywhere axios runs if global access were ever needed, but it isn't here. The precedence flip from PlatformFormData || FormData to runtimeFD || PlatformFormData is also a silent breakage on Node: Node 18+ has global FormData, so this picks the spec-compliant one over form-data, and any caller of axios.toFormData(obj) on Node who relied on .getHeaders() / .getLength() (documented form-data API) gets an object without them. And it isn't needed for the RN bug — RN bundlers resolve the browser field in package.json, so PlatformFormData already points at lib/platform/browser/classes/FormData.js, which is typeof FormData !== 'undefined' ? FormData : null. In RN, that's the runtime's FormData.
Second, tighten the Axios.js change. What you have:
headers: isForm
? (config && config.headers ? config.headers : {})
: {},config.headers is already merged in by mergeConfig(config || {}, …) — the second argument is the overlay, not the source. Re-passing config.headers is either a no-op or runs the header merge strategy twice. The minimal version is headers: {}, or drop the headers key from the overlay entirely. Same effect: no placeholder is forced, anything in config.headers still flows through normally.
Third, add tests. At minimum: postForm (and put/patch) no longer emit the bare placeholder; Node form-data path still ends up with Content-Type: multipart/form-data; boundary=… via getHeaders(); spec-compliant FormData path through the http adapter still gets a boundary via formDataToStream.
This PR fixes a long-standing issue where axios.postForm, axios.putForm, and axios.patchForm fail on React Native Android with a Network Error.
The issue occurs because Axios forces a bare:
Content-Type: multipart/form-dataheader without a boundary.
React Native Android requires a boundary (e.g. boundary=----RNFormBoundaryABC123).
When Axios overrides the header, React Native cannot append the boundary, causing the request to be malformed and immediately rejected.
iOS is more permissive, so it does not fail.
What This PR Changes:-
iOS React Native (already worked)
Standard axios.post / axios.patch / axios.put (not using FormData helpers)
Technical Summary
Before (broken on Android):
headers: { 'Content-Type': 'multipart/form-data' }After (correct):
headers: isForm ? (config?.headers || {}) : {}This lets React Native generate:
Content-Type: multipart/form-data; boundary=----RNGeneratedBoundary123which Android accepts.
Result:-
Fixes
#6968