> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oximail.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Files

> The JMAP File Storage surface (draft-ietf-jmap-filenode): FileNode trees, blobs, version history, trash, per-viewer stars, thumbnails, and anonymous share links.

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

| Object          | Methods                                                   | Notes                                                                                  |
| --------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `FileNode`      | `get`, `set`, `query`, `queryChanges`, `changes`, `share` | Files and directories in one tree.                                                     |
| `FileVersion`   | `get`, `query`, `restore`                                 | Immutable content history of one node — see [version history](#version-history) below. |
| `FileShareLink` | `get`, `set`                                              | Anonymous links to a node: expiry, download caps, optional password.                   |

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:

| Property        | Notes                                                       |
| --------------- | ----------------------------------------------------------- |
| `id`            | The version's own id.                                       |
| `fileNodeId`    | The node this version belongs to.                           |
| `versionNumber` | Monotonic per node.                                         |
| `blobId`        | Download it like any blob.                                  |
| `size`          | Captured at snapshot time.                                  |
| `contentType`   | `string \| null` — the key is always present, never absent. |
| `createdBy`     | `string \| null`, same rule.                                |
| `createdAt`     | Server-set (UTCDate).                                       |

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](../operator/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](./jmap-mail).
