| Internet-Draft | JMAP Object Metadata | October 2026 |
| De Gennaro | Expires 20 April 2027 | [Page] |
- Workgroup:
- JMAP
- Internet-Draft:
- draft-ietf-jmap-metadata-03
- Published:
- Intended Status:
- Standards Track
- Expires:
JMAP Object Metadata
Abstract
This document defines an extension to the JSON Meta Application Protocol (JMAP) that lets clients and servers attach metadata to objects of existing JMAP data types. Each data type the server lists gains two new properties, metadata and privateMetadata, whose values are objects keyed by namespace identifier. A namespace identifier is either a name registered with IANA or a domain name controlled by the vendor providing the namespace; the latter allows vendors and applications to extend the metadata schema without prior coordination. Because metadata is carried as a property on the related object, clients use the existing /get, /set, /changes, and /query methods to read, modify, and synchronize it.¶
Status of This Memo
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 20 April 2027.¶
Copyright Notice
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.¶
1. Introduction
The JSON Meta Application Protocol (JMAP) [RFC8620] is a generic protocol for synchronizing data, such as mail, calendars or contacts, between a client and a server. It is optimized for mobile and web environments, and aims to provide a consistent interface to different data types.¶
Metadata, or annotations, are auxiliary data elements that provide additional context, user-defined properties, or system-specific information about primary data objects. They enable user annotations, application-specific settings, collaborative tagging, and similar functionality. Other protocols have addressed this need with mechanisms such as the IMAP METADATA extension [RFC5464] and WebDAV dead properties [RFC4918]; this specification provides an analogous facility within JMAP. The term "metadata" in this document refers only to the properties defined here, not to the Email properties described in Section 4.1.1 of [RFC8621].¶
This document defines a uniform mechanism for managing such metadata. Each listed JMAP data type gains a metadata property (shared metadata) and, optionally, a privateMetadata property (per-user metadata). The value of each property is an object keyed by namespace identifier. This document defines no initial namespaces; vendors use domain-name identifiers for proprietary extensions, and additional registered identifiers may be defined by future specifications.¶
Because metadata is carried as a property on the related object, the existing JMAP methods for that data type apply unchanged. Clients fetch metadata with the type's /get method, modify it through /set patches, learn of changes through /changes, and search across metadata and primary properties together through /query.¶
1.1. Notational Conventions
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 definitions of JSON keys and datatypes in this document follow the conventions described in Section 1.1 of [RFC8620]. The terms "Foo", "SetError", "PatchObject" and "state string" are used as defined in [RFC8620]. This document also uses the following terms:¶
-
related object: the JMAP object carrying the
metadataandprivateMetadataproperties.¶ -
listed data type: a data type that appears in the
dataTypesfield of the account capability (Section 1.2.1).¶ -
user: the authenticated identity of the session, which is not necessarily the owner of the account.¶
1.2. Addition to the Capabilities Object
The capabilities object is returned as part of the JMAP Session object; see Section 2 of [RFC8620]. This document defines one additional capability URI.¶
1.2.1. urn:ietf:params:jmap:metadata
This capability represents support for the metadata extension defined in this document. Servers that include this capability provide the metadata and (optionally) privateMetadata properties on the data types they list.¶
The value of this property in the JMAP Session "capabilities" property is an empty object.¶
The value of this property in an account's "accountCapabilities" property is an object containing the following field:¶
-
dataTypes:
String[DataTypeMetadataInfo] -
An object whose keys are the names of JMAP data types for which the server supports metadata in this account. A type that does not appear in this object does not gain the
metadataorprivateMetadataproperties in this account. The value associated with each type is aDataTypeMetadataInfoobject as defined below.¶
A DataTypeMetadataInfo object has the following fields:¶
-
namespaces:
String[] -
The set of IANA-registered metadata namespace names (as defined in Section 8.2) that the server supports on this data type. Each value MUST be a registered name (Section 2.1). A registered namespace MUST NOT be listed for a data type, or written under a property, that its registration does not allow. Vendor (domain-name) namespaces MUST NOT appear in this list; their support is signalled by
supportsVendorNamespacesinstead.¶ -
supportsVendorNamespaces:
Boolean(default: false) -
Indicates whether the server accepts vendor (domain-name) namespaces on this data type. If true, any well-formed domain-name namespace (Section 2.1) may be written by clients, subject to the other constraints of this specification (access control,
maxDepth, quota). If false, any/setoperation that targets a domain-name namespace MUST be rejected with an "invalidProperties" SetError, and any subselector or filter condition referencing such a namespace MUST be treated as unsupported per the rules of Section 3.1 and Section 3.6.¶ -
supportsPrivate:
Boolean(default: false) -
Indicates whether this account, on this data type, supports per-user
privateMetadata. This is a server feature flag, not a per-user permission. If false, theprivateMetadataproperty MUST be absent from response objects of this type, allprivateMetadata*filter conditions (Section 3.6) MUST be rejected with an "unsupportedFilter" error, and any/setoperation that targetsprivateMetadataMUST be rejected with an "invalidProperties" SetError. Per-user authorization of aprivateMetadatawrite that the server otherwise supports is defined in Section 4.¶ -
maxDepth:
UnsignedInt|null(default: null) -
Maximum depth of nested objects within a namespace value on this data type. A depth of 1 indicates only flat properties are supported; a depth of 2 allows one level of nesting; and so forth. If not
null, the value MUST be at least 1. A value ofnullmeans there is no server-enforced depth limit beyond quota and size limits. Depth is defined in Section 2.1.¶
A server SHOULD NOT advertise a data type that supports neither registered namespaces (empty namespaces) nor vendor namespaces (supportsVendorNamespaces: false), since such an entry advertises support for nothing. If no data type would be listed in dataTypes, the server SHOULD NOT include the capability in the account's "accountCapabilities".¶
A worked example of the capability object appears in Section 6.1.¶
2. The Metadata Properties
This extension introduces two properties on each listed data type:¶
-
metadata:
String[Object](default:{}) -
An object containing shared metadata associated with the object. Each key is a namespace identifier (see Section 2.1) and each value is structured according to the namespace's definition. Shared metadata is visible to every user with read access to the related object, subject to Section 4. When returned, its value MUST be an object (
{}when no metadata is set), nevernull.¶ -
privateMetadata:
String[Object](default:{}) -
An object containing per-user metadata associated with the object, with the same structure as
metadata. Private metadata is visible only to the user who set it; the server MUST filter responses so that each user sees only their ownprivateMetadatacontent, as described in Section 4. When returned, its value MUST be an object ({}if the user has set no private metadata on the object), nevernull. WhensupportsPrivateis false for the data type, this property MUST NOT be returned.¶
Both properties are mutable and behave like any other property of the related object. They appear in /get responses, accept patches through /set, contribute to the related type's state string (Section 2.2), and may be searched through the /query filter conditions defined in Section 3.6.¶
2.1. Namespaces
The keys of the metadata and privateMetadata objects are namespace identifiers. Each identifier MUST be one of:¶
-
A registered name: a sequence of lowercase US-ASCII letters, digits, hyphens, and underscores, with no dot ("."). Registered names may be registered using the procedure in Section 8.2; this document defines no registered names.¶
-
A domain name controlled by the vendor providing the namespace, in DNS form (containing at least one ".", e.g.,
example.com,acme.example.org). Vendors MAY use domain-name namespaces for proprietary metadata without registration; the dot in the name guarantees no collision with registered names. Domain-name identifiers MUST be in lowercase, with internationalized labels in A-label form. Servers MUST reject, with an "invalidProperties" SetError, a/setoperation that uses a key that is not a valid namespace identifier, including a domain name containing uppercase ASCII letters.¶
A namespace is supported on a data type if it is a registered name listed in that type's namespaces, or a domain name and the type's supportsVendorNamespaces is true.¶
The value associated with each namespace key MUST be a JSON object, whose internal structure is defined by the specification or vendor owning that namespace; a namespace set to {} remains stored until it is removed with null. Property names within the namespace object follow normal JSON conventions; they do not need to be domain-prefixed, since cross-namespace isolation is already provided by the top-level key.¶
Example:¶
{
"metadata": {
"acme.example.com": {
"color": "blue",
"priority": "high",
"project": {
"id": "ALPHA-2024",
"deadline": "2024-12-31"
}
}
}
}
¶
The depth of a value is defined recursively: a scalar (string, number, boolean or null) has depth 0; an array has the maximum depth of its elements (0 if empty); an object has depth 1 plus the maximum depth of its member values (1 if empty). For example, {"a": 1} has depth 1 and {"x": [{"y": 1}]} has depth 2. If the account capability specifies a maxDepth for a data type, the depth of a namespace value MUST NOT exceed it; servers MUST reject writes that would exceed it with an "invalidProperties" SetError.¶
A patch that targets one namespace MUST NOT remove or alter any other namespace, including namespaces that are not supported on the data type.¶
Vendors are encouraged to register namespace identifiers that are likely to be useful beyond the vendor's own products, using the procedure in Section 8.2. Registration enhances interoperability and avoids fragmentation.¶
2.1.1. Data in Unsupported Namespaces
Data can be stored under a namespace that is not supported on the data type, for example after a server reconfiguration, after a vendor withdraws support, or when the data was copied from another account (Section 3.4). Such data MUST continue to be returned by /get for read-only access. Any /set operation that targets the namespace, other than a patch that removes the namespace entirely ("metadata/<namespace>": null or "privateMetadata/<namespace>": null), MUST be rejected with an "invalidProperties" SetError. A complete replacement of metadata or privateMetadata MAY include such a namespace only with a value equal to the stored one, where two JSON objects are equal when they have the same members with equal values, regardless of member order; omitting the namespace from a complete replacement removes it. This ensures clients can always migrate data off an unsupported namespace, but cannot continue to extend it.¶
2.2. State Behavior
Changes to metadata or privateMetadata are changes to the related object. They advance the related type's state string, appear in the related type's /changes response, and are otherwise indistinguishable from changes to any other property of that type, with the following clarification:¶
Because privateMetadata is filtered per viewer, a change made by user A to privateMetadata on a shared object MUST advance the related type's state string for user A and MUST NOT advance it for any other user. The same rule applies to /queryChanges: a change to user B's privateMetadata MUST NOT cause user A's Foo/queryChanges to consider the object changed for filter purposes, since the change is not visible to A. A server that cannot provide per-viewer state MUST set supportsPrivate to false for every data type, since cross-viewer state advancement leaks the existence of another user's private write (see Section 7).¶
Changes to metadata or privateMetadata alone MUST NOT update server-maintained modification timestamps of the related object, such as the changed property of FileNode [I-D.ietf-jmap-filenode] or the updated property of CalendarEvent [I-D.ietf-jmap-calendars]. This overrides any requirement of those specifications that the timestamp change on every update.¶
3. Standard Method Extensions
For each data type listed in the dataTypes field of the account capability, the standard JMAP methods for that type are extended as described in the following subsections; a server MUST implement these extensions for every listed data type.¶
These extensions apply to requests whose using includes urn:ietf:params:jmap:metadata. For other requests, metadata and privateMetadata are not default properties of Foo/get (although they are still returned when requested by name), Foo/set MUST reject any write to them with an "invalidProperties" SetError, and Foo/changes MUST behave as if ignoreMetadataOnlyChanges were true. The state string is the same as for requests that use this capability.¶
This document does not extend methods that create or parse objects from uploaded data, such as Email/import and Email/parse ([RFC8621]). Objects created or returned by such methods have empty metadata and privateMetadata.¶
3.1. /get
For a listed data type, when properties is omitted or null, the server MUST include metadata and, where supportsPrivate is true, privateMetadata in each returned object, except for Email/get, whose default property list (Section 4.2 of [RFC8621]) is unchanged. The two properties may also be requested explicitly by name in the properties array, like any other JMAP property.¶
When a client wants only a subset of the metadata, it MAY request individual namespaces using slash-separated subselector paths in the properties argument. The following rules apply to subselectors introduced by this specification:¶
-
A subselector has the form
metadata/<namespace>orprivateMetadata/<namespace>, where<namespace>is a non-empty namespace identifier. Any otherpropertiesentry beginning withmetadata/orprivateMetadata/MUST be rejected with an "invalidArguments" method-level error.¶ -
Multiple subselectors with the same root combine by union. For example,
["metadata/x", "metadata/y"]returns{"metadata": {"x": ..., "y": ...}}(and nothing else undermetadata).¶ -
An explicit root entry supersedes subselectors with the same root. For example,
["metadata", "metadata/x"]returns the completemetadataobject; themetadata/xentry is redundant rather than restrictive.¶ -
A subselector naming a namespace that is not supported (Section 2.1) on the data type MUST be silently omitted from the response, unless the object has data stored under that namespace (Section 2.1.1), which is then returned. This lets clients perform capability-tolerant fetches without first consulting the session capability.¶
-
This specification defines slash-path subselectors only for
metadataandprivateMetadata. The use of slash-path subselectors with any other property name is outside the scope of this document.¶
3.2. /set
The metadata and privateMetadata properties are mutable.¶
In a create entry of Foo/set, each property's value MUST be a complete JSON object (possibly the empty object {}). A value of null for either property MUST be rejected with an "invalidProperties" SetError; the canonical "no metadata" form is {}. In an update, null is equivalent to {}.¶
In an update entry of Foo/set, clients MAY either send a complete object as the value of metadata or privateMetadata (replacing the previous value entirely), or use the PatchObject form (Section 5.3 of [RFC8620]). Patch paths start with metadata/<namespace> or privateMetadata/<namespace> (replacing or removing an entire namespace value) and MAY continue to any depth below it (for example, metadata/<namespace>/<key>/<subkey>), with the semantics of Section 5.3 of [RFC8620]: the parent of the target MUST exist, and a path that traverses an array is rejected with an "invalidPatch" SetError.¶
Setting a patch value to null removes the targeted key, per standard PatchObject semantics (Section 5.3 of [RFC8620]). Setting a path to a complete object value replaces whatever was previously at that path; in particular, a patch of the shape "metadata/<namespace>": { ... } replaces the entire content of that namespace, deleting any keys not present in the new value. Clients that want to add or update individual properties without removing others need to patch the specific keys.¶
The server MUST validate every write against the account capability for the data type. When more than one of the following rules applies, the server MUST evaluate them in the order listed; the first matching rule determines the SetError:¶
-
If the user cannot read the related object, reject with "notFound". If the user lacks write access to the related object and the write targets
metadata, reject with "forbidden".¶ -
If the path begins with
privateMetadataandsupportsPrivateis false for this data type, reject with "invalidProperties".¶ -
If the targeted namespace is not supported on this data type, reject with "invalidProperties" (except for the migration case in Section 2.1.1).¶
-
If a namespace value would exceed
maxDepth, reject with "invalidProperties".¶
Patch errors that can be determined from the request alone (for example, two paths where one is a prefix of the other) MUST be reported with "invalidPatch" before rule 1 is evaluated. Patch errors that depend on the current value of the property (a missing parent, or a path through an array) MUST be reported with "invalidPatch" after rule 3 and before rule 4.¶
3.2.1. Quota Enforcement on /set
Servers SHOULD enforce quota limits on the total storage consumed by metadata within an account, as described in Section 5. If a create, update, or copy would cause the account to exceed its metadata quota, the server MUST reject the operation with an "overQuota" SetError.¶
3.3. /changes
For each data type listed in dataTypes, the Foo/changes request accepts the following additional optional argument:¶
-
ignoreMetadataOnlyChanges:
Boolean(default: false) -
If true, the server MUST exclude from the
updatedarray any id whose only changes sincesinceState, from the viewer's perspective, are confined tometadataand/orprivateMetadata. Ids with any other change remain inupdated. If the server cannot determine that an id's changes are metadata-only, it MUST include the id. Excluded ids do not count towardmaxChanges. The state string still advances normally, so a client using this argument continues to see correct synchronization for all non-metadata-only changes; metadata-only changes are simply not reported to this client. When this argument is true,updatedProperties(defined below) MUST NOT listmetadataorprivateMetadata. ForMailbox/changesit may list the count properties (Section 2.2 of [RFC8621]); for other types it MUST benull.¶
The response is extended to include the following additional argument:¶
-
updatedProperties:
String[]|null -
This argument is determined per viewer: each user's
Foo/changesresponse is computed against the changes visible to that user (in particular,privateMetadatawritten by other users is invisible). If, from the viewer's perspective, the only properties that have changed on every id in theupdatedarray since the old state aremetadataand/orprivateMetadata, this argument MUST be set to a list containing only those property names (e.g.,["metadata"],["privateMetadata"], or["metadata", "privateMetadata"]). If any other property (apart from the Mailbox count properties described below) of any id in the array may also have changed (from the viewer's perspective), or if the server cannot determine the answer for any id in the array, this argument MUST benull. The argument applies uniformly to all ids inupdated; per-id granularity is not provided. It is always present, and isnullwhenupdatedis empty.¶
This argument follows the same convention used by Mailbox/changes in Section 2.2 of [RFC8621]. For Mailbox/changes, when every id in updated changed only in its counts (as defined there) and/or in metadata or privateMetadata, the value is the union of the count property names and the metadata property names that may have changed.¶
3.4. /copy
Foo/copy copies metadata to the new object. The copying user's privateMetadata on the source object becomes that user's privateMetadata on the new object; other users' privateMetadata MUST NOT be copied. When the request does not use this capability, only metadata is copied. The create object MAY include metadata and privateMetadata to override the copied values, with the same rules as a create in Foo/set; for Email/copy, this extends the list of properties that may be set in Section 4.7 of [RFC8621]. Copied metadata counts against quota (Section 5) as any other write.¶
Copied values are not revalidated against the destination account: data in a copied namespace that is not supported there is handled as described in Section 2.1.1, and maxDepth applies only to values supplied in the create object. privateMetadata is not copied when the destination does not support it, and neither property is copied when the data type is not listed in the destination account. With onSuccessDestroyOriginal, other users' privateMetadata on the source object is destroyed together with it.¶
3.5. /queryChanges
Foo/queryChanges is unchanged in its method signature and follows the per-viewer rule in Section 2.2.¶
3.6. /query
For each data type listed in dataTypes, the FilterCondition object for Foo/query is extended with the optional fields defined below. Several of those fields take a MetadataTextMatch value.¶
A MetadataTextMatch object has the following fields:¶
-
path:
String -
The path within the metadata object to match against: a namespace identifier, optionally followed by a JSON Pointer [RFC6901] into the namespace value (for example,
<namespace>,<namespace>/<key>or<namespace>/<key>/<subkey>). Object members are addressed by name; a path that would traverse an array or a scalar addresses no value. A path that is not well formed (for example, one with an invalid~escape) MUST be rejected with an "invalidArguments" error.¶ -
value:
String -
The string to search for at
path. The interpretation of this string (substring containment versus exact equality) is determined by the FilterCondition field that carries theMetadataTextMatch.¶
The extended FilterCondition fields are:¶
-
metadataExists:
String -
A path, with the syntax defined for
MetadataTextMatch. Matches an object if and only if a value is present at the given path within the object'smetadataproperty. A namespace-only path matches if the namespace key is present and its value is not the empty object{}.¶ -
privateMetadataExists:
String -
As
metadataExists, but matches against the authenticated user'sprivateMetadata.¶ -
metadataTextContains:
MetadataTextMatch -
Matches an object if the
metadatavalue at the suppliedpathis a string that contains the suppliedvalueas a substring under the "i;unicode-casemap" collation [RFC5051]. The condition does not match if the path does not exist withinmetadata, or if the value at the path is not a string.¶ -
privateMetadataTextContains:
MetadataTextMatch -
As
metadataTextContains, but searches the authenticated user'sprivateMetadata.¶ -
metadataTextEquals:
MetadataTextMatch -
Matches an object if the
metadatavalue at the suppliedpathis a string that is exactly equal to the suppliedvalue, comparing Unicode code points without case folding or normalization. The condition does not match if the path does not exist withinmetadata, or if the value at the path is not a string.¶ -
privateMetadataTextEquals:
MetadataTextMatch -
As
metadataTextEquals, but searches the authenticated user'sprivateMetadata.¶
The following availability rules apply to all six conditions:¶
-
Servers MUST support
metadataExistsandprivateMetadataExists(wheresupportsPrivateis true).¶ -
Servers MAY reject any of the four text-match conditions with an "unsupportedFilter" error if implementation cost or storage layout makes them infeasible. Clients need to be prepared to fall back to a client-side scan in this case.¶
-
Any
privateMetadata*condition (existence or text) MUST be rejected with an "unsupportedFilter" error ifsupportsPrivateis false for the data type.¶ -
Conditions are evaluated against the stored data whether or not the namespace is supported on the data type, so data stored under an unsupported namespace (Section 2.1.1) can still be found. A condition naming an unsupported namespace with no stored data, or a string that is not a valid namespace identifier, matches no objects; it does not produce an error.¶
These conditions compose with the data type's existing filter conditions through the usual AND/OR/NOT operators (Section 5.5 of [RFC8620]). For example, a client may query for Email objects in a particular mailbox whose vendor metadata contains a search term in a single server-side query, as shown in Section 6.7.¶
4. Access Control
Access control for metadata follows the access control of the related object. The two properties are governed by different rules:¶
-
The
metadataproperty is shared. A user who has read access to the related object can read the object'smetadata. A user who has write access to the related object can modify the object'smetadata. For the data types listed in Table 1, "read" and "write" are the rights given there. For other shareable data types (Section 4 of [RFC9670]), they are the rights the type's specification defines inmyRightsfor reading the object and for modifying its properties. For other data types, read and write access are those of the account, and write access additionally requires that the account is not read-only (isReadOnlyis false, Section 2 of [RFC8620]).¶ -
The
privateMetadataproperty is per-user. The server MUST filter responses so that each user sees only theprivateMetadatacontent they themselves wrote. Private metadata written by another user on the same related object MUST NOT be visible. To writeprivateMetadataon a related object, the user needs only read access to the related object; write access on the related object is not required.¶
For the following data types, "read" and "write" are defined as shown. When an object belongs to more than one container (an Email in several Mailboxes, a CalendarEvent in several Calendars, or a ContactCard in several AddressBooks), read access requires the right on at least one of the containers, and write access requires it on every container the object belongs to; when the same update changes the object's containers, write access requires it on every container both before and after the change. On create, the rights apply to the containers given in the create.¶
| Type | Read | Write |
|---|---|---|
| Email [RFC8621] |
mayReadItems on a containing Mailbox |
maySetKeywords on every containing Mailbox |
| Mailbox [RFC8621] | the Mailbox is returned to the user by Mailbox/get
|
mayRename
|
| CalendarEvent [I-D.ietf-jmap-calendars] |
mayReadItems on a containing Calendar |
mayWriteAll, or mayWriteOwn if the user is an owner of the event or the event has no owner, on every containing Calendar |
| Calendar [I-D.ietf-jmap-calendars] | the Calendar is returned to the user by Calendar/get
|
mayWriteAll
|
| ContactCard [RFC9610] |
mayRead on a containing AddressBook |
mayWrite on every containing AddressBook |
| AddressBook [RFC9610] | the AddressBook is returned to the user by AddressBook/get
|
mayWrite
|
| FileNode [I-D.ietf-jmap-filenode] |
mayRead
|
mayRename
|
| SieveScript [RFC9661] | access to the account | the account is not read-only |
[[Open issue: Several of these mappings reuse rights defined for other purposes (for example, maySetKeywords, mayRename, or mayWriteAll for a Calendar's own properties), and JMAP for Calendars and JMAP for Contacts define no right for modifying the properties of a Calendar or AddressBook. The working group needs to decide whether to keep these mappings or to define a dedicated right, such as mayWriteMetadata, for every data type that supports metadata. WebDAV (the DAV:write-properties privilege of [RFC3744]) and IMAP ANNOTATE (the "n" right of [RFC5257]) both separate this right from the right to modify content. Similarly, JMAP for Calendars defines mayUpdatePrivate for per-user event properties, whereas this document lets any user with read access write privateMetadata.]]¶
The server MUST enforce these rules consistently across /get, /set, /query, /queryChanges, and /changes. An object the user cannot read is treated for metadata and privateMetadata exactly as for its other properties (for example, it is reported in notFound). A write to an object the user cannot read MUST be rejected with a "notFound" SetError, so that the existence of the object is not revealed. A write to metadata without write permission on the related object MUST be rejected with a "forbidden" SetError.¶
When sharing permissions on a related object change, the visibility of its metadata changes accordingly. privateMetadata belonging to a user remains visible to that user as long as the user retains read access to the related object. If read access is revoked, the object, including the user's privateMetadata, becomes inaccessible to that user; the server MAY delete that privateMetadata, and if it does not and access is restored, it is visible again. A user's privateMetadata is destroyed together with the related object.¶
4.1. Account Delegation and Administrative Access
privateMetadata is keyed by the authenticated identity of the session. A delegate, an impersonating administrator, or any other session authenticated as a different identity than the account owner MUST see and write only its own privateMetadata, never the account owner's. Exposing the account owner's privateMetadata to such a session is outside the scope of this document.¶
5. Quota
Servers SHOULD enforce quota limits on the storage consumed by metadata within an account. Unbounded metadata growth could be exploited to exhaust resources, particularly because privateMetadata is per-user and so a single shared object can carry one private payload per user with access to it.¶
Servers that support the JMAP Quotas extension [RFC9425] SHOULD integrate metadata storage into the quota framework defined there. Servers that do not implement [RFC9425] SHOULD still enforce implementation-defined limits and reject overruns with an "overQuota" SetError.¶
The size metric used for metadata quota is implementation-defined; clients cannot assume that two servers compute identical sizes for the same data. Servers can document the metric they use.¶
A server that also exposes the related object over other protocols (for example, as WebDAV dead properties [RFC4918] or IMAP METADATA [RFC5464]) MAY count data that is not visible through JMAP against the quota and limits described in this section. Clients therefore need to be prepared for a limit error on an object whose visible metadata is small.¶
6. Examples
6.1. Capability
A session for an account whose server supports metadata on four data types, with varying private-metadata and depth settings:¶
{
"capabilities": {
"urn:ietf:params:jmap:metadata": {}
},
"accounts": {
"A1": {
"accountCapabilities": {
"urn:ietf:params:jmap:metadata": {
"dataTypes": {
"Email": {
"namespaces": [],
"supportsVendorNamespaces": true,
"supportsPrivate": true,
"maxDepth": 4
},
"Mailbox": {
"namespaces": [],
"supportsVendorNamespaces": true,
"supportsPrivate": true,
"maxDepth": null
},
"CalendarEvent": {
"namespaces": [],
"supportsVendorNamespaces": true,
"supportsPrivate": false,
"maxDepth": 2
},
"FileNode": {
"namespaces": ["photography"],
"supportsVendorNamespaces": false,
"supportsPrivate": false,
"maxDepth": 3
}
}
}
}
}
}
}
¶
6.2. Fetching a Mailbox with Its Metadata
Request the shared metadata and the user's private metadata for a single Mailbox:¶
[
["Mailbox/get", {
"accountId": "A1",
"ids": ["MB1"],
"properties": [
"id", "name",
"metadata/acme.example.com",
"privateMetadata/acme.example.com"
]
}, "c1"]
]
¶
Response:¶
[
["Mailbox/get", {
"accountId": "A1",
"state": "mb-100",
"list": [
{
"id": "MB1",
"name": "Team Inbox",
"metadata": {
"acme.example.com": {
"color": "blue",
"owner": "team-alpha"
}
},
"privateMetadata": {
"acme.example.com": {
"workflowState": "pending-review"
}
}
}
],
"notFound": []
}, "c1"]
]
¶
6.3. Patching a Single Property within a Namespace
Update one property inside a namespace without disturbing the others:¶
[
["Mailbox/set", {
"accountId": "A1",
"update": {
"MB1": {
"metadata/acme.example.com/color": "green"
}
}
}, "c1"]
]
¶
Removing a single property uses a patch to null:¶
[
["Mailbox/set", {
"accountId": "A1",
"update": {
"MB1": {
"metadata/acme.example.com/color": null
}
}
}, "c1"]
]
¶
6.4. Creating an Email with Private Metadata
Create a draft Email and attach a private workflow annotation atomically. The metadata is just another property on the create:¶
[
["Email/set", {
"accountId": "A1",
"create": {
"draft1": {
"mailboxIds": { "MB1": true },
"keywords": { "$draft": true },
"subject": "Project Update",
"from": [{ "email": "alice@example.com" }],
"to": [{ "email": "bob@example.com" }],
"bodyStructure": {
"type": "text/plain",
"partId": "1"
},
"bodyValues": {
"1": { "value": "Here is the project update..." }
},
"privateMetadata": {
"acme.example.com": {
"workflowState": "pending-review",
"assignedTo": "carol@example.com"
}
}
}
}
}, "c1"]
]
¶
The response is a single Email/set response; there is no separate metadata response, because metadata is part of the Email object.¶
6.5. Attaching Photography Metadata to a FileNode
This example uses a fictional IANA-registered namespace photography, assumed for the purpose of this example to be specified for photographic information about image files and applicable to the FileNode data type. The capability for the account in Section 6.1 advertises FileNode as supporting photography and no vendor namespaces.¶
Request:¶
[
["FileNode/set", {
"accountId": "A1",
"update": {
"F456": {
"metadata/photography": {
"geoLocation": {
"latitude": 46.362,
"longitude": 14.090
},
"cameraMake": "Canon",
"cameraModel": "EOS R5",
"aperture": "f/2.8",
"shutterSpeed": "1/250",
"iso": 400,
"focalLength": "50mm",
"dateTaken": "2023-10-01T01:14:00Z",
"imageSize": {
"width": 6000,
"height": 4000
}
}
}
}
}, "c1"]
]
¶
Subsequent retrieval of the FileNode, requesting only the relevant metadata namespace:¶
[
["FileNode/get", {
"accountId": "A1",
"ids": ["F456"],
"properties": [
"id", "name", "type", "size",
"metadata/photography"
]
}, "c2"]
]
¶
Response:¶
[
["FileNode/get", {
"accountId": "A1",
"state": "f-200",
"list": [
{
"id": "F456",
"name": "lake-island.jpg",
"type": "image/jpeg",
"size": 2458624,
"metadata": {
"photography": {
"geoLocation": {
"latitude": 46.362,
"longitude": 14.090
},
"cameraMake": "Canon",
"cameraModel": "EOS R5",
"aperture": "f/2.8",
"shutterSpeed": "1/250",
"iso": 400,
"focalLength": "50mm",
"dateTaken": "2023-10-01T01:14:00Z",
"imageSize": {
"width": 6000,
"height": 4000
}
}
}
}
],
"notFound": []
}, "c2"]
]
¶
6.6. Updating a CalendarEvent and Its Metadata Atomically
This example updates a primary property of a CalendarEvent and two vendor metadata properties in the same /set call. As with any update entry, either all of these changes apply or none do.¶
Request:¶
[
["CalendarEvent/set", {
"accountId": "A1",
"update": {
"CE789": {
"title": "Quarterly Review Meeting",
"start": "2024-12-15T14:00:00",
"metadata/acme.example.com/lastModifiedReason":
"Rescheduled per manager request",
"metadata/acme.example.com/approvalStatus": "pending"
}
}
}, "c1"]
]
¶
A successful response is a single CalendarEvent/set response; metadata changes do not produce a separate response because they are properties of the event itself:¶
[
["CalendarEvent/set", {
"accountId": "A1",
"oldState": "c-200",
"newState": "c-201",
"updated": {
"CE789": null
}
}, "c1"]
]
¶
6.7. Querying by Metadata and a Primary Property Together
Find all Email objects in a particular Mailbox whose private vendor memo contains the text "follow up":¶
[
["Email/query", {
"accountId": "A1",
"filter": {
"operator": "AND",
"conditions": [
{ "inMailbox": "MB-inbox" },
{
"privateMetadataTextContains": {
"path": "acme.example.com/memo",
"value": "follow up"
}
}
]
}
}, "c1"]
]
¶
6.8. Detecting a Metadata-Only Change through /changes
After another client updates the shared metadata of two Emails, the next Email/changes call returns:¶
[
["Email/changes", {
"accountId": "A1",
"oldState": "e-100",
"newState": "e-103",
"hasMoreChanges": false,
"created": [],
"updated": ["EM1", "EM2"],
"destroyed": [],
"updatedProperties": ["metadata"]
}, "c1"]
]
¶
The client knows it only needs to refetch the metadata property of EM1 and EM2, not the full Email objects.¶
7. Security Considerations
7.1. Metadata Confidentiality
Metadata may contain sensitive information: personal notes, workflow states, application-specific data, and other user-generated content. Servers MUST enforce the access-control rules in Section 4. Failures here are direct privacy breaches.¶
privateMetadata is per-user. The server MUST ensure that each user sees only their own content, even on shared related objects. This is the most common implementation pitfall in this specification: a query or /get implementation that returns another user's privateMetadata is a serious bug. Servers MUST verify that result filtering applies uniformly to /get, /query, /changes, /queryChanges, and to any cached or denormalized representation.¶
Per-viewer state (Section 2.2) interacts with confidentiality. A server that advances the related type's state for user B in response to user A's private write is not leaking the metadata content, but it is leaking a signal that something private to A changed on the related object. The specification therefore requires per-viewer state, and requires supportsPrivate to be false where the server cannot provide it.¶
7.2. User Identity and Authentication
The private-metadata model relies on accurate user identification. Servers need to identify the authenticated user reliably for every metadata operation and keep each privateMetadata value associated with the user that wrote it, so that identity spoofing or authentication bypass cannot expose another user's private metadata. Delegated and administrative sessions are covered in Section 4.1.¶
7.3. Untrusted Namespace Values
Namespace values are arbitrary client-supplied content. Servers and clients need to treat them as untrusted input: validate them before use, apply context-appropriate output encoding (for example, when rendering them in a web page or storing them in a database), and never interpret them as code. Servers store namespace values as given; they do not alter them, since clients and metadataTextEquals rely on exact values.¶
Clients that render metadata need appropriate sandboxing and Content Security Policy, and need to distinguish clearly in their user interface between metadata (visible to others) and privateMetadata (visible only to the user), so that users do not inadvertently disclose private notes. Clients also cannot assume the structure of a namespace value without checking it.¶
7.4. Resource Exhaustion
Without controls, metadata can be abused for denial of service:¶
-
Storage exhaustion: bulk creation of large metadata payloads, particularly under
privateMetadata, where every user with access to a shared object can independently consume space.¶ -
Processing exhaustion: deeply nested or pathologically large namespace values.¶
-
Query complexity: text-match filter conditions (
metadataTextContains/metadataTextEqualsand their private variants) that require expensive scanning, particularly when combined with per-user filtering across many users. Servers MAY reject such conditions with "unsupportedFilter" (Section 3.6) to bound the cost.¶
Servers SHOULD apply the quota mechanism in Section 5, and can also bound value sizes, enforce maxDepth, time out long-running queries, rate-limit metadata writes, and monitor for unusual usage patterns (for example, a single user creating private metadata on unusually many objects). Per-user filtering of privateMetadata needs to be efficient so that it does not itself become a denial-of-service vector.¶
8. IANA Considerations
8.1. JMAP Capability Registration for "metadata"
IANA will register the "metadata" JMAP Capability as follows:¶
Capability Name: urn:ietf:params:jmap:metadata
Specification document: this document
Intended use: common
Change Controller: IETF
Security and privacy considerations: this document, Section 7¶
8.2. Creation of the "JMAP Metadata Namespaces" Registry
IANA is requested to create the "JMAP Metadata Namespaces" registry to record metadata namespace identifiers used as top-level keys in the metadata and privateMetadata properties defined by this specification.¶
This registry uses the Expert Review process (Section 4.5 of [RFC8126]). Registrants must propose a name without a dot ("."), since names containing a dot are reserved for vendor domain-name namespaces and need no registration. The designated expert MUST ensure that the proposed name does not collide with an existing registration and that the specification provides sufficient detail for interoperability (in particular, the structure of the namespace's value).¶
Registrations may be of Intended Use "common", "reserved", or "obsolete". A "reserved" registration reserves a name without assigning semantics; an "obsolete" registration marks a name that should no longer be used by up-to-date implementations.¶
8.2.1. Preliminary Community Review
Notice of a potential new registration SHOULD be sent to the JMAP mailing list jmap@ietf.org for review. The intent is to solicit comments on the namespace identifier, the clarity of the specification, and any interoperability or security considerations. The submitter MAY revise or withdraw the proposal at any time.¶
8.2.2. Change Procedures
The change controller for a registration MAY request changes to its definition using the same procedure as the original registration. Significant changes that would invalidate existing data SHOULD be made only to correct serious errors. Registrations MUST NOT be deleted; namespaces that are no longer appropriate for use can be marked obsolete by a change to their intended-use field.¶
The change controller of a registration MAY transfer responsibility to another person or agency by informing IANA.¶
8.2.3. "JMAP Metadata Namespaces" Registry Template
- Namespace Identifier:
-
The name registered for use as a key in the
metadataorprivateMetadataobject. MUST consist of lowercase US-ASCII letters, digits, hyphens, and underscores, and MUST NOT contain a dot.¶ - Value Type:
-
A description of the structure of values stored under this namespace key (for example, "object whose values are strings").¶
- Applicable Properties:
-
One of:
"metadata"(this namespace may appear only undermetadata),"privateMetadata"(only underprivateMetadata), or"both"(under either).¶ - Applicable Data Types:
-
A list of JMAP data type names on which this namespace is meaningful, or
"any"if the namespace is not restricted.¶ - Reference or Description:
-
A brief description, or an RFC number and section reference, describing the namespace's contents. May be omitted for "reserved" entries.¶
- Intended Use:
-
"common","reserved", or"obsolete".¶ - Change Controller:
-
Who may request changes to this entry (IETF for RFCs from the IETF stream).¶
8.2.4. Submit Request to IANA
Registration requests can be sent to iana@iana.org.¶
8.2.5. Designated Expert Review
The designated expert (DE) is primarily concerned with preventing name collisions and ensuring that the specification provides enough detail for interoperability. For common-use registrations, the DE is expected to confirm that suitable documentation (per Section 4.6 of [RFC8126]) is available. A published specification is not required for reserved or obsolete registrations.¶
The DE will either approve or deny the registration and publish a notice of the decision to the JMAP working group mailing list (or its successor) and to IANA. A denial MUST be justified and SHOULD include concrete suggestions for how the request can be modified to become acceptable.¶
8.2.6. Initial Contents
This document defines no initial entries for the "JMAP Metadata Namespaces" registry. Future specifications may register entries through the procedure defined in this section.¶
9. References
9.1. Normative References
- [RFC2119]
- Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/rfc/rfc2119>.
- [RFC5051]
- Crispin, M., "i;unicode-casemap - Simple Unicode Collation Algorithm", RFC 5051, DOI 10.17487/RFC5051, , <https://www.rfc-editor.org/rfc/rfc5051>.
- [RFC6901]
- Bryan, P., Ed., Zyp, K., and M. Nottingham, Ed., "JavaScript Object Notation (JSON) Pointer", RFC 6901, DOI 10.17487/RFC6901, , <https://www.rfc-editor.org/rfc/rfc6901>.
- [RFC8126]
- Cotton, M., Leiba, B., and T. Narten, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 8126, DOI 10.17487/RFC8126, , <https://www.rfc-editor.org/rfc/rfc8126>.
- [RFC8174]
- Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/rfc/rfc8174>.
- [RFC8620]
- Jenkins, N. and C. Newman, "The JSON Meta Application Protocol (JMAP)", RFC 8620, DOI 10.17487/RFC8620, , <https://www.rfc-editor.org/rfc/rfc8620>.
- [RFC9670]
- Jenkins, N., Ed., "JSON Meta Application Protocol (JMAP) Sharing", RFC 9670, DOI 10.17487/RFC9670, , <https://www.rfc-editor.org/rfc/rfc9670>.
9.2. Informative References
- [I-D.ietf-jmap-calendars]
- Jenkins, N. and M. Douglass, "JSON Meta Application Protocol (JMAP) for Calendars", Work in Progress, Internet-Draft, draft-ietf-jmap-calendars-33, , <https://datatracker.ietf.org/doc/html/draft-ietf-jmap-calendars-33>.
- [I-D.ietf-jmap-filenode]
- Gondwana, B., "JMAP File Storage extension", Work in Progress, Internet-Draft, draft-ietf-jmap-filenode-16, , <https://datatracker.ietf.org/doc/html/draft-ietf-jmap-filenode-16>.
- [RFC3744]
- Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol", RFC 3744, DOI 10.17487/RFC3744, , <https://www.rfc-editor.org/rfc/rfc3744>.
- [RFC4918]
- Dusseault, L., Ed., "HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)", RFC 4918, DOI 10.17487/RFC4918, , <https://www.rfc-editor.org/rfc/rfc4918>.
- [RFC5257]
- Daboo, C. and R. Gellens, "Internet Message Access Protocol - ANNOTATE Extension", RFC 5257, DOI 10.17487/RFC5257, , <https://www.rfc-editor.org/rfc/rfc5257>.
- [RFC5464]
- Daboo, C., "The IMAP METADATA Extension", RFC 5464, DOI 10.17487/RFC5464, , <https://www.rfc-editor.org/rfc/rfc5464>.
- [RFC8621]
- Jenkins, N. and C. Newman, "The JSON Meta Application Protocol (JMAP) for Mail", RFC 8621, DOI 10.17487/RFC8621, , <https://www.rfc-editor.org/rfc/rfc8621>.
- [RFC9425]
- Cordier, R., Ed., "JSON Meta Application Protocol (JMAP) for Quotas", RFC 9425, DOI 10.17487/RFC9425, , <https://www.rfc-editor.org/rfc/rfc9425>.
- [RFC9610]
- Jenkins, N., Ed., "JSON Meta Application Protocol (JMAP) for Contacts", RFC 9610, DOI 10.17487/RFC9610, , <https://www.rfc-editor.org/rfc/rfc9610>.
- [RFC9661]
- Murchison, K., "The JSON Meta Application Protocol (JMAP) for Sieve Scripts", RFC 9661, DOI 10.17487/RFC9661, , <https://www.rfc-editor.org/rfc/rfc9661>.
Appendix A. Changes
[[This section to be removed by RFC Editor]]¶
draft-ietf-jmap-metadata-03¶
-
Clarified that metadata-only changes do not update server-maintained modification timestamps.¶
-
Defined behavior for requests that do not use the capability.¶
-
Allowed patch paths at any depth, and placed "invalidPatch" in the
/setvalidation order.¶ -
Defined the interaction of
updatedPropertiesandignoreMetadataOnlyChangeswith the Mailbox count properties.¶ -
Added
/copybehavior; excluded/importand/parsestyle methods.¶ -
Excluded metadata from the
Email/getdefault property list.¶ -
Specified the "i;unicode-casemap" collation for
metadataTextContains.¶ -
Required lowercase domain-name namespace identifiers.¶
-
Defined read and write rights for the data types of JMAP Mail, Calendars, Contacts, FileNode and Sieve, and the rule for objects in several containers.¶
-
Clarified handling of data in unsupported namespaces in subselectors, filters, complete replacements and copies.¶
-
Allowed filter condition paths at any depth below a namespace.¶
-
Added terminology, defined depth recursively, and made editorial clarifications throughout.¶
-
Recommended omitting the capability when no data type is listed, and noted that limits may count data from other protocols.¶
draft-ietf-jmap-metadata-02¶
-
Redesigned the extension around two properties (
metadataandprivateMetadata) on each opted-in JMAP data type, rather than a separateMetadatadata type with its own methods. Removed theMetadatadata type, allMetadata/*methods, the related object types, the extended/get//setarguments, and the uniqueness and cascading-deletion machinery.¶ -
Added a per-data-type capability advertising IANA-registered namespace support (
namespaces), vendor (domain-name) namespace support (supportsVendorNamespaces), private-metadata support (supportsPrivate), and a nesting limit (maxDepth).¶ -
Added
updatedPropertiestoFoo/changesfor opted-in types, modelled onMailbox/changesin RFC 8621. Specified per-viewer behavior for/changesand/queryChangeson shared objects carrying per-user private metadata.¶ -
Added FilterCondition fields to
Foo/query:metadataExists,privateMetadataExists,metadataTextContains,metadataTextEquals, and the corresponding private variants. Servers MAY reject the text-match conditions with "unsupportedFilter" when implementation cost is prohibitive.¶ -
Added an
ignoreMetadataOnlyChangesargument toFoo/changes, letting metadata-uninterested clients skip wakeups for metadata-only changes on data types they otherwise care about.¶ -
Specified PatchObject semantics for
metadataandprivateMetadata, including handling of unknown namespaces, error ordering on/set, and the migration case where a namespace is removed from the server's advertised set after data has been written.¶ -
Replaced the previous metadata-types and metadata-properties registries with a single "JMAP Metadata Namespaces" registry. This document defines no initial entries; bindings to other protocols (e.g., IMAP METADATA, WebDAV dead properties) are expected to be defined in separate specifications.¶
draft-ietf-jmap-metadata-01¶
-
Renamed
parentTypeandparentIdproperties torelatedTypeandrelatedIdthroughout the document to better reflect their purpose and avoid implying a strict hierarchy.¶ -
Added
filterRelatedTypeandfilterMetadataTypeoptional parameters to Metadata/changes method to allow filtering changes by related object type and metadata type.¶ -
Changed the error type from "invalidProperties" to "alreadyExists" when a client attempts to create a Metadata object that violates the uniqueness constraint.¶
-
Added requirement that
relatedTypemust be specified in the same FilterCondition object wheneverrelatedIdsis used in Metadata/query.¶ -
Added
idas a MUST support property for sorting in Metadata/query; other properties changed to SHOULD support.¶ -
Replaced the
metadataTypesargument with afetchMetadataBoolean argument (default: false) in the extended /get method.¶ -
Added
onSuccessCreateMetadataandonSuccessUpdateMetadataarguments to the extended /set method to enable transactional metadata operations that are conditional on the success of the related object operation.¶
draft-ietf-jmap-metadata-00¶
-
Initial version¶