Many REST-style APIs accept both binary content and structured metadata in a single call so that uploads stay consistent and transactional. Sending files together with JSON in one multipart submission avoids race conditions between separate requests and keeps related data tightly coupled for operations such as image uploads, log bundles, and backup imports.

In HTTP, the multipart/form-data encoding splits the request body into distinct parts separated by a boundary marker, with each part carrying its own Content-Disposition and optional Content-Type headers. When curl is invoked with the –form option, each form field or file becomes one part in that multipart body; file parts use the @file syntax, while JSON metadata can be read from disk or supplied inline with an explicit type=application/json attribute.

Incorrect field names, mismatched content types, or broken shell quoting around JSON frequently cause multipart uploads to fail even when the URL and authentication are correct. Testing against echo services before targeting production endpoints reduces the risk of malformed payloads, and careful handling of secrets inside .json files or inline literals helps prevent accidental disclosure on shared systems.

Steps to upload files and JSON with curl:

  1. Create a sample file on disk to represent the binary payload.
    $ printf 'Example payload for multipart upload.\n' > sample.txt
    $ ls -l sample.txt
    -rw-r--r-- 1 user user 43 May 18 10:15 sample.txt

    Any existing file path can replace sample.txt as long as the curl command later points to the correct location.

  2. Create a JSON document containing metadata that accompanies the file.
    $ cat <<'EOF' > metadata.json
    {
      "description": "Example file",
      "tags": ["curl", "multipart"],
      "expires_in_days": 7
    }
    EOF
    $ cat metadata.json
    {
      "description": "Example file",
      "tags": ["curl", "multipart"],
      "expires_in_days": 7
    }

    Storing API tokens, passwords, or other sensitive values in plain .json files exposes them to local users; prefer environment variables, encrypted filesystems, or dedicated secret stores when metadata must contain secrets.

  3. Send a multipart HTTP POST request that uploads the file and JSON metadata loaded from the file.
    $ curl --request POST \
      --url https://httpbin.org/post \
      --form 'file=@sample.txt;type=text/plain' \
      --form 'metadata=@metadata.json;type=application/json'
    {
      "args": {},
      "data": "",
      "files": {
        "file": "Example payload for multipart upload.\n",
        "metadata": "{\n  \"description\": \"Example file\",\n  \"tags\": [\"curl\", \"multipart\"],\n  \"expires_in_days\": 7\n}\n"
      },
      "form": {},
    ##### snipped #####
    }

    Field names such as file and metadata must match the parameter names expected by the remote API, and the type=application/json attribute marks the metadata part with the correct content type.

  4. Send an equivalent multipart request that uses inline JSON metadata instead of the JSON file.
    $ curl --request POST \
      --url https://httpbin.org/post \
      --form 'file=@sample.txt;type=text/plain' \
      --form 'metadata={"description":"Inline JSON","tags":["curl","multipart"],"expires_in_days":1};type=application/json'
    {
      "args": {},
      "data": "",
      "files": {
        "file": "Example payload for multipart upload.\n"
      },
      "form": {
        "metadata": "{\"description\":\"Inline JSON\",\"tags\":[\"curl\",\"multipart\"],\"expires_in_days\":1}"
      },
    ##### snipped #####
    }

    Inline JSON benefits from enclosing the entire value in single quotes so that double quotes inside the JSON remain untouched by the shell on Linux and other Unix-like systems.

  5. Verify that the server received both parts by inspecting the files and form sections of the echoed response.
    $ curl --request POST \
      --url https://httpbin.org/post \
      --form 'file=@sample.txt;type=text/plain' \
      --form 'metadata=@metadata.json;type=application/json' \
      --silent | jq '.files.file, .files.metadata'
    "Example payload for multipart upload.\n"
    "{\n  \"description\": \"Example file\",\n  \"tags\": [\"curl\", \"multipart\"],\n  \"expires_in_days\": 7\n}\n"

    Success signals include non-empty entries for the expected part names under files or form and an HTTP 2xx status code; on Ubuntu, jq installs with sudo apt update && sudo apt install --assume-yes jq for more convenient JSON inspection.

Discuss the article:

Comment anonymously. Login not required.