[data-reveal]{opacity:1!important;transform:none!important}
Components

File storage

Wire a storage backend for the file and image types, configure FileType constraints, and understand the staged-upload lifecycle.

The file and image DataTypes (Data types) give forms their upload fields. Behind them sit a storage backend and an upload lifecycle with one rule to remember: every upload lands on a temporary staged key, promoted to its final key only when a form saves.

The storage backend

The DMS talks to storage through the AntelopeJS interface-file-storage contract: load any module that implements it in your antelope.config.ts, and the file routes pick it up — nothing to wire on the DMS side. Each backend carries its own config; when it defines several named storages, a FileType's storage option selects one, and omitting it uses the default.

In production, avoid a local-disk backend: the files live on one machine's filesystem, so they don't survive an instance being replaced and can't be shared between instances. Use a remote storage service.

Declare a file field

A file field is a form field whose type is a FileType:

{
  id: "avatar",
  label: "Avatar",
  type: new DefaultDataTypes.FileType({
    multiple: false,
    constraints: { maxSize: 2 * 1024 * 1024, allowedMimetypes: ["image/*"] },
  }),
}

FileType options:

OptionTypePurpose
multiplebooleanAccept several files; the value becomes an array of keys.
constraintsUploadConstraintsmaxSize (bytes) and allowedMimetypes (wildcards like image/* supported). Omitted, the field accepts any size and type.
pathstringStorage path the uploaded keys are filed under.
storagestringNamed storage to upload to, when the backend defines several. Omit for the default.
fallbackstringText rendered in place of an empty value (table cells, read views).

The upload lifecycle

From the user picking a file to the record holding its final key:

  1. Upload to staging. The form obtains a presigned upload URL, the browser sends the bytes directly to storage under a temporary staged key, and the form stores that key as its value. The presign request carries a signed upload token — stamped into the field when the page registered, binding the field's authoritative storage/path — so a client can never presign an upload into a destination the field does not define. The key itself is server-generated, so nothing can be overwritten, and the token grants no reads.
  2. Promote on save. When the form submits, the new/edit route promotes every staged key to its final location and writes the promoted keys into the row. If the database write fails after promotion, the files roll back to their staged keys.
  3. Cleanup. Editing a row deletes the files it no longer references; deleting a row deletes its files.
Promotion only happens when a TableView form saves. A form abandoned after the upload leaves the file at its staged key — clearing abandoned staged uploads is the storage backend's own garbage collection, not a DMS job. An upload path you build yourself must promote its staged keys itself.

Upload tokens and custom components

Form and TableView stamp the tokens of every upload field they carry — including the forms they embed, like a relation field's inline add form — when they serialize, at page registration. A custom component that embeds form fields of its own is its fields' host and does the same: attach StampUploadFieldTokens (from interfaces/cms/uploads) through transformOptions on its builder. The contract is fail-closed — a field nobody stamped renders fine and presign refuses it with a 403.

Storage paths are not tenant-scoped: file isolation comes from server-generated, unguessable keys referenced by tenant-scoped records, not from the path. The path option is a layout choice, not a security boundary.