Internet-Draft JMAP FileNode October 2026
Gondwana Expires 19 April 2027 [Page]
Workgroup:
Network Working Group
Internet-Draft:
draft-ietf-jmap-filenode-16
Updates:
8620 (if approved)
Published:
Intended Status:
Standards Track
Expires:
Author:
B. Gondwana
Fastmail

JMAP File Storage extension

Abstract

The JMAP base protocol (RFC8620) provides the ability to upload and download arbitrary binary data. This binary data is called a "blob", and can be used in all other JMAP extensions.

This extension adds a method to expose blobs as a filesystem along with the types of metadata that are provided by other remote filesystem protocols.

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 19 April 2027.

▲

Table of Contents

1. Introduction

JMAP ([JMAP-CORE] — JSON Meta Application Protocol) is a generic protocol for synchronizing data 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.

In the same way that JMAP Calendars ([JMAP-CALENDARS]) replaces CalDAV ([CALDAV]) and JMAP Contacts ([JMAP-CONTACTS]) replaces CardDAV ([CARDDAV]), this document replaces the use of WebDAV ([WEBDAV]) for remote filesystem access.

1.1. Conventions Used in This Document

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.

2. Addition to the Capabilities Object

The capabilities object is returned as part of the JMAP Session object; see [JMAP-CORE], Section 2.

This document defines an additional capability URI.

2.1. urn:ietf:params:jmap:filenode

The capability urn:ietf:params:jmap:filenode being present in the "accountCapabilities" property of an account represents support for the FileNode datatype. Servers that include the capability in one or more "accountCapabilities" properties MUST also include the property in the "capabilities" property.

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:

  • maxFileNodeDepth: "UnsignedInt|null"

    The maximum depth of the FileNode hierarchy (that is, one more than the maximum number of ancestors a FileNode may have), or null for no limit.

  • maxSizeFileNodeName: "UnsignedInt"

    The maximum length, in (UTF-8) octets, allowed for the name of a FileNode. This MUST be at least 100, although it is recommended servers allow more.

  • forbiddenNameChars: "String|null"

    A string where each character is forbidden in FileNode names. If null, the server does not restrict name characters beyond the requirement that names be non-empty Net-Unicode strings. The server MUST reject names containing any of these characters with an "invalidProperties" error.

  • forbiddenNodeNames: "String[]|null"

    An array of complete names that the server will not accept as FileNode names, compared case-insensitively. For example, [".", "..", "CON", "PRN", "AUX", "NUL"] would forbid common path-traversal names and Windows reserved device names. If null, the server does not forbid any specific names.

  • supportedNodeTypes: "String[]"

    The @type values that the server allows clients to create in this account (see "FileNode objects" below). This MUST include "Directory" and "File". It MAY include "SymLink" and any other value registered in the "JMAP FileNode Types" registry. Clients MUST ignore values they do not recognise.

  • fileNodeQuerySortOptions: "String[]"

    A list of all the values the server supports for the "property" field of the Comparator object in a "FileNode/query" sort (see "FileNode/query" below). This MAY include properties the client does not recognise (for example, custom properties specified in a vendor extension). Clients MUST ignore any unknown properties in the list.

  • mayCreateTopLevelFileNode: "Boolean"

    If true, the user may create a FileNode (see "FileNode/set" below) in this account with a null parentId. (Permission for creating a child of an existing FileNode is given by the "myRights" property on that FileNode.)

  • webTrashUrl: "String|null"

    The URL at which the folder with the "trash" role can be viewed on the web. If null, there is no web URL available.

  • caseInsensitiveNames: "Boolean"

    If true, the server treats file names as case-insensitive for all name collision checks, including the sibling uniqueness constraint and onExists handling. The server preserves the original case as provided by the client.

  • webUrlTemplate: "String|null"

    A template by which any node can be viewed on the web, in URI Template (level 1) format [URI-TEMPLATE]. The available variable is {id}, which is the FileNode id. If null, there is no web URL available.

  • webWriteUrlTemplate: "String|null"

    A URI Template (level 1) [URI-TEMPLATE] for writing content to a FileNode with @type "File". The available variable is {id}, which is the FileNode id. If null, direct HTTP writes are not supported and clients must use FileNode/set to update content. See "Direct HTTP Write" below for the HTTP semantics.

2.1.1. Capability Example

{
  "urn:ietf:params:jmap:filenode": {
    "maxFileNodeDepth": 50,
    "maxSizeFileNodeName": 255,
    "fileNodeQuerySortOptions": [
      "name", "mediaType", "size", "created", "modified",
      "nodeType", "tree"
    ],
    "supportedNodeTypes": ["Directory", "File", "SymLink"],
    "forbiddenNameChars": "/<>:\"\\|?*",
    "forbiddenNodeNames": [".", "..", "CON", "PRN", "AUX",
      "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5",
      "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2",
      "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
    ],
    "caseInsensitiveNames": false,
    "mayCreateTopLevelFileNode": false,
    "webTrashUrl": "https://files.example.com/trash",
    "webUrlTemplate": "https://files.example.com/view/{id}",
    "webWriteUrlTemplate": "https://files.example.com/write/{id}"
  }
}

3. FileNode Data Type

A FileNode is a set of metadata which behaves similarly to an inode in a filesystem. In [WEBDAV] terminology a Directory corresponds to a collection and a File to a resource.

The following JMAP Methods are selected by the urn:ietf:params:jmap:filenode capability.

3.1. FileNode objects

