Selecting a file does not upload it, completing a network transfer does not prove the server accepted it, and showing a preview does not prove it is safe to serve later. A browser form can send a file with multipart/form-data. When constructing FormData for fetch, let the browser set the Content-Type header so it includes the correct boundary; a hand-written multipart header without that boundary breaks parsing. Progress reporting requires an API or upload protocol that exposes transferred bytes. A large-file resume feature needs an explicit upload ID, chunk positions, integrity checks, expiry, and finalization step on the server.
Multipart Upload Progress and Retry Boundaries
Working case
The intake form accepts a 47 MB inspection video. A developer shows 'Uploaded' as soon as the browser has sent all bytes, but the server later rejects the file after validation. Change the states to Selected, Transferring, Processing, Accepted, or Rejected. For a small file, one multipart request may be enough. For larger files on unstable connections, split into bounded chunks only if the server supports session ownership and idempotent chunk retries. A cancelled upload must release on-device preview URLs and tell the server to expire or discard unfinished data.
Implementation boundary
async function sendEvidence(formElement, endpoint) {
const payload = new FormData(formElement);
const response = await fetch(endpoint, { method: "POST", body: payload });
if (!response.ok) throw new Error("Evidence was not accepted");
return response.json();
}The browser adds the multipart boundary when given FormData; do not set that Content-Type manually. The server still checks authorization, size, media type from inspected bytes, storage quotas, and malware policy before attaching a file to a case. Fetch does not provide a simple cross-browser upload progress event through this pattern, so a product requiring byte progress needs a supported transport or a separate chunk protocol. Retrying a complete one-shot POST can duplicate the file unless the server uses a stable request identity. Keep the upload's temporary ID scoped to the account and case.
Cost and boundaries
A single multipart transfer costs O(b) bytes for a file of size b, plus protocol overhead; building large blobs in JavaScript can add O(b) memory on top of the browser's file handle. Chunking with c-byte parts adds roughly O(b/c) requests and metadata operations, but permits bounded retry of a failed part. Each server-side temporary object consumes storage until accepted or expired. Select thresholds from measured network conditions and server limits, and do not add a resume protocol to a form that only accepts tiny attachments.
Failure trace
The client sets Content-Type: multipart/form-data by hand, omitting the boundary; the server reports an empty file even though the browser sent bytes. Remove that header and let FormData control it. A second failure occurs when the server returns 202 for processing and the UI calls the upload Accepted immediately. Wait for final validation or show Processing with a status check. If a chunk retry arrives twice, the server must recognize the same upload ID and byte range rather than appending duplicate bytes.
Verification
- Multipart requests include a browser-generated boundary.
- Transfer completion does not imply server acceptance.
- A repeated chunk cannot duplicate bytes or attach a file twice.
Practice drill
Submit a small file using a native form and FormData, then inspect the request boundary in developer tools. Break the manually set header in a controlled branch and confirm the server refuses it. For a simulated large file, interrupt the third chunk, retry the same range, and verify the final byte length and checksum. Reject the file after transfer and check that the interface says Rejected rather than Uploaded. Cancel midway and verify preview URL cleanup and temporary server-data expiry.
Decision note
Represent upload states honestly and introduce resumable chunks only with server-owned identity, integrity, and finalization.
Common Mistakes
- Manually setting multipart Content-Type for FormData fetch.
- Treating a on-device preview as a validated server asset.
- Retrying a non-idempotent upload without a stable upload identity.
Connected lessons
Forms and Data Entry Workflows; Native Form Submission and Progressive Enhancement; Asynchronous Field Validation Races; Autosave, Draft Recovery, and Conflicts; Safe File Upload Pipeline; Idempotent Write Requests and Lost Responses; Browser Memory and Resource Lifecycles.
Apply and check
Build Project: recoverable case intake form and review Web Development: rendering and forms decisions quiz.
