File upload fields
A file field is an ordinary string property with a storage block. The
column holds text — the object’s key in the bucket, or its URL if you ask for
that — and the block says where the bytes go and what the form will accept.
That split matters more than it looks. Most of storage is read by the admin
panel’s uploader on its way to the API, so a write that does not come from a
form does not go through it — those options carry the label below. The two that
decide what a file may be, maxSize and acceptedFiles, are the exception:
the server checks them on every upload into the property’s storagePath.
One file
Section titled “One file”avatar: { type: "string", name: "Main image", storage: { storagePath: "avatars", acceptedFiles: ["image/*"], maxSize: 2 * 1024 * 1024 }}Several files
Section titled “Several files”An array of the same thing. The storage block moves to of, because it
describes each element rather than the list:
images: { type: "array", name: "Images", of: { type: "string", storage: { storagePath: "images", acceptedFiles: ["image/*"] } }}Storage options
Section titled “Storage options”Everything inside storage.
| Property | Type | Description |
|---|---|---|
storagePath |
string | function |
Required. Where in the bucket the file goes. A string with placeholders — {file}, {file.name}, {file.ext}, {rand}, {entityId}, {propertyKey}, {path} — or a function of the upload’s context. Admin form only — not enforced by the API or the database: it is the panel’s uploader that resolves it, and client.storage.upload() names its own key. A string path is also where the server holds every upload to this property’s maxSize and acceptedFiles |
fileName |
string | function |
The uploaded object’s file name, with the same placeholders. Admin form only — not enforced by the API or the database. |
storageSource |
string |
Which registered bucket, when a project has more than one. The bucket("media") handle works here too |
public |
boolean |
Store under the public prefix and serve through a stable, token-less, cacheable URL. false by default, which means private objects and short-lived signed URLs |
acceptedFiles |
FileType[] |
MIME types this property takes. The asterisk form works — image/*. Enforced by the server for every upload into the property’s storagePath — see What the server checks |
maxSize |
number |
Largest accepted file, in bytes. Enforced by the server, like acceptedFiles, on the plain and the resumable upload paths alike |
metadata |
Record<string, unknown> |
Object metadata to store alongside the file |
includeBucketUrl |
boolean |
Write s3://my-bucket/path/to/file.png into the column instead of path/to/file.png. false by default |
storeUrl |
boolean |
Write the download URL into the column instead of the key. false by default, and worth leaving that way: a URL that carries a token stops working when the token does, and the original reference is then gone |
imageResize |
ImageResize |
Resize and crop before uploading, for image/jpeg, image/png and image/webp. Admin form only — not enforced by the API or the database. |
processFile |
(file: File) => Promise<File> |
Transform the file before it is uploaded. Runs in the browser. Admin form only — not enforced by the API or the database. |
postProcess |
(pathOrUrl: string) => Promise<string> |
Transform the resolved path or URL before it is saved. Runs in the browser. Admin form only — not enforced by the API or the database. |
previewUrl |
(fileName: string) => string |
Build the preview URL, for when the file’s real address is not the field’s value. Admin form only — not enforced by the API or the database. |
Resizing
Section titled “Resizing”imageResize takes maxWidth and maxHeight in pixels, a mode of
"contain" (scale down to fit, the default) or "cover" (fill the bounds and
crop), a format of "original", "jpeg", "png" or "webp", and a
quality between 0 and 100 for the lossy formats — 80 by default. It happens in
the browser, before the bytes are sent, so an upload that did not come from the
panel is stored at its original size.
What the server checks
Section titled “What the server checks”maxSize and acceptedFiles used to be enforced by exactly one thing: the
browser. The upload route knew a single global size cap and nothing about the
property a file was destined for, so curl -F file=@payload.exe past the picker
put a 40 MB executable in the avatar bucket and the config that said otherwise
was never consulted.
Both are now checked by the server against where the file lands. On
0.23 they are checked only against the property an upload names, and an upload
that names none meets the source’s own cap alone. A
property’s storagePath is where its files go, so it is also where its limits
hold: every upload whose key is a file directly under that path — from the
panel, client.storage.putObject, a resumable upload, Studio’s storage browser
or curl — meets them, whether or not it names the property, on the property’s
own storage source. Placeholders match whatever the panel would fill in:
{entityId}, {rand}, {file} and the like stand for one path segment,
{path} for any number. A public: true property’s path is under public/.
- Several properties sharing one path: a file one of them accepts is accepted.
- An upload that also names its property (
collection+property, asputObject({ context })sends them) is held to that property too. Naming a looser property does not unlock a stricter one’s path. The panel’s uploads (file fields, table cells and markdown images) always name it. - A
storagePathwritten as a function cannot be read without running it, so it has no server-side rule: those limits hold only for uploads that name the property, which the panel’s do. - A path no property claims is held to the source’s own limit
(
STORAGE_MAX_FILE_SIZE) alone.
Images in a markdown field
Section titled “Images in a markdown field”A markdown property (admin.markdown: true) with a storage block takes image
uploads in its editor. The text stores each image as a storage reference —
 — not as a URL: a private file’s URL
carries a download token that expires in minutes, and the text is saved. The
admin panel’s editor and previews exchange each reference for a fresh URL when
they show it; a site that renders the field does the same with
resolveStorageReferences(text, client) — see
Files inside text.
Text saved before references holds the download URLs themselves, with expired
tokens. The editor, the previews and resolveStorageReferences renew them when
they show the text, because a token still names its object; re-inserting the image
or rewriting the text with storageReferencesForDownloadUrls(text) stores a
reference.
Where the file is allowed to go
Section titled “Where the file is allowed to go”storagePath is a default, not a boundary: nothing on the server confines an
upload to that prefix. Deciding who may write where is what a storage
authorization rule is for — see Storage.
Files are never deleted for you
Section titled “Files are never deleted for you”Clearing a file field, replacing its file, or deleting the row it belongs to
leaves the stored object where it is. Every upload gets a key of its own, so a
replacement writes a new object rather than overwriting the old one, and
nothing removes the previous one: two rows can hold the same key, so the
panel cannot know a file is unused. Objects no row points at stay in the
bucket, and are billed, until you delete them — from Studio’s storage browser,
with client.storage.deleteObject(key), or with a lifecycle rule on the
bucket. Since 0.25 rebase storage orphans lists the files no row names, and deletes them
with --delete — see the CLI.
Moving a property to another storage source
Section titled “Moving a property to another storage source”A file field stores the object’s key, not its source. Changing a property’s
storageSource points every existing value at the new source, where those
keys do not exist, so previews of files uploaded before the change answer
“not found”. Copy the objects to the new source under the same keys before
changing the property — rebase storage move --from <old> --to <new> --prefix <storagePath>, or the provider’s own tooling, such as aws s3 sync or
gcloud storage cp — or keep the old source for the existing files.
Next Steps
Section titled “Next Steps”- Properties — every property type and its options
- Storage — buckets, signed URLs, and what the server does with an upload
- Security Rules (RLS) — who may read the rows these files hang off