| Internet-Draft | JMAP Blob | November 2026 |
| Gondwana | Expires 6 May 2027 | [Page] |
The JSON Meta Application Protocol (JMAP) base protocol ([JMAP-CORE]) provides the ability to upload and download arbitrary binary data via HTTP POST and GET on a defined endpoint. This binary data is called a "blob".¶
This extension adds additional ways to create and access blobs by making inline method calls within a standard JMAP request. It also adds a reverse lookup mechanism to discover where blobs are referenced within other data types, support for large blobs via chunked construction with server-side optimisation, and server-side blob conversion operations including image format conversion, archive creation and extraction, compression and decompression, and delta/patch operations.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 6 May 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Sometimes JMAP ([JMAP-CORE]) interactions require creating a blob and then referencing it. Embedding blobs directly into the JMAP method calls array can reduce round trips.¶
Likewise, when fetching an object, it can be useful to also fetch the raw content of that object without a separate round trip.¶
When JMAP is proxied through a system that applies additional access restrictions, it can be useful to know which objects reference any particular blob; this document defines a way to discover those references.¶
For large blobs, a client may wish to upload data in chunks and have the server assemble them efficiently, or retrieve information about how the server has stored a blob internally.¶
Many applications also benefit from server-side transformations — converting images, creating or unpacking archives, compressing or decompressing data, or computing and applying deltas — without needing to download and re-upload blob content.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
The capabilities object is returned as part of the JMAP Session object; see [JMAP-CORE], Section 2.¶
This document defines one additional capability URI.¶
The capability urn:ietf:params:jmap:blob2 being present in the
"accountCapabilities" property of an account represents support for
the blob methods defined in this document: Blob/set, Blob/get,
Blob/lookup, and Blob/convert. Servers that include this capability
in one or more "accountCapabilities" properties MUST also include it
in the "capabilities" property.¶
A server MAY also advertise urn:ietf:params:jmap:blob (as defined
in [JMAP-BLOB]) to support older clients, but a client MUST NOT include
both urn:ietf:params:jmap:blob and urn:ietf:params:jmap:blob2
in the using array of the same request. The blob2 capability
supersedes the blob capability and does not depend on it.¶
The value of this property in the JMAP session "capabilities" property MUST be an empty object.¶
The value of this property in an account's "accountCapabilities" property is an object that MUST contain the following information on server capabilities and permissions for that account:¶
maxSizeBlobSet: "UnsignedInt|null"¶
The maximum size of a blob (in octets) that the server will allow to be created (including blobs created by concatenating multiple data sources together). A creation that would exceed it is rejected with a "tooLarge" SetError. If null, the server does not advertise a specific limit but MAY still reject blobs that are too large.¶
maxDataSources: "UnsignedInt"¶
The maximum number of DataSourceObjects allowed in the data
array of a single creation in Blob/set. Servers MUST support
at least 64 DataSourceObjects per creation. A creation with
more is rejected with a "tooLarge" SetError.¶
supportedTypeNames: "String[]"¶
An array of data type names that the server supports for Blob/lookup. The values are the names listed in the IANA "JMAP Data Types" registry. If the array is empty, the server does not support Blob/lookup. Servers MAY include private type names not in the registry; clients MUST ignore names they do not recognise. Clients MUST also ignore names that they do recognise if the corresponding capability for that type name is not present in the 'capabilities' section of the session object, because that means that the name has been used as a private type rather than a capability that the client knows.¶
supportedDigestAlgorithms: "String[]"¶
An array of digest algorithms supported for Blob/get and for checking digests in Blob/set, from the IANA "Hash Algorithms for HTTP Digest Fields" registry [DIGEST-FIELDS], expressed in lowercase (e.g., "sha-256", "sha").¶
The server lists the algorithms in its order of preference, and SHOULD only list ones it can compute efficiently: a server that stores a digest of each blob, for example, can return that one without reading the blob. Clients SHOULD use the first algorithm in the list that they support. An algorithm the registry marks as deprecated, such as "sha" (SHA-1), which [JMAP-BLOB] servers and clients commonly use, MAY still be listed. SHA-1 is vulnerable to collision attacks: it detects accidental corruption, but a client relying on a digest to detect deliberate tampering SHOULD NOT use it.¶
uploadUrl: "String|null"¶
A URI template (optionally containing {accountId}) for
uploading blobs via HTTP POST for this account, in the same
format as the session-level uploadUrl defined in
[JMAP-CORE], Section 2. Servers MUST still provide the
session-level uploadUrl; this per-account URL is an
alternative that MAY point to a different server or be more
efficient for this account. Uploads to this URL return a JSON
object with the same properties as the session-level upload
response, plus an expires property as defined in "The expires
Response Property" below. If null, clients SHOULD use the
session-level uploadUrl.¶
chunkSize: "UnsignedInt|null"¶
A hint indicating the preferred chunk size in octets. If a client uploads blobs with exactly this size except for the final chunk, and uses Blob/set with DataSourceObjects referencing these chunks, the server can optimise storage of these chunks. Servers MUST allow other sizes for the individual data blocks in Blob/set though, and will then choose whether to store them as an array of blobs still, or to combine them.¶
supportedImageReadTypes: "String[]|null"¶
The media types ([MEDIA-TYPES]) that the server can read as input to ImageConvertRecipe. If null, the server does not support image conversion.¶
supportedImageWriteTypes: "String[]|null"¶
The media types ([MEDIA-TYPES]) that the server can produce as output from ImageConvertRecipe. If null, the server does not support image conversion.¶
supportedArchiveTypes: "String[]|null"¶
The archive media types ([MEDIA-TYPES]) supported for creating archives via ArchiveRecipe. If null, the server does not support archive creation.¶
supportedExtractTypes: "String[]|null"¶
The archive media types supported for extracting archives via
ExtractRecipe. This MAY include types not in
supportedArchiveTypes (e.g., "application/vnd.ms-tnef").
If null, the server does not support archive extraction.¶
supportedCompressTypes: "String[]|null"¶
The compression media types ([MEDIA-TYPES]) supported for compressing via CompressRecipe. If null, the server does not support compression.¶
supportedDecompressTypes: "String[]|null"¶
The compression media types supported for decompressing via
DecompressRecipe. This MAY include types not in
supportedCompressTypes. If null, the server does not
support decompression.¶
supportedDeltaTypes: "String[]|null"¶
The delta media types ([MEDIA-TYPES]) supported for computing deltas via DeltaRecipe. If null, the server does not support delta computation.¶
supportedPatchTypes: "String[]|null"¶
The delta media types supported for applying patches via
PatchRecipe. This MAY include types not in
supportedDeltaTypes. If null, the server does not
support patch application.¶
maxConvertSize: "UnsignedInt|null"¶
If supplied, the maximum size in octets of any single input blob to a Blob/convert operation. Requests referencing a blob larger than this value MUST be rejected with a "tooLarge" SetError. If null, the server does not advertise a specific limit but MAY still reject blobs that are too large.¶
maxArchiveEntries: "UnsignedInt|null"¶
If supplied, the maximum number of entries allowed in an ArchiveRecipe. Requests exceeding this limit MUST be rejected with a "tooLarge" SetError. If null, the server does not advertise a specific limit but MAY still reject requests with too many entries.¶
maxImageDimension: "UnsignedInt|null"¶
If supplied, the maximum value accepted for width or height
in an ImageConvertRecipe, in pixels. Requests exceeding this
limit MUST be rejected with a "tooLarge" SetError. If null,
the server does not advertise a specific limit but MAY still
reject requests with dimensions that are too large.¶
{
"urn:ietf:params:jmap:blob2": {
"maxSizeBlobSet": 52428800,
"maxDataSources": 64,
"supportedTypeNames": ["Email", "Thread", "Mailbox"],
"supportedDigestAlgorithms": ["sha-256", "sha"],
"uploadUrl": "https://up.example.com/jmap/upload/{accountId}/",
"chunkSize": 5242880,
"supportedImageReadTypes": [
"image/png",
"image/jpeg",
"image/gif",
"image/tiff"
],
"supportedImageWriteTypes": [
"image/png",
"image/jpeg"
],
"supportedArchiveTypes": [
"application/zip",
"application/x-tar"
],
"supportedExtractTypes": [
"application/zip",
"application/x-tar",
"application/vnd.ms-tnef"
],
"supportedCompressTypes": [
"application/gzip",
"application/zstd"
],
"supportedDecompressTypes": [
"application/gzip",
"application/x-bzip2",
"application/zstd"
],
"supportedDeltaTypes": [
"application/x-rdiff-delta",
"text/x-diff"
],
"supportedPatchTypes": [
"application/x-rdiff-delta",
"text/x-diff"
],
"maxConvertSize": 104857600,
"maxArchiveEntries": 10000,
"maxImageDimension": 8192
}
}
¶
A DataSourceObject describes a source of data for use in Blob/set.
It MUST contain exactly one of either data:asText or data:asBase64
(for inline data) or blobId (for a reference to an existing blob).
The data:asText and data:asBase64 properties MUST NOT both be
present in the same DataSourceObject.¶
data:asText: "String|null"¶
The data represented as a UTF-8 string.¶
data:asBase64: "String|null"¶
blobId: "BlobId"¶
The blobId of an existing blob whose octets are to be copied into the new blob.¶
offset: "UnsignedInt|null"¶
The offset (in octets) within the data source from which to start copying. If null, defaults to 0 (the start of the data source).¶
length: "UnsignedInt|null"¶
The number of octets to copy from the data source. If null,
copy from offset to the end of the data source.¶
size: "UnsignedInt|null"¶
The full size of the data source in octets. In the chunks
array of a Blob/get response this is the size of the underlying
chunk blob.¶
position: "UnsignedInt|null"¶
The byte offset of the start of this data source within the
outer (containing) blob. Only meaningful in the chunks array
of a Blob/get response. This value is redundant with the sum of
the length values of the preceding chunks, but it allows a
client to locate the chunk covering a particular byte range
without fetching or summing every earlier chunk, and when a
DataSourceObject is reused in Blob/set it acts as a consistency
check that the chunk is being placed where the client expects.¶
A DataSourceObject in the chunks array of a Blob/get response MAY
also include digest:* properties (e.g. digest:sha-256) when
those names are included in the dataSourceProperties request
argument. Each digest value is computed over the octets that the
chunk contributes to the blob (i.e. after applying offset and
length).¶
When a server provides size, position, or digest:* values in
a Blob/get response, it MUST calculate them correctly.¶
A DataSourceObject used in Blob/set is invalid if data:asText is
not valid UTF-8, if data:asBase64 is not valid base64, if the range
given by offset and length begins or extends past the end of the
data, or if any size, position, or digest:* value it contains
does not match the actual data. The server MUST NOT guess the
user's intent: it MUST reject the creation with an
"invalidProperties" SetError whose properties is ["data"]. If
a blobId does not exist, or the user cannot access it, the server
MUST reject the creation with a "notFound" SetError.¶
Blob/set is defined under the urn:ietf:params:jmap:blob2 capability
and requires that capability in the request's using array.¶
This method creates, touches, and destroys blobs. It is a standard Foo/set method ([JMAP-CORE], Section 5.3) with the following blob-specific behaviours:¶
Blobs have no state, as in [JMAP-BLOB]: Blob/get returns no
state string, and there is no Blob/changes method. Blob/set does
not take an ifInState argument, and a non-null value is rejected
with an "invalidArguments" error. Unlike other Foo/set methods,
the response has oldState and newState both null.¶
create: "Id[BlobCreateObject]|null"¶
A map of a creation id to a BlobCreateObject describing the blob to create, or null if no blobs are to be created.¶
update: A map of blobId to a patch object. The only property a
client can request to change is expires; all other properties can
only be set to their existing values. The server can honour the
requested expires (by not returning an expires in the updated
BlobObject) or return a different expires value if it applied a
different lifetime. The purpose of update is to "touch" the blob,
refreshing its lifetime on the server. If the blobId does not
exist, it MUST be reported in a notUpdated map with a "notFound"
SetError.¶
destroy: The server MUST reject any blob that is still referenced by another object with a "blobHasReference" SetError.¶
A BlobCreateObject has the following properties:¶
data: "DataSourceObject[]"¶
An array of zero or more DataSourceObjects. The new blob is formed by concatenating the data described by each DataSourceObject in the order given.¶
type: "String|null" (default: null)¶
A media type hint ([MEDIA-TYPES]) for the blob. The server MAY use this when setting the Content-Type of a subsequent download of the blob, but is not required to. The server MAY infer the type itself and override the client's hint.¶
size: "UnsignedInt|null" (default: null)¶
The size in octets of the blob being created: the total of the octets contributed by all the DataSourceObjects. If given, the server MUST reject the creation with an "invalidProperties" SetError if the resulting blob is a different size.¶
digest:<algorithm>: "String|null" (default: null)¶
The base64-encoded digest of the whole blob being created,
computed with the named algorithm, as returned by Blob/get. A
client MAY give any number of these. The server MUST verify each
one against the resulting blob, and if any does not match, MUST
reject the creation with an "invalidProperties" SetError and MUST NOT create the blob. A server MUST reject a digest whose
algorithm is not in its supportedDigestAlgorithms capability
with an "invalidProperties" SetError, so that a client never
believes an unchecked blob was verified.¶
This lets a client that uploaded content in several pieces, or sent it inline, confirm in the same call that the server holds exactly the bytes it meant to send, without a further Blob/get.¶
noPersist: "Boolean" (default: false)¶
If true, the resulting blob is ephemeral: it may be referenced
via creation id backreferences within the same JMAP request,
but the server is not required to persist it beyond the lifetime
of the request. The server MAY omit ephemeral blobs from the
created map of the response and from the createdIds of the
final Response object if it did not create a referenceable blob.
This allows servers to optimise pipelines where intermediate
blobs are never needed after the request completes.¶
Inline data (data:asText and data:asBase64) is part of the JMAP
request, so it counts towards the server's maxSizeRequest limit
([JMAP-CORE], Section 2), and base64 makes it about a third larger
than the octets it carries. Clients SHOULD consider using the upload
endpoint ([JMAP-CORE], Section 6.1) for blobs larger than a
megabyte, and referencing the uploaded blob by blobId.¶
Apart from oldState and newState, the response is a standard
Foo/set response. The objects in the
created and updated maps are BlobObjects. If an update set a
new expires, the server returns a BlobObject with the actual
expires that was applied; if the server accepted the requested
value it need not return an expires in the BlobObject.¶
A BlobObject has the following properties:¶
id: "BlobId"¶
The id of the blob.¶
type: "String|null"¶
The media type of the blob, or null if unknown.¶
size: "UnsignedInt|null"¶
The size of the blob in octets, or null if the server has not yet generated a Blob/convert result (see "Blob/convert").¶
expires: "UTCDate|null"¶
See "The expires Response Property" below.¶
Blob/get is defined under the urn:ietf:params:jmap:blob2 capability
and requires that capability in the request's using array.¶
This method retrieves blob data. It is a standard Foo/get method ([JMAP-CORE], Section 5.1) with the following additional parameters and a custom response.¶
In addition to the standard accountId, ids, and properties
arguments, Blob/get accepts:¶
offset: "UnsignedInt|null" (default: null)¶
The starting byte position within the blob. If null, defaults to 0.¶
length: "UnsignedInt|null" (default: null)¶
The number of bytes to return. If null, returns all bytes from
offset to the end of the blob.¶
dataSourceProperties: "String[]" (default: ["blobId", "offset", "length"])¶
If supplied, only the properties listed in the array are returned
for each DataSourceObject in the chunks array. This argument
has no effect unless chunks is included in properties. Available
properties include blobId, size, offset, length,
position, and digest:* values where * is an algorithm
name from the supportedDigestAlgorithms capability (e.g.
digest:sha-256).¶
The requestable properties in the properties array are:¶
data:asText¶
The data in the selected range as a UTF-8 string. If the
selected bytes are not valid UTF-8 (including truncation in the
middle of a multi-octet sequence), the response value is null
and isEncodingProblem is set to true.¶
data:asBase64¶
The data in the selected range encoded as base64 ([BASE64]).¶
data¶
Returns data:asText if the selected bytes are valid UTF-8,
otherwise returns data:asBase64.¶
digest:<algorithm>¶
The base64-encoded digest of the octets in the selected range,
computed using the named algorithm: exactly the octets that a
data property would return. If the range extends past the end
of the blob, that is the octets from offset to the end, or no
octets if offset is past the end.¶
size¶
The total size of the blob in octets, regardless of the range
selected. Servers SHOULD store the size of a blob in a form that
is cheap to read, and clients SHOULD request only size when
that is all they need, since fetching blob content can be much
more expensive for the server.¶
imageData¶
If the blob is an image or video type and the server can
extract metadata, this is an ImageData object. Otherwise
null. Only returned if explicitly requested in properties.
The server MAY need to read the blob content to compute this
data, so clients SHOULD only request it when needed. Since
blobs are immutable, the result for a given blobId will never
change, and the server SHOULD cache it.¶
An ImageData object has the following properties:¶
width: "UnsignedInt|null" Width of the image in pixels, if known.¶
height: "UnsignedInt|null" Height of the image in pixels, if known.¶
orientation: "UnsignedInt|null" EXIF orientation value (1-8) as defined in [EXIF]. If present, indicates how the image should be rotated/flipped for display.¶
date: "UTCDate|null" Date the image or video was captured (from EXIF DateTimeOriginal or equivalent), if present.¶
gps: "ImageGPS|null" GPS coordinates from EXIF, if present. An ImageGPS object has the following properties:¶
duration: "Number|null" Duration in seconds for video content, if known. This is a floating-point number to allow sub-second precision.¶
comment: "String|null" Embedded EXIF comment or description, if present. This is read-only — it reflects the metadata stored in the blob content and is not a user-editable property.¶
chunks¶
An array of DataSourceObjects describing the internal chunk
structure of the blob (see below). Only returned if explicitly
requested in properties. The properties returned for each
DataSourceObject are controlled by the dataSourceProperties
argument.¶
A digest:* name in properties or dataSourceProperties whose
algorithm is not in the server's supportedDigestAlgorithms
capability is rejected with an "invalidArguments" error.¶
If neither offset nor length are specified (i.e., both null),
the default properties are data and size. Otherwise, the client
MUST specify the properties to return.¶
The response contains a list of objects, one per blobId. Each
object contains the requested properties plus:¶
id: "BlobId"¶
The blobId of the blob.¶
isEncodingProblem: "Boolean" (default: false)¶
Set to true if data or data:asText was requested but the
selected range is not valid UTF-8.¶
isTruncated: "Boolean" (default: false)¶
Set to true if the selected range extends past the end of the
blob. In this case, only the bytes from offset to the end of
the blob (or an empty string if offset is past the end) are
returned. A null length selects everything from offset to
the end, so it sets isTruncated only if offset is past the
end of the blob.¶
chunks: "DataSourceObject[]"¶
Present only if chunks was requested in properties. An
array of one or more data source objects describing the internal
chunk structure of the blob. The blob is reconstructed by
concatenating the data from each data source object in the
listed order. The client MUST use the offset and length
of each DataSourceObject to determine which octets to read from
each chunk's underlying blob.¶
A server that does not store the blob in chunks returns
a single DataSourceObject covering the entire blob, with
blobId set to the blob's own id, offset 0, length and
size equal to the size of the blob, and position 0. This
allows clients to process the chunks array the same way
regardless of how the server stores blobs.¶
The response also contains:¶
Blob/lookup is defined under the urn:ietf:params:jmap:blob2
capability and requires that capability in the request's using
array.¶
This method provides a reverse lookup: given a set of blobIds, it returns which objects of specified data types reference those blobs.¶
Since different data types have different semantics of "contains",
the definition of "reference" is somewhat loose, but roughly means
"you could discover this blobId by looking at this object or at
other objects recursively contained within this object". For
example, with a server that supports [JMAP-MAIL], if any Email in
a Mailbox references a blob, then that Mailbox references the
blob, and any Thread containing such an Email references it too.
This does not work the other way: an Email that references a
Mailbox in its mailboxIds does not thereby reference the blobs of
other Emails in that Mailbox.¶
The method takes the following arguments:¶
accountId: "Id"¶
The id of the account to use.¶
typeNames: "String[]"¶
A list of data type names to search. Only type names listed in
the server's supportedTypeNames capability, and whose "JMAP
Data Types" registry entry has "Can Reference Blobs" set to
"Yes" (or private types defined to reference blobs), are valid. If a
type name is not known by the server or the associated capability
has not been requested in using, the server MUST return an
"unknownDataType" error.¶
ids: "BlobId[]"¶
The blobIds to look up.¶
The response contains:¶
accountId: "Id"¶
The id of the account used for the operation.¶
list: "BlobInfo[]"¶
A list of BlobInfo objects, one per blobId. A BlobInfo object has the following properties:¶
Access control: if a blob is not visible to the authenticated user or
does not exist on the server at all, the server MUST still return an
empty array for each type name. This ensures that the response does
not reveal whether the blob exists but is inaccessible to the user.
Likewise, the server MUST NOT return the id of an object that the
user could not discover by querying. For example, if an Email that
references the blob is in a Mailbox the user cannot see, that Email's
id MUST NOT appear in matchedIds.¶
The Blob/set response and the Blob/convert response include the following property on each BlobObject:¶
expires: "UTCDate|null"¶
A hint from the server indicating the likely availability of the blob. The blob is likely to remain available until this time, and likely not to be available after it. This is not a guarantee in either direction: the server MAY garbage collect the blob before this time if it is unreferenced, and MAY retain it longer. If null, the server does not have a specific expiry time for the blob.¶
Clients that need the blob to persist beyond the expires time
should reference it from a persistent object (e.g., a FileNode
or an Email) before it expires.¶
Blob/convert is defined under the urn:ietf:params:jmap:blob2
capability and requires that capability in the request's using
array.¶
Blob/convert performs server-side transformations on blobs. Like
Blob/set, it takes an accountId and a create argument that maps
creation ids to conversion request objects.¶
Each conversion request object MAY also include the following property:¶
noPersist: "Boolean" (default: false)
If true, the resulting blob is ephemeral: it may be referenced via
creation id backreferences within the same JMAP request, but the
server is not required to persist it beyond the lifetime of the
request. The server MAY omit ephemeral blobs from the created
map of the response and from the createdIds of the final Response
object if it did not create a referenceable blob.¶
Each conversion request object MUST contain exactly one of the following properties, which determines the type of conversion:¶
imageConvert: ImageConvertRecipe¶
archive: ArchiveRecipe¶
extract: ExtractRecipe¶
compress: CompressRecipe¶
decompress: DecompressRecipe¶
delta: DeltaRecipe¶
patch: PatchRecipe¶
The response has the same structure as Blob/set: a created map of
creation id to a BlobObject for each successful conversion, and a
notCreated map of creation id to a SetError object for each failed
conversion. The id is the blobId of the created blob. The
response also includes an expires property on each BlobObject (see
"The expires Response Property" above).¶
If the conversion completed but encountered problems (e.g., a corrupt archive where some entries extracted successfully but others did not, or a truncated compressed stream that partially decompressed), the response for that creation also includes:¶
isIncomplete: "Boolean" (default: false) If true, the conversion did not complete cleanly. The output blob may be partial or missing some content.¶
description: "String|null" A human-readable description of the problem, if any.¶
If the conversion failed entirely and no usable output could be
produced, the server MUST return a conversionFailed SetError
in the notCreated map.¶
Creation id backreferences (using the # prefix) resolve to
the id of the created blob and may be used in subsequent
conversions within the same Blob/convert call or in later
method calls within the same JMAP request.¶
A server MAY return a blobId for a conversion result without
immediately generating the output data. In this case the server
MUST generate the data when the blob is later accessed (e.g., via
a download request or as input to another operation). This allows
the server to respond quickly to Blob/convert requests while
deferring expensive work such as image resizing or archive
creation. The returned blobId MUST be usable in all contexts
where a regular blobId is accepted. If the exact size of the output
is not yet known, the server returns a null size for that
creation. If the deferred generation later fails (e.g., the source
blob has expired), the server treats the blob as not existing: a
download returns 404 (Not Found), Blob/get reports it in notFound,
and a creation in Blob/set or Blob/convert that references it fails
with a "notFound" SetError. If a client later requests the size property via
Blob/get for a deferred blob, the server MUST generate the blob
at that point and return the actual size.¶
The server MUST resolve the order of dependencies between entries
in the create map and process them in an order such that all
backreferences are satisfied. If a dependency cycle is detected,
all members of the cycle MUST be rejected with an
"invalidProperties" error.¶
An ImageConvertRecipe converts an image blob to a different format or size. It is an object with the following properties:¶
blobId: "BlobId" The blobId of the source image.¶
type: "String"
Media type ([MEDIA-TYPES]) of the image to create (e.g.
"image/png"). MUST be one of the values in the server's
supportedImageWriteTypes capability. The source image MUST
be in a format listed in supportedImageReadTypes.¶
width: "UnsignedInt|null"
Maximum width in pixels of the image to create. If null, the
server preserves the source width (or scales proportionally if
only height is given).¶
height: "UnsignedInt|null"
Maximum height in pixels of the image to create. If null, the
server preserves the source height (or scales proportionally if
only width is given).¶
ignoreAspect: "Boolean|null" If true, resize to exactly the given width and height, even if the aspect ratio is changed. If null or false, the image is scaled to fit within the given dimensions while preserving the aspect ratio.¶
quality: "UnsignedInt|null" Compression quality for lossy formats, as a value from 1 (lowest quality, smallest file) to 100 (highest quality, largest file). Only meaningful for formats that support lossy compression such as image/jpeg and image/webp. If null, the server selects a sensible default.¶
colorSpace: "String|null" The color space for the output image. Defined values are "sRGB" and "grayscale". If null, the server preserves the source image's color space where possible.¶
background: "String|null" A fill color to use when the source image has transparency but the target format does not support it (e.g. converting PNG to JPEG). The value is a CSS-style hex color string (e.g. "#ffffff" for white). If null, the server selects a sensible default (typically white).¶
stripMetadata: "Boolean|null" If true, strip image metadata such as EXIF, XMP, and IPTC data from the output. If null or false, the server preserves metadata where the target format supports it.¶
autoOrient: "Boolean|null" If true, automatically rotate and flip the image according to its EXIF orientation tag, then reset the tag. If null or false, the image data is not reoriented.¶
Errors:¶
"notFound" — the referenced blobId does not exist.¶
"invalidProperties" — the type is not in
supportedImageWriteTypes, or the source blob is not in a format
listed in supportedImageReadTypes.¶
"tooLarge" — the source blob exceeds maxConvertSize, or the
requested dimensions exceed maxImageDimension.¶
"conversionFailed" — the image conversion failed entirely.¶
An ArchiveRecipe creates an archive blob from a list of entries. It is an object with the following properties:¶
type: "String"
The media type ([MEDIA-TYPES]) of the archive to create. MUST be
one of the values in the server's supportedArchiveTypes capability.¶
entries: "ArchiveEntry[]" An array of ArchiveEntry objects describing the contents of the archive.¶
Errors:¶
"notFound" — a referenced entry blobId does not exist.¶
"invalidProperties" — the type is not in supportedArchiveTypes;
an entry has an unsupported entryType for the archive format; or
a required field (e.g. linkTarget for symlink entries) is missing.¶
"tooLarge" — the number of entries exceeds maxArchiveEntries,
or a referenced blob exceeds maxConvertSize.¶
"conversionFailed" — the archive creation failed entirely.¶
An ArchiveEntry describes a single entry in an archive. It is used both as input (in ArchiveRecipe) and as output (in ExtractRecipe results). It is an object with the following properties:¶
name: "String" The path of the entry within the archive. Directory entries MUST have a name ending with "/".¶
blobId: "BlobId|null" The blobId of the content for this entry. MUST be non-null for file entries. MUST be null or absent for directory, symlink, hardlink, fifo, and device entries. Violating these constraints is an "invalidProperties" error.¶
entryType: "String|null" The type of the entry. If null, defaults to "file". Defined values are "file" (a regular file, the default), "directory", "symlink" (a symbolic link), "hardlink" (a hard link), "fifo" (a named pipe), "blockDevice" (a block device node), and "charDevice" (a character device node). The server MUST reject entries with unsupported types for the chosen archive format with an "invalidProperties" error.¶
modified: "UTCDate|null" The modification time of the entry. If null, defaults to the current server time.¶
created: "UTCDate|null" The creation time of the entry, or null if it has none. Formats that cannot record a creation time ignore it.¶
linkTarget: "String|null" The target path for symlink and hardlink entries. MUST be non-null when entryType is "symlink" or "hardlink". MUST be null for all other entry types.¶
mode: "String|null" Unix file permissions as an octal string (e.g. "0755", "0644"). If null, the server chooses a reasonable default. This is represented as a string rather than an integer to avoid ambiguity between octal and decimal interpretation.¶
uid: "UnsignedInt|null" The numeric user ID of the entry owner.¶
gid: "UnsignedInt|null" The numeric group ID of the entry owner.¶
ownerName: "String|null" The user name of the entry owner.¶
groupName: "String|null" The group name of the entry owner.¶
devMajor: "UnsignedInt|null" The major device number for "blockDevice" and "charDevice" entries.¶
devMinor: "UnsignedInt|null" The minor device number for "blockDevice" and "charDevice" entries.¶
comment: "String|null" A comment string for this entry.¶
compressionMethod: "String|null" The per-entry compression method. Defined values are "store" (no compression) and "deflate". If null, the server chooses a reasonable default.¶
The zip format only supports "file" and "directory" entry types.
The comment and compressionMethod properties are only meaningful
for zip archives. The created time is stored in an extra field.
The mode, uid, gid, ownerName,
groupName, devMajor, and devMinor properties are ignored.¶
The tar format supports all entry types. The mode, uid, gid,
ownerName, groupName, devMajor, and devMinor properties are
meaningful for tar archives. The created, comment, and
compressionMethod properties are ignored.¶
Tar archives are not inherently compressed. To create a compressed tar archive (e.g. a .tar.gz file), first create the tar archive using ArchiveRecipe, then compress the result using CompressRecipe (see "Creating a Compressed Tar Archive" in "Examples").¶
The cpio format supports all entry types except that ownerName and
groupName are not supported. The created, comment, and
compressionMethod properties are ignored.¶
An ExtractRecipe extracts the entry listing from an existing archive blob. It is an object with the following properties:¶
blobId: "BlobId" The blobId of the archive to extract.¶
type: "String|null" The media type of the archive format. If null, the server SHOULD attempt to auto-detect the format from the blob content (e.g. by inspecting magic bytes). If auto-detection fails, the server MUST return an "unknownFormat" error.¶
An ExtractRecipe creates no blob. The id, type, and size in
its result are those of the archive blob that was extracted, and a
backreference to its creation id resolves to that blob. noPersist
has no effect on an ExtractRecipe. A successful ExtractRecipe
result also includes:¶
entries: "ArchiveEntry[]" An array of ArchiveEntry objects describing the contents of the archive. Each file entry will have a blobId that can be used to access the content of that entry.¶
Errors:¶
"notFound" — the referenced blobId does not exist.¶
"unknownFormat" — the server could not determine or does not support the archive format.¶
"invalidProperties" — the type is not in supportedExtractTypes.¶
"tooLarge" — the source blob exceeds maxConvertSize.¶
"conversionFailed" — the extraction failed entirely.¶
A CompressRecipe compresses a blob using a specified compression algorithm. It is an object with the following properties:¶
blobId: "BlobId" The blobId of the data to compress.¶
type: "String"
The media type ([MEDIA-TYPES]) of the compression format to use.
MUST be one of the values in the server's supportedCompressTypes
capability.¶
level: "UnsignedInt|null" The compression level, where higher values produce smaller output at the cost of more CPU time. If null, the server uses the format's default level. The valid range depends on the format; if the requested level is outside the valid range, the server SHOULD use the nearest valid value.¶
checksum: "Boolean|null" If true, include an integrity checksum in the compressed output. If null, the server uses the format's default behaviour.¶
Errors:¶
"notFound" — the referenced blobId does not exist.¶
"invalidProperties" — the type is not in supportedCompressTypes.¶
"tooLarge" — the source blob exceeds maxConvertSize.¶
"conversionFailed" — the compression failed entirely.¶
Compression level ranges from 1 (fastest) to 9 (best compression).
The default is typically 6. Gzip always includes a CRC-32 checksum;
the checksum property is ignored.¶
Compression level ranges from 1 (fastest, 100k block size) to 9
(best compression, 900k block size). The default is typically 9.
Bzip2 always includes a CRC-32 checksum; the checksum property is
ignored.¶
Compression level ranges from 0 (fastest) to 9 (best compression).
The default is typically 6. Xz always includes an integrity check;
if checksum is true the server SHOULD use SHA-256, otherwise CRC-64
is used by default.¶
Compression level ranges from 1 (fastest) to 22 (best compression).
The default is typically 3. If checksum is true, an xxHash-64
checksum is included in the frame; the default is false.¶
A DecompressRecipe decompresses a compressed blob. It is an object with the following properties:¶
blobId: "BlobId" The blobId of the compressed data to decompress.¶
type: "String|null" The media type of the compression format. If null, the server SHOULD attempt to auto-detect the format from magic bytes. If auto-detection fails, the server MUST return an "unknownFormat" error.¶
Errors:¶
"notFound" — the referenced blobId does not exist.¶
"unknownFormat" — the server could not determine or does not support the compression format.¶
"invalidProperties" — the type is not in supportedDecompressTypes.¶
"tooLarge" — the source blob exceeds maxConvertSize.¶
"conversionFailed" — the decompression failed entirely.¶
A DeltaRecipe computes a delta between two blobs. It is an object with the following properties:¶
blobId: "BlobId" The blobId of the original (base) blob.¶
newBlobId: "BlobId" The blobId of the new blob to compare against.¶
type: "String"
The media type of the delta format to produce. MUST be one of the
values in the server's supportedDeltaTypes capability.¶
The result blob is the computed delta, which can be applied to the base blob using PatchRecipe to reconstruct the new blob.¶
Errors:¶
"notFound" — a referenced blobId does not exist.¶
"invalidProperties" — the type is not in supportedDeltaTypes.¶
"tooLarge" — a referenced blob exceeds maxConvertSize.¶
"conversionFailed" — the delta computation failed entirely.¶
The server computes an rdiff signature of the base blob and then generates a delta against the new blob. The resulting delta blob can only be applied to the exact base blob used to generate it.¶
The server produces a bsdiff-format patch. Both blobs must fit in memory; servers MAY reject very large blobs with a "tooLarge" error.¶
The server produces a unified diff. Both blobs are interpreted as text. If either blob contains content that cannot be interpreted as text, the server MUST return an "unknownFormat" error.¶
A PatchRecipe applies a delta to a base blob to produce a new blob. It is an object with the following properties:¶
blobId: "BlobId" The blobId of the base blob to patch.¶
deltaBlobId: "BlobId" The blobId of the delta to apply.¶
deltaType: "String"
The media type of the delta format. MUST be one of the values in
the server's supportedPatchTypes capability.¶
The result blob is the patched output.¶
Errors:¶
"notFound" — a referenced blobId does not exist.¶
"unknownFormat" — the delta blob is not valid for the specified format (e.g., corrupt or malformed delta data).¶
"invalidProperties" — the deltaType is not in supportedPatchTypes.¶
"tooLarge" — a referenced blob exceeds maxConvertSize.¶
"conversionFailed" — the patch application failed entirely.¶
This example creates a blob from inline text data:¶
[["Blob/set", {
"accountId": "abc",
"create": {
"b1": {
"data": [{"data:asText": "Hello, world!"}],
"type": "text/plain"
}
}
}, "0"]]
¶
The response:¶
[["Blob/set", {
"accountId": "abc",
"created": {
"b1": {
"id": "Babc123",
"type": "text/plain",
"size": 13
}
},
"notCreated": null
}, "0"]]
¶
This example fetches a blob's chunk structure with offsets, sizes, and SHA-256 digests:¶
[["Blob/get", {
"accountId": "abc",
"ids": ["B1a2b3c"],
"properties": ["size", "chunks"],
"dataSourceProperties": [
"blobId", "size", "offset", "length", "position",
"digest:sha-256"
]
}, "0"]]
¶
The response shows the blob is stored as two chunks:¶
[["Blob/get", {
"accountId": "abc",
"list": [{
"id": "B1a2b3c",
"size": 10485760,
"chunks": [
{
"blobId": "Bchunk1",
"size": 5242880,
"offset": 0,
"length": 5242880,
"position": 0,
"digest:sha-256": "a1b2c3..."
},
{
"blobId": "Bchunk2",
"size": 5242880,
"offset": 0,
"length": 5242880,
"position": 5242880,
"digest:sha-256": "d4e5f6..."
}
]
}],
"notFound": []
}, "0"]]
¶
The position values show where each chunk fits in the assembled
blob, and the digest:sha-256 values can be used to verify chunk
integrity.¶
This example finds which Mailbox and Email objects reference two blobs:¶
[["Blob/lookup", {
"accountId": "abc",
"typeNames": ["Mailbox", "Email"],
"ids": ["Bfirst", "Bsecond"]
}, "0"]]
¶
The response shows that the first blob is referenced by two emails in the same mailbox, and the second blob is not referenced by any objects of the requested types (or is not visible to the user):¶
[["Blob/lookup", {
"accountId": "abc",
"list": [
{
"id": "Bfirst",
"matchedIds": {
"Mailbox": ["M1"],
"Email": ["E1", "E2"]
}
},
{
"id": "Bsecond",
"matchedIds": {
"Mailbox": [],
"Email": []
}
}
]
}, "0"]]
¶
This example creates a zip file containing an HTML document, a CSS file, and a JPEG image:¶
[["Blob/convert", {
"accountId": "abc",
"create": {
"z1": {
"archive": {
"type": "application/zip",
"entries": [
{
"name": "site/index.html",
"blobId": "Baaaa"
},
{
"name": "site/style.css",
"blobId": "Bbbbb"
},
{
"name": "site/photo.jpg",
"blobId": "Bcccc"
}
]
}
}
}
}, "0"]]
¶
The response includes the blobId, type, and size of the created archive:¶
[["Blob/convert", {
"accountId": "abc",
"created": {
"z1": {
"id": "B9f2a4e",
"type": "application/zip",
"size": 104857
}
},
"notCreated": {}
}, "0"]]
¶
This example creates a .tar.gz file from the same three files.
The intermediate tar blob uses noPersist since only the final
compressed result is needed:¶
[["Blob/convert", {
"accountId": "abc",
"create": {
"t1": {
"noPersist": true,
"archive": {
"type": "application/x-tar",
"entries": [
{
"name": "site/index.html",
"blobId": "Baaaa",
"modified": "2026-03-01T12:00:00Z",
"mode": "0644"
},
{
"name": "site/style.css",
"blobId": "Bbbbb",
"modified": "2026-03-01T12:00:00Z",
"mode": "0644"
},
{
"name": "site/photo.jpg",
"blobId": "Bcccc",
"modified": "2026-02-15T09:30:00Z",
"mode": "0644"
}
]
}
},
"t2": {
"compress": {
"blobId": "#t1",
"type": "application/gzip"
}
}
}
}, "0"]]
¶
Because "t1" was created with noPersist, the server may omit it
from the response:¶
[["Blob/convert", {
"accountId": "abc",
"created": {
"t2": {
"id": "Bd81c7f",
"type": "application/gzip",
"size": 98304
}
},
"notCreated": {}
}, "0"]]
¶
This example decompresses and extracts a .tar.gz file to discover
its contents. The intermediate decompressed tar blob uses
noPersist:¶
[["Blob/convert", {
"accountId": "abc",
"create": {
"u1": {
"noPersist": true,
"decompress": {
"blobId": "Bd81c7f",
"type": "application/gzip"
}
},
"u2": {
"extract": {
"blobId": "#u1",
"type": "application/x-tar"
}
}
}
}, "0"]]
¶
The response for the ExtractRecipe gives the id, type, and
size of the archive it extracted, which here is the decompressed
tar created by "u1", and an entries array listing the archive
contents, with a blobId for each file entry:¶
[["Blob/convert", {
"accountId": "abc",
"created": {
"u2": {
"id": "Be3a901",
"type": "application/x-tar",
"size": 102400,
"entries": [
{
"name": "site/index.html",
"blobId": "Bdd001",
"entryType": "file",
"modified": "2026-03-01T12:00:00Z",
"mode": "0644"
},
{
"name": "site/style.css",
"blobId": "Bdd002",
"entryType": "file",
"modified": "2026-03-01T12:00:00Z",
"mode": "0644"
},
{
"name": "site/photo.jpg",
"blobId": "Bdd003",
"entryType": "file",
"modified": "2026-02-15T09:30:00Z",
"mode": "0644"
}
]
}
},
"notCreated": {}
}, "0"]]
¶
The returned blobIds ("Bdd001", "Bdd002", "Bdd003") can be used to download individual files or as inputs to further Blob/convert operations.¶
This example computes a unified diff between two versions of a text file:¶
[["Blob/convert", {
"accountId": "abc",
"create": {
"d1": {
"delta": {
"blobId": "BoldVersion",
"newBlobId": "BnewVersion",
"type": "text/x-diff"
}
}
}
}, "0"]]
¶
The response contains the delta blob:¶
[["Blob/convert", {
"accountId": "abc",
"created": {
"d1": {
"id": "Bdelta789",
"type": "text/x-diff",
"size": 1234
}
},
"notCreated": {}
}, "0"]]
¶
The delta can later be applied to the original blob to reconstruct the new version:¶
[["Blob/convert", {
"accountId": "abc",
"create": {
"p1": {
"patch": {
"blobId": "BoldVersion",
"deltaBlobId": "Bdelta789",
"deltaType": "text/x-diff"
}
}
}
}, "0"]]
¶
The result is a blob identical to "BnewVersion":¶
[["Blob/convert", {
"accountId": "abc",
"created": {
"p1": {
"id": "Breconstructed",
"type": "application/octet-stream",
"size": 48576
}
},
"notCreated": {}
}, "0"]]
¶
All security considerations from [JMAP-CORE] apply to this document.¶
Servers MUST NOT allow a client to access blob data that the authenticated user does not have permission to access. When a DataSourceObject references a blobId, the server MUST verify that the requesting user has access to that blob.¶
Clients that create blobs from inline data (data:asText or data:asBase64) MUST NOT assume that the server will accept all possible values. Servers MUST reject malformed inputs as specified in the DataSourceObject definition.¶
JSON parsers are not all consistent in handling non-UTF-8 data, and
JMAP requires all JSON to be UTF-8, so a server MUST return null for
data:asText (setting isEncodingProblem) rather than return octets
that are not valid UTF-8.¶
The server MUST NOT trust that the data given to Blob/set is a well-formed instance of its stated media type. If the server parses a blob — to convert it, extract an archive, or read image metadata — it SHOULD only use hardened parsers designed to deal with arbitrary untrusted data. The server SHOULD NOT reject data on the grounds that it is not a valid specimen of the stated type.¶
Several operations defined in this document can consume significant server resources.¶
Archive and compression operations may require substantial CPU and memory. Servers SHOULD impose reasonable limits on archive size, number of entries, nesting depth of archives within archives, compression ratios (to mitigate zip bomb attacks), and total processing time. Servers SHOULD reject requests that would exceed these limits with a "tooLarge" or "serverFail" error as appropriate.¶
Image conversion can also consume significant resources, especially for very large images or high output dimensions.¶
Digest algorithms can be expensive to calculate over large blobs. Servers that share resources between users SHOULD track resource use by clients and rate-limit expensive operations, such as computing digests in Blob/get or verifying them in Blob/set, to avoid resource starvation.¶
Delta and patch operations can also be resource-intensive, particularly for large blobs. Servers SHOULD impose limits on the size of blobs that can be used as inputs to these operations.¶
Servers SHOULD advertise their limits via the maxConvertSize,
maxArchiveEntries, and maxImageDimension capability properties
so that clients can avoid making requests that will be rejected.
Even when these properties are not advertised, servers SHOULD set
sensible internal limits and reject requests that exceed them.¶
Archive formats allow entry names containing path separators and relative path components such as "../". Malicious archives may use names like "../../etc/passwd" to attempt directory traversal. While ExtractRecipe only returns entry metadata and blob references (not files on the server filesystem), clients that extract archive contents to a filesystem MUST validate entry names and reject or sanitize paths containing ".." components or absolute paths. Servers SHOULD reject ArchiveRecipe requests containing entry names with ".." path components.¶
The ability to split content into multiple blobs, recombine them via Blob/set, and apply delta patches may be used to bypass security scanners that inspect blob content: carefully chosen data sources can recreate dangerous content on the far side of a scanner watching the upload endpoint. Servers that perform content scanning SHOULD provide a hook to scan the blob that results from concatenating the data sources in Blob/set, in the same way as blobs arriving through the upload endpoint, and SHOULD scan the output of Blob/convert operations as well as the inputs.¶
IANA is requested to register the "Blob Management" Capability as follows:¶
Capability Name: urn:ietf:params:jmap:blob2¶
Intended use: common¶
Change Controller: IETF¶
Specification document: this document¶
Security and privacy considerations: this document, Security Considerations¶
IANA is requested to update the "urn:ietf:params:jmap:blob" capability, registered by [JMAP-BLOB], as follows:¶
Capability Name: urn:ietf:params:jmap:blob¶
Specification document: [JMAP-BLOB], superseded by this document¶
Intended use: obsolete¶
Change Controller: IETF¶
Security and privacy considerations: [JMAP-BLOB], Section 5¶
IANA is requested to update the "unknownDataType" entry in the "JMAP Error Codes" registry, registered by [JMAP-BLOB], as follows:¶
JMAP Error Code: unknownDataType¶
Intended use: common¶
Change Controller: IETF¶
Description: The server does not recognise the data type specified
in the typeNames array of a Blob/lookup request, or the capability
required to use that data type has not been included in the using
array of the request.¶
IANA is requested to register the following entries in the "JMAP Error Codes" registry:¶
JMAP Error Code: unknownFormat¶
Intended use: common¶
Change Controller: IETF¶
Description: The server could not determine the format of the blob, or the detected format is not supported. This error is returned when auto-detection of archive or compression format fails, or when the blob content does not match the specified format.¶
Reference: this document¶
JMAP Error Code: blobHasReference¶
Intended use: common¶
Change Controller: IETF¶
Description: The blob cannot be destroyed because it is still referenced by one or more objects. The client can use Blob/lookup to discover which objects reference the blob.¶
Reference: this document¶
IANA is requested to update the reference for the "JMAP Data Types" registry to this document (replacing the reference to [JMAP-BLOB]). The registry's fields, registration policy, and instructions to the designated experts below are carried forward unchanged from [JMAP-BLOB], so that they remain defined in a current document. The existing entries are unchanged.¶
Each entry has the following fields:¶
Type Name: the name of the data type, as used in typeNames in
Blob/lookup and as a key in a StateChange object ([JMAP-CORE],
Section 7.1).¶
Can Reference Blobs: "Yes" if objects of this type can reference blobs (see "Blob/lookup"), otherwise "No".¶
Can Use for State Change: "Yes" if the type name can appear in a StateChange object, otherwise "No".¶
Capability: the capability URI that a client includes in using to
use this data type.¶
Reference: the document that defines the data type.¶
The registration policy for this registry is "Specification Required" [IANA-GUIDELINES]. Either an RFC or a similarly stable reference document defines a JMAP Data Type and associated capability.¶
IANA will appoint designated experts to review requests for additions to this registry, with guidance to allow any registration that provides a stable document describing the capability and control over the URI namespace to which the capability URI points.¶
EDITOR: please remove this section before publication.¶
The source of this document exists on github at: https://github.com/brong/draft-gondwana-jmap-blobext/¶
draft-ietf-jmap-blobext-03¶
Added created to ArchiveEntry.¶
Carried over from RFC9404 what this document had lost: Blob/lookup
does not return ids of objects the user cannot discover, the
meaning of "reference" and the "Can Reference Blobs" restriction,
the server's handling of untrusted blob data, scanning of
concatenated blobs, the cost of digests, the request size limit
for inline data, and the hints about fetching size.¶
Digest algorithm names come from the "Hash Algorithms for HTTP Digest Fields" registry of RFC9530 (which replaces the RFC3230 registry RFC9404 used), not the "Hash Function Textual Names" registry, where SHA-1 is "sha-1". Servers list algorithms in their order of preference, only ones they can compute efficiently, and clients use the first they support; "sha" (deprecated there) remains allowed.¶
A Blob/get digest is over exactly the octets returned for the range, including when the range runs past the end of the blob.¶
Added optional size and digest:<algorithm> properties to the
BlobCreateObject, checked against the whole blob being created, so
a client can verify an assembled or inline upload in the same
call. (RFC9404 defined neither; per-source values were already
checked on each DataSourceObject.)¶
Restated the registration policy and designated expert guidance for the "JMAP Data Types" registry, so they are not left only in the obsoleted RFC9404.¶
Blobs have no state, as in RFC9404: Blob/set takes no ifInState
and returns null oldState and newState. (Thanks to Mark
Atwood.)¶
The dataSourceProperties default is ["blobId", "offset",
"length"], so chunks fed back into Blob/set keep their ranges.
(Thanks to Mark Atwood.)¶
Named the SetErrors for Blob/set: "invalidProperties" for an
invalid DataSourceObject, "tooLarge" for exceeding
maxDataSources or maxSizeBlobSet, and "notFound" for an unknown
blobId. An unsupported digest:* name in Blob/get is
"invalidArguments". (Thanks to Mark Atwood.)¶
BlobObject size is null until a deferred Blob/convert result is
generated, and a deferred result whose generation fails is treated
as not existing everywhere. (Thanks to Mark Atwood.)¶
The id, type, and size of an ExtractRecipe result are those
of the archive it extracted. (Thanks to Mark Atwood.)¶
IANA: the "urn:ietf:params:jmap:blob" capability is marked obsolete, "unknownDataType" is updated rather than registered again, and the "JMAP Data Types" registry's fields are defined here. RFC9404 is now an informative reference. (Thanks to Mark Atwood.)¶
Editorial: removed a duplicate .tar.gz example, used "media type" throughout, added a reference for EXIF, and removed a stray back matter marker from the text. (Thanks to Mark Atwood.)¶
draft-ietf-jmap-blobext-02¶
Listed chunks as a requestable Blob/get property and fixed the
chunks example to request it.¶
Explained the purpose of the DataSourceObject position property.¶
Servers that do not store blobs in chunks return a single chunk covering the whole blob.¶
Added a Blob/lookup example.¶
Extended acknowledgements.¶
draft-ietf-jmap-blobext-01¶
Added per-account uploadUrl capability property, allowing
servers to direct uploads for individual accounts to a different
endpoint. Uploads to this URL return an expires property in
addition to the standard upload response fields.¶
Blob/set is now a standard Foo/set method with state strings
(ifInState/oldState/newState). Renamed UploadObject to
BlobCreateObject. The expires property can be set via update.¶
Removed hardcoded type value lists from normative text (supportedArchiveTypes, supportedCompressTypes, supportedDeltaTypes, ArchiveRecipe, CompressRecipe). Values are now described as media types per [MEDIA-TYPES]. Format-specific considerations subsections remain as informative examples.¶
Obsoletes RFC9404: incorporated all content from RFC9404 into this document, making it self-contained.¶
Renamed Blob/upload to Blob/set, making it a standard Foo/set operation with create, update, and destroy at the top level.¶
Added urn:ietf:params:jmap:blob capability definition (previously
defined only in RFC9404).¶
Added Blob/lookup method (previously defined only in RFC9404).¶
Added full DataSourceObject definition (merging RFC9404 base definition with blobext extensions).¶
Added full Blob/get definition (merging RFC9404 base definition with blobext extensions).¶
Added inline blob creation example.¶
Updated IANA section to include blob capability, unknownDataType error code, and JMAP Data Types registry.¶
draft-ietf-jmap-blobext-00¶
No changes, just uploading with the new name¶
draft-gondwana-jmap-blobext-06¶
Removed resumableUploadUrl capability property.¶
Added isIncomplete and description response properties for partial conversion results.¶
Added conversionFailed error code for total conversion failure.¶
Made expires always present (null if indeterminate) rather than optional.¶
Split supportedImageTypes into supportedImageReadTypes and supportedImageWriteTypes.¶
Made all supported type lists nullable (null = not supported).¶
Changed imageData duration from UnsignedInt to Number for sub-second precision.¶
Clarified imageData comment as read-only.¶
Added EXIF orientation reference and digest algorithm registry reference.¶
Fixed grammar and consistent error ordering across recipes.¶
Structural reorder: DataSourceObject before Blob/get, ArchiveEntry promoted to peer section.¶
draft-gondwana-jmap-blobext-05¶
Added imageData property to Blob/get for image/video metadata extraction (dimensions, orientation, date, GPS, duration).¶
Renamed UnArchiveRecipe to ExtractRecipe and UnCompressRecipe to DecompressRecipe.¶
Split capability lists into separate create/extract pairs: supportedArchiveTypes/supportedExtractTypes, supportedCompressTypes/supportedDecompressTypes, supportedDeltaTypes/supportedPatchTypes.¶
draft-gondwana-jmap-blobext-04¶
Moved noPersist from top-level Blob/upload property to per-item in the create map, for consistency with Blob/convert.¶
draft-gondwana-jmap-blobext-03¶
Added update (touch) and destroy operations to Blob/upload.¶
draft-gondwana-jmap-blobext-02¶
Replaced RdiffRecipe with generic DeltaRecipe and PatchRecipe using media types (rdiff, bsdiff, unified diff).¶
Replaced supportsRdiff with supportedPatchTypes capability.¶
Clarified chunkSize as a hint, not a definitive statement.¶
Added lazy generation text for Blob/convert (deferred output).¶
Defined "expires" response property for blob creation responses.¶
draft-gondwana-jmap-blobext-01¶
Added Blob/convert method with recipes for image conversion, archiving, compression, and rdiff.¶
Added noPersist option to Blob/upload and Blob/convert for ephemeral intermediate blobs in pipelines.¶
Removed rdiffSignature and rdiffPatch from Blob/get and DataSourceObject.¶
Fleshed out capability object with supported type lists.¶
Added capability and method examples.¶
Expanded Security Considerations.¶
Updated IANA registrations with error codes and corrected capability URI.¶
Added limit capability properties: maxConvertSize, maxArchiveEntries, maxImageDimension, and supportsRdiff.¶
Added dependency resolution requirement for Blob/convert create map with cycle detection.¶
Now updates both RFC 8620 and RFC 9404.¶
draft-gondwana-jmap-blobext-00¶
initial proposal¶
The authors would like to thank Mauro De Gennaro, Ben Bucksch, Ricardo Signes, Neil Jenkins, Ken Murchison, Arnt Gulbrandsen, Seph Gentle, Matthew Horsfall, Mark Atwood, and the members of the JMAP Working Group at the IETF for their contributions and feedback.¶