`curl -F` Multipart Uploads Are Easy Once You Stop Treating Them Like JSON Requests
A practical curl multipart guide showing how to send files and form fields with -F, why uploads fail when developers keep thinking in JSON-only terms, and how to debug the request shape quickly.
Why this trips people up: developers get used to JSON APIs, then the first file upload endpoint arrives and they keep trying to send it like another
-H 'Content-Type: application/json' -d ...request. Multipart requests are a different shape.
What -F is doing
With curl, -F sends form fields as multipart form data. That is the right fit for endpoints expecting uploaded files or form-like request bodies.
A simple file upload example:
curl -F "[email protected]" https://example.com/uploadThat @ matters. It tells curl to send the file contents, not the literal string.
File plus normal fields
Multipart requests often include metadata too:
curl \
-F "[email protected]" \
-F "user_id=42" \
-F "category=finance" \
https://example.com/uploadThis is not the same as embedding everything into one JSON blob and hoping the server interprets the file path for you.
The mistake people keep making
They manually set:
-H "Content-Type: multipart/form-data"without letting curl build the boundary correctly. In many cases, that is the wrong instinct. Multipart requests need a boundary, and curl already knows how to construct it when you use -F.
So the better habit is: use -F, and do not force headers you do not actually need to handcraft.
Why uploads still fail
Usually one of these is true:
- the field name is wrong
- the file path is wrong
- the server expected multipart but got JSON
- the client manually set a broken content type
That last one is more common than people expect. A lot of multipart pain is self-inflicted by trying to be too clever with headers.
Why -v is your friend here
If the request feels wrong, turn on verbose mode:
curl -v -F "[email protected]" https://example.com/uploadYou are not trying to inspect every byte forever. You are trying to answer basic questions fast:
- did curl send the request you thought it would
- did the server reject the upload immediately
- is the endpoint responding like a normal multipart handler
Fast visibility beats guesswork.
How to handle auth and custom field requirements
Real upload endpoints rarely accept only a single file. They often require auth headers, API keys, or extra fields such as folder IDs, message text, or media type hints.
That usually looks more like this:
curl \
-H "Authorization: Bearer $TOKEN" \
-F "[email protected]" \
-F "purpose=profile-image" \
https://example.com/uploadThis is where people often panic and go back to a JSON mindset. They assume the whole request must be rewritten by hand because auth is involved. It does not. Auth headers and multipart fields happily coexist. The file still belongs in -F.
How to debug the server side expectation
If the backend says “missing file” or “unsupported media type,” inspect the server contract carefully:
- what exact field name is expected
- does the endpoint want one file or multiple files
- does it require additional text fields
- is there an auth header the frontend already includes
Many upload bugs are not transport bugs. They are contract-mismatch bugs. The client sent upload, the server expected file, and everybody lost thirty minutes pretending the multipart format itself was mysterious.
A practical mental model
Treat multipart as “a bag of named fields, some of which may contain file content.” That model is simple enough to debug from both sides. Once you think about it that way, curl -F stops feeling like a weird trick and starts feeling like the obvious tool.
Final recommendation
Treat multipart uploads as their own request shape instead of as “JSON plus a file somehow.” With curl, -F is the clean default, and the fewer homemade content-type tricks you add, the more likely the request will be correct. Upload debugging gets much easier once you let the tool do the multipart work it was designed to do.