Every FileNode has a type, given by its @type property. This document defines three types: "File", "Directory", and "SymLink". Each type has its own set of properties in addition to the properties common to all FileNodes.

Properties that this document defines for one type MUST NOT appear on a FileNode of another type; they are omitted rather than set to null. An attempt to create or update a FileNode that sets a property not defined for its @type to a non-null value is an "invalidProperties" error. The server MUST ignore a null value for such a property, both on create and on update. This lets a client send the same set of properties for nodes of any type.

FileNode/get has additional rules for these properties (see "FileNode/get" below).

3.1.1. Common properties

The following properties are defined for every FileNode, regardless of its type:

  • @type: "String" (immutable)

    The type of the node. Values are registered in the "JMAP FileNode Types" registry (see IANA Considerations). This document defines the values "File", "Directory", and "SymLink". This MUST be provided on creation. Attempting to change @type after creation is an "invalidProperties" error. A server MUST reject the creation of a node with an @type that is not in the supportedNodeTypes capability with an "invalidProperties" error. Clients MUST be prepared to receive nodes with @type values they do not recognise, and SHOULD NOT interpret any of their properties other than the common properties.

  • id: "Id" (immutable; server-set)

    The Id of the FileNode.

  • parentId: "Id|null"

    The Id of the parent node, or null if this is a top level node. The parent MUST be a node whose @type permits children; of the types defined in this document, only "Directory" does. Setting parentId to any other node is an "invalidProperties" error.

  • name: "String"

    User-visible name for the FileNode. This MUST be a Net-Unicode string [UNICODE] of at least 1 character in length, subject to the maximum size given in the capability object. There MUST NOT be two sibling FileNodes with both the same parent and the same name. The server MUST reject names containing any character listed in forbiddenNameChars, or matching any entry in forbiddenNodeNames (case-insensitively), with an "invalidProperties" error. See Implementation Considerations for guidance on which characters and names to forbid.

  • created: "UTCDate" (default: current server time)

    The date the node was created.

  • modified: "UTCDate|null" (default: current server time)

    The date the node was last updated. The server does not automatically update this value. If the client does not include modified in an update, the server MUST leave it unchanged. If the client sets modified to null, the server MUST set it to the current server time. See Implementation Considerations for usage guidance.

  • accessed: "UTCDate|null" (default: current server time)

    The date the node was last accessed. As with modified, the server does not automatically update this value; clients SHOULD provide an updated value when appropriate. If not included in an update, the server MUST leave it unchanged. If set to null, the server MUST set it to the current server time.

  • changed: "UTCDate" (server-set)

    The date the server last recorded a change to any property of this node. The server MUST automatically update this value whenever any property of the node is modified, including blobId, name, parentId, shareWith, executable, and all other mutable properties. This is not settable by clients.

  • isSubscribed: "Boolean" (default: true)

    This property is stored per user, and if true, the node should be displayed to the current user. Some servers may not allow this field to be changed for some or all nodes, for example only for directories, or only for nodes on which the shareWith ACL is actually set for this user, not nodes which inherit their ACL from a parent.

  • myRights: "FilesRights" (server-set)

    The set of rights (ACLs) the user has in relation to this node. A FilesRights object has the following properties:

    • mayRead: Boolean The user may read the properties and blob content of this node.

    • mayAddChildren: Boolean The user may create child nodes in this directory. This is always false for nodes whose @type does not permit children.

    • mayRename: Boolean The user may rename or move this node.

    • mayDelete: Boolean The user may destroy this node.

    • mayModifyContent: Boolean The user may update the content-related properties of this node (blobId, mediaType, target, modified, accessed, executable).

    • mayShare: Boolean The user may change the sharing of this node (see [JMAP-SHARING]).

  • shareWith: "Id[FilesRights]|null"

    A map of userId to rights for users this node is shared with. The owner of the node MUST NOT be in this set. This is null if the user requesting the object does not have myRights.mayShare, or if the node is not shared with anyone.

3.1.2. File properties

A FileNode with @type "File" is a regular file with blob content. In addition to the common properties, it has the following properties:

  • blobId: "Id"

    The blobId for the content of this file. This MUST be provided on creation, including for zero byte files. Setting blobId to null is an "invalidProperties" error. Updating the blobId replaces the file's content; the server MUST update size to match the new blob. A blob referenced by a FileNode MUST NOT be expired or garbage collected by the server while the FileNode exists.

  • size: "UnsignedInt" (server-set)

    The size in bytes of the associated blob data. Size is optional on create and update, but if provided it MUST match the size of the provided blobId. If the client updates blobId without providing size, the server sets size from the new blob and returns the size in the "updated" response.

  • mediaType: "String" (default: "application/octet-stream")

    The media type of the file. If not provided on creation, the server sets it to "application/octet-stream". Setting mediaType to null is an "invalidProperties" error. Valid values are found in the IANA media-types registry. Servers MUST NOT reject media types that are not recognised. Servers MUST reject media types if the value does not conform to the ABNF of [MEDIATYPE] Section 4.2.

  • executable: "Boolean" (default: false)

    If true, the file should be treated as an executable by operating systems that support this flag.

3.1.3. Directory properties

A FileNode with @type "Directory" is a collection that may contain child nodes. In addition to the common properties, it has the following property:

  • role: "String|null" (default: null)

    An indication that this directory has a special role. A directory with the "root" role should have a null parentId. Clients MUST ignore unrecognised role values. Values are registered in the "JMAP FileNode Roles" registry (see IANA Considerations).

3.1.5. FileNode Examples

A directory, a file inside it, and a symlink pointing to the file. Common properties other than @type, id, parentId, and name are omitted for brevity:

