Skip to content

5. Blobs and references

JMAP separates structured data (rows in your tables) from binary blobs (images, attachments, …). Blobs get their own HTTP endpoints and a dedicated capability — urn:ietf:params:jmap:blob.

Upload

Two paths:

Out-of-band HTTP upload — best for large payloads:

curl -X POST "http://localhost:8000/jmap/upload/$ACCOUNT_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: image/png' \
  --data-binary @logo.png

Returns {"accountId": "...", "blobId": "<id>", "type": "image/png", "size": 42}.

Inline upload via JMAP — best when you can batch with other calls:

["Blob/upload", {
  "accountId": "ACC",
  "create": {
    "k1": {
      "data": [{"data:asText": "hello"}],
      "type": "text/plain"
    }
  }
}, "c0"]

Download

curl "http://localhost:8000/jmap/download/$ACCOUNT_ID/<blob-id>/logo.png" \
  -H "Authorization: Bearer $TOKEN" -o logo.png

The filename in the path is a hint for the Content-Disposition header — the server uses it verbatim.

Reference blobs from your objects

Many capabilities want to attach blobs to their data type. E.g. an email attachment is a blob referenced from a Mailbox; a Todo could have a logo blob.

Two pieces:

  1. Store the blob id on your object (just a string column).
  2. Write a BlobReference row so Blob/lookup can answer "where is this blob used?":
from jmaple.db.models import BlobReference
ctx.db.add(BlobReference(
    blob_id=todo.logo_blob_id,
    account_id=account_id,
    data_type="Todo",
    object_id=todo.id,
))

Blob/lookup walks BlobReference to enumerate objects pointing at a blob — useful for orphan-cleanup and "what depends on this attachment".

Result references — chain calls

A common pattern: create a blob via Blob/upload, then save its id on a Todo in the same request. Result references let you pipe one method's response into the next without an extra round-trip:

{
  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:blob", "urn:example:todos"],
  "methodCalls": [
    ["Blob/upload", {
      "accountId": "ACC",
      "create": {"k1": {"data": [{"data:asText": "hi"}], "type": "text/plain"}}
    }, "c0"],
    ["Todo/set", {
      "accountId": "ACC",
      "create": {
        "t1": {
          "text": "with attached blob",
          "#logoBlobId": {"resultOf": "c0", "name": "Blob/upload", "path": "/created/k1/blobId"}
        }
      }
    }, "c1"]
  ]
}

The #logoBlobId key (note the leading #) marks a result reference. The framework evaluates the JSON Pointer against the response of call c0 and substitutes the resolved value before validating Todo/set args.

Next: push notifications →