Uploading Images from React Native to a Node.js API
TIL that a multipart upload from React Native needs the file described as an object with uri/name/type, not sent as a raw file:// string — an easy mismatch to miss since it fails silently on some devices.
The Problem #
Sending a photo from the driver app to the delivery API meant building a multipart request, and the first version just appended the file:// URI string to FormData the way you might append any other field.
Context #
This was the photo-proof-of-delivery upload — a driver takes a photo, it needs to reach the Node.js API and get attached to the delivery record.
What I Tried #
const formData = new FormData();
formData.append("photo", photoUri); // wrong: just a stringWhat Went Wrong #
The request reached the server, but multer on the receiving end saw a text field, not a file — no binary content, no filename, no content type. The upload "succeeded" from the client's point of view (no thrown error) while producing nothing usable server-side, which made it look like a backend bug at first.
The Solution #
React Native's FormData expects a file field to be an object describing the URI, filename, and MIME type — not the URI on its own.
const formData = new FormData();
formData.append("photo", {
uri: photoUri,
name: "delivery-proof.jpg",
type: "image/jpeg",
} as any); // React Native's FormData typing doesn't match the DOM's
await fetch(`${API_URL}/deliveries/${id}/photo`, {
method: "POST",
body: formData,
headers: { "Content-Type": "multipart/form-data" },
});Why It Works #
React Native's FormData implementation is a shim over the native networking layer, not a literal browser FormData — it needs that shape (uri/name/type) to know how to stream the actual file bytes from the device's filesystem into the multipart body. A plain string has no filename or content type for it to work with.
Lessons Learned #
The failure mode here — no client-side error, no server-side crash, just a missing file — is exactly the kind of bug that survives in production because nothing obviously breaks. It only surfaces when someone notices a delivery record with no photo attached.
What I Would Do Differently #
I'd add a server-side check that rejects (with a clear error) any multipart field it can't identify as an actual uploaded file, so a malformed client request fails loudly instead of silently dropping the photo.
Related Concepts #
multipart/form-data, multer file parsing, React Native's FormData vs. the browser's.
Related content
Building Photo Proof of Delivery: Camera to Dashboard, End to End
TIL that the hard part of a photo-proof-of-delivery feature isn't the camera or the upload individually — it's making sure a completed delivery can never end up without its proof attached.
Debugging an Android-Only FormData Upload Failure
TIL an upload that worked reliably on iOS and failed intermittently on Android came down to a missing file extension on the filename I was generating — Android's multipart parsing cared, iOS's didn't.
Serving Premium Downloads Without Exposing the File's Real Storage URL
TIL how to replace a guessable direct-download link with a permission-checked, time-limited presigned URL, so the object storage path a purchased asset actually lives at is never sent to the client.