[
  {
    "@type": "Directory",
    "id": "n1",
    "parentId": null,
    "name": "Documents",
    "role": "documents"
  },
  {
    "@type": "File",
    "id": "n3",
    "parentId": "n1",
    "name": "report.pdf",
    "blobId": "Bfa61c0",
    "size": 48576,
    "mediaType": "application/pdf",
    "executable": false
  },
  {
    "@type": "SymLink",
    "id": "n7",
    "parentId": null,
    "name": "latest-report",
    "target": ["Documents", "report.pdf"]
  }
]

3.2. FileNode Methods

3.2.1. FileNode/get

This is a standard Foo/get method, with the following differences:

  • The @type property is always returned, even if it is not included in the properties argument.

  • If the properties argument includes a property that is defined for some FileNode type but not for a node's @type, the server omits that property from that node in the response. It does not return the property with a null value.

There is one additional request argument:

  • fetchParents: "Boolean" (default: false)

    If true, the server returns all ancestor nodes of the requested ids in addition to the requested nodes themselves. This allows a client to reconstruct the full path to each requested node in a single call. Ancestor nodes that have already been included (either because they were explicitly requested or because they are ancestors of multiple requested nodes) are not duplicated. The ancestors are returned as additional entries in the list array.

3.2.2. FileNode/changes

This is a standard Foo/changes method.

3.2.3. FileNode/set

This is a standard Foo/set method, with the following differences:

Since parentId creates a tree structure, an attempt to move a node to a parent for which this node is also an ancestor is an error, and an invalidProperties error will be returned.

A server MUST order creation and deletion operations within a single FileNode/set such that the uniqueness of sibling names is retained at the end of the transaction, but replacing an existing file can be done atomically.

There are these additional top level arguments:

  • onDestroyRemoveChildren: "Boolean" (default: false)

    If false, an attempt to destroy a FileNode which is the parentId of another FileNode will be rejected with a nodeHasChildren error. NOTE: if all the child nodes are being destroyed in the same operation, then the server MUST NOT return this error. Servers MUST either sort the destroys children before parents, or only check this constraint on the final state, remembering that JMAP set operations must be atomic.

    If true, then all child nodes will also be destroyed when a node is destroyed. When deleting child nodes, the server MUST include the ids of all deleted nodes in the method response.

  • onExists: "String|null" (default: null)

    If null, an attempt to create or update a FileNode which would cause a name collision will be rejected by the server with an "alreadyExists" error.

    If "replace", the existing item will be destroyed. In this case, the server MUST include the id of the replaced item in the destroyed response list. NOTE: if the replaced item is a Directory which has children, then the server MUST respond with a nodeHasChildren error to this action unless onDestroyRemoveChildren is true.

    Replacement does not depend on the @type of either node; a node of any type may replace a node of any other type.

    If "rename", the server will change the "name" field to not clash, using an algorithm of its choice. If the server changes the name, it MUST include the new "name" value in the created or updated response field for this id.

    If "newest", the server compares the modified timestamp of the incoming item with that of the existing FileNode. If the incoming item has a strictly later modified value, the existing node is destroyed and the operation proceeds as with "replace". Otherwise the operation is rejected as with null, returning an "alreadyExists" error with the existingId of the surviving node.

    If "unchanged", the operation proceeds as with "replace", but only if a precondition on the colliding node is satisfied. The client supplies this precondition in the "ifUnchangedBy" argument ([JMAP-CONDITIONAL]). The server matches it against the node that occupies the contested name at the start of the method, found by that node's current id: the server evaluates the "ifUnchangedBy" entry whose key is that id. If "ifUnchangedBy" contains no entry for the occupant's id, or that entry's precondition is not satisfied, the server MUST NOT replace the node. It rejects the operation as with null, and returns an "alreadyExists" error with the existingId of the surviving node. This binds the replacement to the one node the client expected. If a concurrent change has moved a different node into the contested name, its id does not match the supplied precondition. The server then refuses the replacement, and does not destroy a node the client never saw. As with "replace", if the node to replace is a Directory with children, the server MUST return a "nodeHasChildren" error unless onDestroyRemoveChildren is true; it MUST also include the id of the replaced node in the destroyed response list. A server that does not support [JMAP-CONDITIONAL] MUST reject "unchanged" with an "invalidArguments" error.

    If the existing node is not discoverable by the user (see "Access Control"), every value except "rename" fails with "forbidden": the server MUST NOT destroy or compare against that node, nor reveal its id. Servers may return "forbidden" for other reasons, such as a file name policy, so it does not confirm that the name is taken.

    "replace", "newest", and "unchanged" also require mayDelete on the existing node, and fail with "forbidden" without it.

  • compareCaseInsensitively: "Boolean" (default: false)

    If true, name collision checks (including onExists handling) treat names as case-insensitive for this request. This has no effect if the server already has caseInsensitiveNames set to true in its capability. This allows clients syncing to case-insensitive platforms to prevent name collisions that would be problematic on those platforms.

Errors (in addition to standard SetError codes from [JMAP-CORE]):

  • "alreadyExists" — a create or update would cause a name collision with a discoverable sibling FileNode, and onExists did not resolve it. The SetError object MUST include an existingId property with the id of the existing FileNode.

  • "forbidden" — a name collision with a sibling the user cannot discover, or an onExists replacement without mayDelete (see onExists above).

  • "invalidProperties" — an attempt was made to move a node to a descendant of itself, creating a cycle in the tree, to make a node the child of a node whose @type does not permit children, to change the @type of a node, or to set a property that is not defined for the node's @type to a non-null value.

  • "nodeHasChildren" — a destroy was attempted on a FileNode that has children, and onDestroyRemoveChildren is false.

3.2.4. FileNode/copy

This is a standard Foo/copy function with the same additional top-level arguments as FileNode/set, onDestroyRemoveChildren and onExists, with the same behaviour.

Errors: the same additional errors as FileNode/set apply.

3.2.5. FileNode/query

This is a standard Foo/query method except for the following:

There's one more property to the query:

  • depth: "UnsignedInt|null"

    The number of levels of subdirectories to recurse into. If absent, null, or zero, do not recurse.

The following filter criteria are defined:

  • isTopLevel: "Boolean"

    If true, the node must have a null parentId to match the condition.

  • parentId: "Id"

    A FileNode id. A node must have a parentId equal to this to match the condition.

  • ancestorId: "Id"

    A FileNode id. A node must have an ancestor (parent, parent of parent, etc.) with an id equal to this to match the condition.

  • descendantId: "Id"

    A FileNode id. A node must be an ancestor (parent, parent of parent, etc.) of the node with this id to match the condition. This is the inverse of ancestorId.

  • nodeType: "String"

    A node type value. Only nodes whose @type is precisely this value match this condition. This filter, and the nodeType sort property below, are named nodeType rather than @type; they operate on the @type property.

  • role: "String"

    A role name. Only nodes with precisely this role match this condition.

  • hasAnyRole: "Boolean"

    If true, any node with a defined role matches this condition. If false, any node which has a role does not match this condition.

  • blobId: "Id"

    A FileNode must have a blobId equal to this to match the condition.

  • isExecutable: "Boolean"

    If true, the FileNode must have a true executable value.

  • createdBefore: "UTCDate"

    The creation date of the node must be before this date to match the condition.

  • createdAfter: "UTCDate"

    The creation date of the node must be on or after this date to match the condition.

  • modifiedBefore: "UTCDate"

    The modified date of the node must be before this date to match the condition.

  • modifiedAfter: "UTCDate"

    The modified date of the node must be on or after this date to match the condition.

  • accessedBefore: "UTCDate"

    The accessed date of the node must be before this date to match the condition.

  • accessedAfter: "UTCDate"

    The accessed date of the node must be on or after this date to match the condition.

  • minSize: "UnsignedInt"

    The size of the node in bytes must be equal to or greater than this number to match the condition.

  • maxSize: "UnsignedInt"

    The size of the node in bytes must be less than this number to match the condition.

  • name: "String"

    A FileNode must have exactly the same octets in its name property to match the condition.

  • nameMatch: "String"

    Matches the name property of the node using glob syntax. The following special patterns are defined: * matches any sequence of characters, ? matches any single character, and a pair of brackets matches any single character in the bracketed set (for example, [abc] matches "a", "b", or "c"; [a-z] matches any lowercase letter; [!abc] or [^abc] matches any character not in the set). All other characters match literally. The match is case-insensitive.

  • mediaType: "String"

    A FileNode must have exactly the same octets in its mediaType property to match the condition.

  • mediaTypeMatch: "String"

    Matches the mediaType property of the node using the same glob syntax as nameMatch.

  • body: "String"

    Match the content of the referenced blob, see the definition of body in section 4.4.1 of [JMAP-MAIL]. The server may use any technology to extract meaningful text from the blob for searching, or interpret the string in any way, to choose the nodes that it believes the user wants to see.

  • text: "String"

    Is equivalent to body OR nameMatch OR mediaTypeMatch.

The blobId, isExecutable, minSize, maxSize, mediaType, mediaTypeMatch, and body conditions match only nodes whose @type is "File". The role and hasAnyRole conditions match only nodes whose @type is "Directory". This applies to both true and false values. For example, a hasAnyRole condition of false matches only Directories without a role, and an isExecutable condition of false matches only Files that are not executable.

It also supports the following additional sort properties:

  • tree:

    Sort by tree; which means by name, but any Directory node is immediately followed by the recursive application of the same sort to its child nodes. This is similar to the output of the find command on a filesystem with the depth parameter provided above.

  • nodeType:

    Sort by @type. Directories sort first, then symlinks, then files, then nodes of any other @type ordered by the @type value.

When sorting by a property that is not defined for a node's @type (for example size on a Directory), nodes without the property sort before nodes that have it when isAscending is true. Nodes that lack the property are ordered among themselves as if that Comparator were absent from the sort.

3.2.6. FileNode/queryChanges

This is a standard Foo/queryChanges method.

4. Direct HTTP Write

When the server advertises a webWriteUrlTemplate, clients can update the content of a File directly via HTTP without using FileNode/set. The URL is constructed by expanding the template with the FileNode id. This is only valid for nodes with @type "File"; requests targeting any other type of node MUST be rejected with an HTTP 400 status.

The client MUST have mayModifyContent permission on the node. Requests without sufficient permission MUST be rejected with an HTTP 403 status. Authentication uses the same mechanism as other JMAP HTTP endpoints.

4.1. PUT

A PUT request replaces the entire content of the node. The request body is the new content. The Content-Type header sets the mediaType of the node. If the request has no Content-Type header, the server sets mediaType to "application/octet-stream".

On success, the server MUST update the node's blobId, size, and mediaType properties, and return an HTTP 200 response with a JSON body:

{
  "blobId": "Bnewblob123",
  "size": 48576,
  "mediaType": "text/plain"
}

4.2. PATCH

A PATCH request applies a partial modification to the node's current content. The Content-Type of the request indicates the patch format. The supported patch formats are those listed in the supportedPatchTypes capability of [JMAP-BLOBEXT] (for example, application/x-rdiff-delta, application/x-bsdiff, text/x-diff). The server applies the delta to the node's current blob to produce the new content.

On success, the server MUST update the node's blobId and size properties. The server MUST NOT change the node's mediaType. A client that needs to change it uses PUT, or updates mediaType with FileNode/set. The response is an HTTP 200 with the same JSON body as PUT:

{
  "blobId": "Bpatched456",
  "size": 49152,
  "mediaType": "text/plain"
}

Servers that do not support a given patch format MUST respond with HTTP 415 (Unsupported Media Type).

5. Access Control

Since nodes create a tree, ACLs created by shareWith automatically apply to children as well, so if mayRead is set on a node, all its child nodes are also readable. The myRights property on every node reflects the derived rights for the current user, taking inheritance into account.

When a shareWith change on a node causes the derived myRights to change for any descendant, the server MUST report those descendants as changed in FileNode/changes responses. The server SHOULD NOT report descendants whose derived myRights did not actually change, but MAY do so if it cannot efficiently determine whether the derived values differ.

If a server does not support changing access on non-directory nodes, it can set mayShare to false on those nodes, even if the parent directory has true.

Nodes which are not "discoverable" MUST return notFound errors if fetched with FileNode/get and MUST NOT be returned in response to FileNode/query. Nodes are discoverable in one of two ways:

  1. If the node or an ancestor of the node has mayRead true.

  2. If the node is an ancestor of a node which has mayRead true.

In the second case, the node itself will have mayRead false, and appears only to give the full path to the visible nodes. This means that a query with that node as the parent will only return those of its children which are otherwise discoverable through the second discoverability rule.

This leads to a sharee seeing the full path to anything that they have access to, but nothing else.

For example, in a unix homedirectory environment where user Alice had shared the "forBob" folder, one might see, when logged in as Bob:

/home
  /alice
    /shared
      /forBob
        file1.jpg
        file2.txt
        ...
  /bob
    bobfile.txt
    ...

While Alice would see many more files and folders at the home directory level.

This does lead to potentially large changes in the visible Node set, so when Alice first shared that directory, Bob would have been told of a large number of new Nodes in response to a FileNode/changes query. The server might report these as "created" or "updated" — it is not always possible for the server to know whether a node was previously visible to the user.

6. Quotas

Servers that support JMAP Quotas ([JMAP-QUOTAS]) SHOULD provide Quota objects with "FileNode" in the types array. Both the octets resource type (total storage used by file node blobs) and the count resource type (number of FileNode objects) are applicable.

7. Integration with JMAP Blob Extensions

When a server advertises both urn:ietf:params:jmap:filenode and urn:ietf:params:jmap:blobext, the ArchiveEntry object type (defined in [JMAP-BLOBEXT]) is extended with two additional properties:

A FileNode with @type "SymLink", whether referenced directly by nodeId or reached by recurse, is added to the archive as an entry with entryType "symlink". The server never follows the symlink. The entry's linkTarget is the elements of the SymLink's target joined with "/". An absolute target, whose first element is the empty string, therefore produces a linkTarget that starts with "/".

Some archive formats cannot hold every node type. For example, "application/zip" has no symlink entries. The ArchiveRecipe (defined in [JMAP-BLOBEXT]) is extended with one additional property that controls how the server handles such nodes:

This property applies only to entries that the server builds from FileNodes. An entry that the client gives in full, with an unsupported entryType, is still an "invalidProperties" error as [JMAP-BLOBEXT] requires.

If nodeId references a FileNode that the user does not have permission to read, or that does not exist, the server MUST reject the Blob/convert creation with a notFound SetError.

7.1. Examples

To archive an entire directory tree rooted at FileNode "dirabc" into a zip file:

json [["Blob/convert", { "accountId": "account1", "create": { "myarchive": { "archive": { "type": "application/zip", "entries": [{ "nodeId": "dirabc", "name": "/", "recurse": true }] } } } }, "R1"]]

To archive specific files from different locations along with a full subdirectory:

json [["Blob/convert", { "accountId": "account1", "create": { "selective": { "archive": { "type": "application/zip", "entries": [ { "nodeId": "filenode1", "name": "README.txt" }, { "nodeId": "dirnode2", "name": "docs/", "recurse": true } ] } } } }, "R1"]]

7.2. Extracting Archives into FileNodes

When both capabilities are present, the ExtractRecipe (defined in [JMAP-BLOBEXT]) is extended with the following additional properties:

  • parentNodeId: "Id|null" The id of a FileNode with @type "Directory". When present, the server extracts the archive contents into this directory, creating FileNode objects for each entry. Subdirectories in the archive are created as FileNodes with @type "Directory". File entries become FileNodes with @type "File" and their blobId set to the extracted content. The created and modified timestamps are taken from the archive entry metadata. If the archive entry includes a mode field, the server SHOULD use it to set the executable property on the created FileNode.

    Symlink entries become FileNodes with @type "SymLink" if "SymLink" is in the supportedNodeTypes capability. The server splits the entry's linkTarget on "/" to produce the target path elements. A linkTarget that starts with "/" produces an empty string as the first element, which marks an absolute path. The server drops any other empty elements.

    An entry is unsupported if the server cannot create a FileNode for it. These are symlink entries when "SymLink" is not in supportedNodeTypes, and entries of any type that has no FileNode equivalent ("hardlink", "fifo", "blockDevice", and "charDevice"). The onUnsupportedEntry property controls how the server handles them.

  • onUnsupportedEntry: "String" (default: "error") If "error", the server rejects the Blob/convert creation with an "unsupportedEntry" SetError, and creates no FileNodes. If "skip", the server does not create a FileNode for an unsupported entry, and extracts the other entries as usual. A skipped entry still appears in the response entries array, with a null nodeId. Any other value is an "invalidProperties" error.

  • onExists: "String|null" (default: null) Controls the behaviour when an extracted entry would collide with an existing FileNode name under the same parent. The values and semantics are the same as for FileNode/set (see above): null rejects with an alreadyExists SetError, "replace" destroys the existing node, "rename" gives the new node a server-chosen name, and "newest" replaces the existing node only if the archive entry has a strictly later modified timestamp.

When parentNodeId is set, each ArchiveEntry in the response entries array includes an additional nodeId property containing the id of the FileNode that was created for that entry, or null if the server skipped the entry.

If parentNodeId references a FileNode that does not exist, is not a Directory, or for which the user does not have mayAddChildren permission, the server MUST reject the Blob/convert creation with an appropriate SetError (notFound, invalidProperties, or forbidden).

7.2.1. Example

Extracting a zip file into a directory:

json [["Blob/convert", { "accountId": "account1", "create": { "e1": { "extract": { "blobId": "Barchive123", "type": "application/zip", "parentNodeId": "dir456" } } } }, "R1"]]

The response includes a nodeId for each created entry:

json [["Blob/convert", { "accountId": "account1", "created": { "e1": { "id": "Barchive123", "type": "application/zip", "size": 104857, "entries": [ { "name": "docs/", "entryType": "directory", "nodeId": "node001" }, { "name": "docs/readme.txt", "blobId": "Bdd001", "entryType": "file", "modified": "2026-03-01T12:00:00Z", "mode": "0644", "nodeId": "node002" }, { "name": "docs/logo.png", "blobId": "Bdd002", "entryType": "file", "modified": "2026-02-15T09:30:00Z", "mode": "0644", "nodeId": "node003" } ] } }, "notCreated": {} }, "R1"]]

7.3. The unsupportedEntry Error

An "unsupportedEntry" SetError means that an archive or extraction with onUnsupportedEntry set to "error" found one or more entries that it could not handle. The SetError object MUST include an entryNames property: a "String[]" that lists the name of each such entry. For an archive, this is the name that the entry would have had in the archive.

8. Extended Attributes and Named Streams

Operating systems associate named side-channel data with files: POSIX extended attributes (getxattr/setxattr), macOS extended attributes (including Finder tags and the quarantine flag) and resource forks, NTFS and SMB2 alternate data streams, and NFSv4 named attributes. A FileNode carries these using the JMAP Object Metadata extension ([JMAP-METADATA]). A server that supports named attributes on FileNodes lists "FileNode" in the "dataTypes" of its urn:ietf:params:jmap:metadata capability. Clients then read and write the attributes through the "metadata" property, and through the "privateMetadata" property where the server sets supportsPrivate. That extension adds both properties to the FileNode object. They are common properties, available on FileNodes of every @type, because platforms also attach named attributes to directories and symbolic links.

8.1. The "xattr" namespace

This document defines the "xattr" namespace for the "metadata" and "privateMetadata" properties. Its value is an object mapping an attribute name to an XattrValue object. The attribute name is the platform-native name of the attribute or stream, stored verbatim (for example "user.comment", "com.apple.metadata:_kMDItemUserTags", or "Zone.Identifier"). Servers and clients MUST NOT normalise attribute names across platforms. The server keeps an attribute that one platform writes, even where another platform has no equivalent for it.

An XattrValue object has the following properties:

  • data: "String|null"

    The attribute value as a UTF-8 string. Used for small textual attributes.

  • blobId: "Id|null"

    The blobId of a blob holding the attribute value. Used for large or binary attributes (for example a resource fork, or a multi-kilobyte stream). A blob referenced by an XattrValue MUST NOT be expired or garbage collected by the server while the FileNode references it, in the same way as the content blob of a File.

  • type: "String|null" (default: null)

    An optional media type ([MEDIATYPE]) for the attribute value.

An XattrValue MUST contain exactly one of "data" or "blobId"; providing both, or neither, is an "invalidProperties" error. Binary attributes small enough to carry inline are still referenced by "blobId" rather than base64-encoded in "data".

The value nests one object level below the namespace value (attribute name to XattrValue). A server that supports the "xattr" namespace MUST advertise a "maxDepth" for FileNode of at least 1, or null (no limit).

Attributes shared with every reader of the node (for example a quarantine flag) are stored under "metadata". Attributes private to the current user (for example that user's own tags) are stored under "privateMetadata" where the server supports it.

Example — a small textual attribute and a large binary one:

{
  "metadata": {
    "xattr": {
      "user.comment": { "data": "draft copy" },
      "com.apple.ResourceFork": {
        "blobId": "G_fork_9c2",
        "type": "application/octet-stream"
      }
    }
  }
}

To set or remove a single attribute without disturbing the others, a client patches the path directly. RFC 6901 escaping applies to any "/" or "~" in the attribute name ("/" as "~1", "~" as "~0"):

[["FileNode/set", {
  "accountId": "u1",
  "update": {
    "f42": {
      "metadata/xattr/Zone.Identifier": { "data": "ZoneId=3" }
    }
  }
}, "0"]]

Setting an attribute's path to null removes it, following the "metadata" patch semantics of [JMAP-METADATA].

9. Implementation Considerations

9.1. Modification Timestamps

Clients SHOULD provide an updated modified value when modifying a node. This gives clients control over the timestamp, for example when preserving original modification times during file synchronization. Clients that do not care about preserving timestamps can set modified to null to have the server use the current time.

Clients may also choose to update the modified timestamp on a parent directory when moving a child node in or out, to reflect that the directory's contents have changed.

9.2. Timestamp Precision

The UTCDate type (RFC 3339) supports fractional seconds. Servers SHOULD include sub-second precision in created, modified, accessed, and changed timestamps where their storage backend supports it. Clients MUST preserve fractional seconds when they send timestamps back to the server.

9.3. Filename Character Restrictions

Servers should set forbiddenNameChars and forbiddenNodeNames to include characters and names that are problematic for their storage backend and for the broadest set of clients.

The following characters are problematic on common platforms:

  • Windows: <, >, :, ", \, |, ?, *, and control characters (U+0000-U+001F). Windows also forbids trailing dots and spaces in names.

  • macOS: : (HFS+ legacy; displayed as / in Finder).

  • Linux: the null byte (U+0000).

The following names are problematic:

  • All platforms: . and .. (path traversal).

  • Windows: reserved device names CON, PRN, AUX, NUL, COM0-COM9, and LPT0-LPT9. These are reserved regardless of file extension (for example, "CON.txt" is also forbidden).

A server targeting broad client compatibility should include at least /<>:"\|?* in forbiddenNameChars and at least ., .., and the Windows reserved names in forbiddenNodeNames.

9.4. Case Sensitivity

Windows and macOS filesystems are typically case-insensitive (treating "Foo" and "foo" as the same name), while Linux filesystems are typically case-sensitive. Clients syncing to case-insensitive platforms should use compareCaseInsensitively on FileNode/set requests to prevent creating sibling nodes whose names differ only in case, as these cannot coexist on the target platform.

9.5. Access Time Updates

Updating accessed on every file read generates significant API traffic for little value. Clients are encouraged to use a "relatime" strategy similar to modern POSIX filesystems: only update accessed if the current value is older than modified, or if more than 24 hours have elapsed since the last accessed update. Clients should batch access time updates with their next JMAP request rather than making a dedicated call.

10. Security considerations

All security considerations from [JMAP-CORE] apply to this document.

10.1. Path Traversal

Clients that reconstruct filesystem paths from FileNode hierarchies MUST validate the resulting paths and reject any that would escape the intended directory tree. Servers SHOULD include / in forbiddenNameChars and . and .. in forbiddenNodeNames to prevent path traversal attacks via node names.

10.3. Denial of Service

Deep nesting of FileNodes or very large numbers of children under a single parent can consume significant server resources. Servers SHOULD enforce reasonable limits via maxFileNodeDepth and MAY impose additional limits on the number of children per node. Recursive operations such as destroying a subtree with onDestroyRemoveChildren can be expensive; servers SHOULD impose limits on the size of subtrees that can be destroyed in a single operation.

10.4. Access Control

The discoverability rules defined in this document mean that ancestor nodes of shared content are visible (with mayRead false) to users who have access to descendants. While these ancestor nodes do not expose file content, their names and structure may reveal information about the file hierarchy. Server administrators and users sharing content should be aware of this.

Changes to the shareWith property on a node affect all descendants. Removing sharing from a parent node will make all descendants undiscoverable to the affected users, which may be surprising if those users had been actively working with the shared files.

10.5. Content Security

Servers that perform content scanning or malware detection SHOULD scan blobs referenced by FileNode objects. The mediaType property is client-asserted and MUST NOT be trusted for security decisions; servers SHOULD independently verify content types where this matters.

11. IANA considerations

11.1. JMAP Capability registration for "filenode"

IANA is requested to register the "filenode" JMAP Capability as follows:

Capability Name: urn:ietf:params:jmap:filenode

Specification document: this document

Intended use: common

Change Controller: IETF

Security and privacy considerations: this document, Security Considerations

11.2. JMAP Error Codes registration for "nodeHasChildren"

IANA is requested to register the "nodeHasChildren" JMAP Error Code as follows:

JMAP Error Code: nodeHasChildren

Intended use: common

Change Controller: IETF

Description: The node being destroyed is still referenced by other nodes which have not been destroyed.

Reference: this document

11.3. JMAP Error Codes registration for "unsupportedEntry"

IANA is requested to register the "unsupportedEntry" JMAP Error Code as follows:

JMAP Error Code: unsupportedEntry

Intended use: common

Change Controller: IETF

Description: An archive or extraction found entries that the archive format or the server cannot represent, and the client asked for an error rather than for the entries to be skipped.

Reference: this document

11.4. JMAP Data Types registration for "FileNode"

IANA is requested to register the "FileNode" JMAP Data Type as follows:

Type Name: FileNode

Can Reference Blobs: Yes

Can Use For State Change: Yes

Capability: urn:ietf:params:jmap:filenode

Reference: this document

11.5. JMAP FileNode Types Registry

IANA is requested to create a new "JMAP FileNode Types" registry with the following initial values. New registrations are subject to Specification Required ([IANA-GUIDELINES]). A registration MUST reference a specification that defines the properties specific to the type and whether nodes of the type may have children.

Table 1
Type Description Reference
File A regular file with blob content this document
Directory A collection that may contain child nodes this document
SymLink A symbolic link to another path in the tree this document

11.6. JMAP FileNode Roles Registry

IANA is requested to create a new "JMAP FileNode Roles" registry with the following initial values. New registrations are subject to Expert Review ([IANA-GUIDELINES]).

Table 2
Role Description Reference
root The base of a filesystem this document
home A user's home directory this document
temp Temporary space; may be cleaned up automatically this document
trash Deleted data; may be removed automatically or manually this document
documents Document storage this document
downloads Downloaded files this document
music Audio files this document
pictures Photos and images this document
videos Video files this document

11.7. JMAP Metadata Namespace Registration for "xattr"

IANA is requested to register the "xattr" namespace in the "JMAP Metadata Namespaces" registry established by [JMAP-METADATA]:

Namespace Identifier: xattr

Value Type: String[XattrValue] — an object mapping an attribute name to an XattrValue object, as defined in "Extended Attributes and Named Streams" of this document.

Applicable Properties: metadata, privateMetadata

Applicable Data Types: FileNode

Reference: this document

Intended Usage: common

Change Controller: IETF

12. TODO

13. Changes

EDITOR: please remove this section before publication.

The source of this document exists on github at: https://github.com/brong/draft-gondwana-jmap-filenode/

draft-ietf-jmap-filenode-16

draft-ietf-jmap-filenode-15

draft-ietf-jmap-filenode-14

draft-ietf-jmap-filenode-13

draft-ietf-jmap-filenode-12

draft-ietf-jmap-filenode-11

draft-ietf-jmap-filenode-10

draft-ietf-jmap-filenode-09

draft-ietf-jmap-filenode-08

draft-ietf-jmap-filenode-07

draft-ietf-jmap-filenode-06

draft-ietf-jmap-filenode-05

draft-ietf-jmap-filenode-04

draft-ietf-jmap-filenode-03

draft-ietf-jmap-filenode-02

draft-ietf-jmap-filenode-01

draft-ietf-jmap-filenode-00

draft-gondwana-jmap-filenode-01

draft-gondwana-jmap-filenode-00

14. Acknowledgements

Ben Bucksch, Mauro De Gennaro, Seph Gentle, Arnt Gulbradson, Neil Jenkins, Ricardo Signes, and members of the JMAP Working Group at IETF.

{backmatter}

15. References

15.1. Normative References

[IANA-GUIDELINES]
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>.
[JMAP-BLOBEXT]
Gondwana, B., "JMAP Blob Extensions", Work in Progress, Internet-Draft, draft-gondwana-jmap-blobext-06, , <https://datatracker.ietf.org/doc/html/draft-gondwana-jmap-blobext-06>.
[JMAP-CONDITIONAL]
Gondwana, B., "JMAP Conditional Set", Work in Progress, Internet-Draft, draft-gondwana-jmap-conditional-00, , <https://datatracker.ietf.org/doc/html/draft-gondwana-jmap-conditional-00>.
[JMAP-CORE]
Jenkins, N. and C. Newman, "The JSON Meta Application Protocol (JMAP)", RFC 8620, DOI 10.17487/RFC8620, , <https://www.rfc-editor.org/rfc/rfc8620>.
[JMAP-METADATA]
De Gennaro, M., "JMAP Object Metadata", Work in Progress, Internet-Draft, draft-ietf-jmap-metadata-02, , <https://datatracker.ietf.org/doc/html/draft-ietf-jmap-metadata-02>.
[JMAP-SHARING]
Jenkins, N., Ed., "JSON Meta Application Protocol (JMAP) Sharing", RFC 9670, DOI 10.17487/RFC9670, , <https://www.rfc-editor.org/rfc/rfc9670>.
[MEDIATYPE]
Freed, N., Klensin, J., and T. Hansen, "Media Type Specifications and Registration Procedures", BCP 13, RFC 6838, DOI 10.17487/RFC6838, , <https://www.rfc-editor.org/rfc/rfc6838>.
[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>.
[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>.
[URI-TEMPLATE]
Gregorio, J., Fielding, R., Hadley, M., Nottingham, M., and D. Orchard, "URI Template", RFC 6570, DOI 10.17487/RFC6570, , <https://www.rfc-editor.org/rfc/rfc6570>.

15.2. Informative References

[CALDAV]
Daboo, C., Desruisseaux, B., and L. Dusseault, "Calendaring Extensions to WebDAV (CalDAV)", RFC 4791, DOI 10.17487/RFC4791, , <https://www.rfc-editor.org/rfc/rfc4791>.
[CARDDAV]
Daboo, C., "CardDAV: vCard Extensions to Web Distributed Authoring and Versioning (WebDAV)", RFC 6352, DOI 10.17487/RFC6352, , <https://www.rfc-editor.org/rfc/rfc6352>.
[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>.
[JMAP-CONTACTS]
Jenkins, N., Ed., "JSON Meta Application Protocol (JMAP) for Contacts", RFC 9610, DOI 10.17487/RFC9610, , <https://www.rfc-editor.org/rfc/rfc9610>.
[JMAP-MAIL]
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>.
[JMAP-QUOTAS]
Cordier, R., Ed., "JSON Meta Application Protocol (JMAP) for Quotas", RFC 9425, DOI 10.17487/RFC9425, , <https://www.rfc-editor.org/rfc/rfc9425>.
[UNICODE]
Klensin, J. and M. Padlipsky, "Unicode Format for Network Interchange", RFC 5198, DOI 10.17487/RFC5198, , <https://www.rfc-editor.org/rfc/rfc5198>.
[WEBDAV]
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>.

Author's Address

Bron Gondwana
Fastmail