Summary
FormData.append() stringifies every value, so a File or Blob is stored
as the string "[object Object]". Consequently a Request/fetch with a
FormData body sends zero bytes and no content-type.
Per the spec, append(name, value) stringifies only when value is not a
Blob; a Blob/File must be kept as an entry value.
This is the outgoing half of multipart. The incoming half is fine — the
Request.formData() fix from #9617 works correctly, verified separately below.
Environment
|
|
| Perry |
d36a1af0c (2026-09-05), reports 0.5.1520 |
| Platform |
Linux x86_64 |
Repro
const bytes = new Uint8Array(1024);
const file = new File([bytes], "rad.jpg", { type: "image/jpeg" });
const blob = new Blob([bytes], { type: "application/octet-stream" });
const fd = new FormData();
fd.append("f", file);
fd.append("b", blob);
fd.append("t", "hello");
console.log(fd.get("f") instanceof File); // expected true
const req = new Request("http://x/", { method: "POST", body: fd });
console.log(req.headers.get("content-type")); // expected multipart/form-data; boundary=…
console.log((await req.arrayBuffer()).byteLength); // expected > 2048
Observed:
File before append typeof=object isFile=true isBlob=true size=1024 name=rad.jpg
Blob before append typeof=object isFile=false isBlob=true size=1024
fd.get('f') [File] typeof=string ctor=String isFile=false isBlob=false str="[object Object]"
fd.get('b') [Blob] typeof=string ctor=String isFile=false isBlob=false str="[object Object]"
fd.get('t') [text] typeof=string ctor=String str="hello"
keys=["f","b","t"]
Request content-type: null
Request body bytes: 0 (expect > 2048)
So:
File/Blob construct correctly — size, name, type and instanceof
are all right before append.
append stores them as "[object Object]".
new Request(…, { body: fd }) sets no content-type and yields a
0-byte body. fetch does the same, so the server sees an empty request.
Note constructor.name is null for a correctly-constructed File/Blob,
which may or may not be related.
The incoming side is fine
Hand-crafting a multipart body and parsing it on the server works exactly as
expected on this same build — so #9617's fix is good and this is a separate
defect:
formDataKeys: ["file","caption"]
file → isFile=true isBlob=true name="a.bin" size=9 mime="application/octet-stream"
caption → typeof=string "hello"
Impact
Any client that uploads with FormData sends an empty body. It fails
quietly — the request is accepted, the server simply sees no file. In our
case (MB24) that is listing photos, KYC documents and claim photos.
It also makes a FormData-based test unable to exercise a working server
parser, which is what masked the state of #9617 for us: our spike posts with
new FormData(), so it still failed after the parser was fixed.
Summary
FormData.append()stringifies every value, so aFileorBlobis storedas the string
"[object Object]". Consequently aRequest/fetchwith aFormDatabody sends zero bytes and nocontent-type.Per the spec,
append(name, value)stringifies only whenvalueis not aBlob; aBlob/Filemust be kept as an entry value.This is the outgoing half of multipart. The incoming half is fine — the
Request.formData()fix from #9617 works correctly, verified separately below.Environment
d36a1af0c(2026-09-05), reports0.5.1520Repro
Observed:
So:
File/Blobconstruct correctly —size,name,typeandinstanceofare all right before
append.appendstores them as"[object Object]".new Request(…, { body: fd })sets nocontent-typeand yields a0-byte body.
fetchdoes the same, so the server sees an empty request.Note
constructor.nameisnullfor a correctly-constructedFile/Blob,which may or may not be related.
The incoming side is fine
Hand-crafting a multipart body and parsing it on the server works exactly as
expected on this same build — so #9617's fix is good and this is a separate
defect:
Impact
Any client that uploads with
FormDatasends an empty body. It failsquietly — the request is accepted, the server simply sees no file. In our
case (MB24) that is listing photos, KYC documents and claim photos.
It also makes a
FormData-based test unable to exercise a working serverparser, which is what masked the state of #9617 for us: our spike posts with
new FormData(), so it still failed after the parser was fixed.