Skip to main content
The drive is the JMAP File Storage draft surface (FileNode objects over the standard blob machinery): a tree of files and directories per account, shareable per node, with anonymous download links as a separate object.

Objects and methods

Content moves over the standard JMAP blob endpoints (/jmap/upload, /jmap/download) — a FileNode create references an uploaded blobId.

Version history

Every content update to a node snapshots the previous blob into its version history, in the same transaction as the update. The current content always lives on the node itself; FileVersion is history only, and every one of its properties is immutable: The urn:ietf:params:jmap:files capability advertises maxVersionsPerNode (10 at v0.30.0). It is the retention window and the pruning bound from one constant: versions past the newest N are pruned in the same transaction that created the newest one, and their blobs are released through the refcounted destroy path — never a direct delete, since a blob may be shared by deduplication. A client should gate its “version history” menu entry on the presence of that key rather than assuming the surface exists.
  • FileVersion/query requires a fileNodeId filter. Versions are meaningless as an account-wide list, and requiring the anchor keeps the surface bounded by the retention cap. Newest first by default. Its queryState is the FileNode collection state — versions only change when a node’s content changes, which moves that state, so one cursor is coherent for both. canCalculateChanges is false: there is no separate version change log, so re-query when FileNode changes.
  • FileVersion/restore never destroys history. It goes through the same storage chokepoint as a FileNode/set { blobId } content update, so the content you are currently on is snapshotted as a new version before being replaced — the version you came from stays reachable, and pruning applies as usual. size and contentType are restored from the version record, which is authoritative because it captured them at snapshot time. Restoring the version whose blob is already current is a content no-op but still answers success. It takes an optional ifInState for the same optimistic concurrency as FileNode/set, checked atomically with the write.
  • Access follows the node. Reading the version list needs mayRead on the owning node, restore needs mayWrite (a read-only shared mount answers forbidden). A node that does not exist and a node a shared caller may not read answer with identical wording — the version surface is not an existence oracle.

Behaviours worth knowing

  • type is honored. type: "directory" creates a directory; the type field is validated, never guessed from the presence of a blob.
  • The tree is rooted; the wire uses null. Every account has a storage root; parentId: null in a query means “top level”. Directory moves are cycle-checked — a move that would create a parent cycle is rejected.
  • Trash rides the same transaction. isTrashed updates are atomic with the rest of the update (a recursive directory trash is one transaction), and trashed nodes restore from the visible trash (ADR-035).
  • Stars are per-viewer. Starring a shared file marks your view; the owner’s object is untouched.
  • Thumbnails are server-derived for previewable types — clients do not upload their own.
  • Share links are scoped and accounted. A FileShareLink grants access to exactly its node subtree, enforces maxDownloads exactly, refunds quota on deletion, and an anonymous reader can only decrypt blobs referenced by the files projection of the resolved account — the crypto layer backs the ACL (encryption at rest).
Blob deduplication is per organization and content-addressed: uploading the same file twice stores it once. Strictness rules are identical to the mail surface.