Skip to content

FormData.append() stringifies Blob/File — fetch with a FormData body sends 0 bytes and no content-type #9842

Description

@proggeramlug

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:

  1. File/Blob construct correctly — size, name, type and instanceof
    are all right before append.
  2. append stores them as "[object Object]".
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions