-
Notifications
You must be signed in to change notification settings - Fork 6
file fields
A file field holds one stored file. The entry keeps the file's id, and a read answers the file
it names: where to fetch it, its name, type and size, and the alt text and caption an editor wrote
on the file. Alt text lives on the file, so it is written once and every entry naming the file
reads it.
Files are stored by the Files module (BarakoCMS.Files), through IFileStore. The field type is
in the core and needs no reference to that module.
{ "name": "Cover", "displayName": "Cover", "type": "file", "editor": "image" }A file field takes no referenceType, options, currency or text rule (minLength,
maxLength, pattern). It may take editor: "image", which tells a console to open an image
picker rather than a picker for any file. It may take requiredWhen, section and sensitivity
like any other field.
The id as text, written with hyphens, 36 characters:
{ "Title": "Harbour", "Cover": "6f9619ff-8b86-d011-b42d-00cf4fc964ff" }Nothing else about the file is copied into the entry. An id written any other way (no hyphens, braces, a download URL) is refused, because the Files module finds the entries that use a file by looking for the id as written there.
Send null, or leave the field out, for no file.
A write may name a file the caller could download:
- any public file of the tenant;
- a private file the caller uploaded, or any private file when the caller holds Admin or SuperAdmin.
Everything else is refused with one message, Field 'Cover' takes the id of a stored file you may use, and this value is not one., whatever the reason: text that is not an id, an id no file has, a
file that was deleted, a file of another tenant, a cached resize, and a private file of somebody
else. The value is not repeated in the message, so a write cannot be used to learn which ids name
a file. An API key reads public files only, so it may attach a public file only.
The value the entry already holds in the field is not checked again. A colleague who may edit the entry but not download its file can still save the entry, and an entry whose file was deleted can still be edited. Changing the field to another file is a new attachment and is checked.
A write that sends the field under two keys differing only in case, such as Cover and cover,
is refused: only one of them would be checked, and a read answers both.
| Writer | Caller asked about | What it may attach |
|---|---|---|
POST /api/contents, PUT /api/contents/{id}, a batch, a rollback, a collection push, a spreadsheet import, a bundle import |
the signed-in user of the request | a public file, or a private one that user may download |
A transition carrying the field (IContentTransitioner) |
the actor the move is made for, not the user whose request is running | for a user actor, a public file or one that user uploaded; for a system actor, a public file only |
A module calling IContentValidatorService itself |
the caller it passes; none passed means no user |
with no caller, a public file only |
A host that replaces IContentValidatorService with its own validator does no file check unless
it implements the overloads taking a caller, which by default call the older members. A decorator
that wraps the built-in validator and forwards only the older members checks every write as no
caller, so its writes take public files only.
| A form submission (BarakoCMS.Forms) | nobody: a form does not offer a file field | nothing |
| The UpdateField workflow action | nobody: a workflow runs for no user | a public file only; anything else fails the action for good |
| A collection sync | nobody | nothing: a sync that maps a source value onto a file field is refused when it is saved |
A bundle carries entries and not files. Importing one where the files it names are missing (into another deployment, or a tenant rebuilt from nothing) refuses every entry naming a missing file, and an import commits all of its entries or none, so the whole import is refused. Restore the files first, with their ids, then import the bundle.
GET /api/public/{type}, GET /api/public/{type}/{slug} (with or without a preview token),
GET /api/public/{type}/search, an entry resolved through ?include=, and the entry an entry or
page share link opens replace the id with the file, for a public file:
"Cover": {
"id": "6f9619ff-8b86-d011-b42d-00cf4fc964ff",
"url": "/api/public/files/6f9619ff-8b86-d011-b42d-00cf4fc964ff",
"fileName": "harbour.png",
"contentType": "image/png",
"size": 48213,
"alt": "Boats at dawn",
"caption": null
}url is an absolute URL when an object store serves the file itself, and otherwise a path to join
to the address the API is served from. alt and caption are null until an editor writes them
(PATCH /api/files/{id}). These are the same members GET /api/public/files/{id}/meta answers
anonymously, so nothing here was private before.
Every other file is left out of the entry: the field is absent, not null and not the id. That
covers a private file, a file that was deleted, an id that is not a file, text stored before the
field was a file field, and every file on a host with no module that stores files. The entry
itself is still delivered. This is the rule the Files module's anonymous routes apply, and the
rule a workflow's email attachments follow: work done for no signed-in caller reads public files
and nothing else.
A file field the type does not mark Public is not delivered at all, as for any field. When the
SEO field SocialImage is a file field, the SEO block's imageUrl is that file's url, or null.
The delivery OpenAPI document describes a file field as this object and never lists it as
required, since it can be absent from any entry.
The Pages module's GET /api/public/pages/resolve answers a page's file fields the same way,
through IPublicContentProjector.ProjectAsync. A module serving entries of its own does the same.
Three payloads leave every file field out, public files included, because they do not read the
file store: the server-sent event stream (/api/public/events, built inside the commit),
webhook payloads (a workflow has no caller to read as), and IPublicContentProjector.Project, the
projector's synchronous member. A SocialImage file field gives them no SEO image.
The id itself is not secret: it names nothing an anonymous reader can fetch, since the public
download and metadata routes refuse a private file. It is still in the entry's search text when the
field is Public, so search?q= or filter[Cover][eq]= with a known id answers which published
entries hold it, without answering the field.
GET /api/contents/{id}, GET /api/contents/by-slug/{type}/{slug} and GET /api/contents keep
the id in data, because data is what a client sends back on its next save. Beside it, a
files member holds the file each field names, keyed as the field is in data, for the files the
caller may download (the rule above for writes):
"data": { "Title": "Harbour", "Cover": "6f9619ff-8b86-d011-b42d-00cf4fc964ff" },
"files": {
"Cover": { "id": "6f9619ff-8b86-d011-b42d-00cf4fc964ff", "url": null, "fileName": "harbour.png", "contentType": "image/png", "size": 48213, "alt": "Boats at dawn", "caption": null }
}url is null for a private file: fetch its bytes from GET /api/files/{id} with the caller's
token. files is left out when no field resolves. The sensitivity scrub runs first, so a field
masked from the caller names no file in files either.
A response reads the file store once for every 500 distinct file ids it names, whatever the number
of entries and fields. A page is at most 100 entries, so a page with five file fields on every
entry is one read. Past 500 ids nothing is dropped: the rest are read in further batches of 500.
An ?include= adds one more read for the included entries' files. An authoring read adds one query
for the content types on the page.
An entry naming a file in a file field uses it. DELETE /api/files/{id} answers 409 with the
entries using it, and GET /api/files/{id}/usage lists them, as for a file named in any other
field. ?force=true deletes anyway. The entries keep the id: delivery leaves the field out, the
authoring read keeps the id in data with no files entry for it, and the entry can still be
edited.
A host with no module that stores files still starts, and a type may still declare a file field.
An entry write naming a new file is refused with Field 'Cover' holds a stored file, and no module that stores files is enabled. Enable BarakoCMS.Files and restart. An entry written while the
module was enabled can still be edited, and delivery leaves its file fields out.
A string or url field is not converted. One that holds a /api/public/files/{id} URL keeps it,
the image editor hint on it keeps working, and the Files module still counts it as a use of the
file. A field whose type is changed to file by a bundle import keeps what its entries hold; such
text does not resolve, and a save that sends it back unchanged is accepted.
Image width and height are not part of the answer: the Files module does not record them yet.
Generated from docs/file-fields.md by scripts/wiki-sync.sh. Edit the doc in the repository, not this page.
Releases
Start here
- Approval by configuration
- Configuring email
- Delivering a client project on barakoCMS
- Deploying barakoCMS on a VM
- Deploying barakoCMS on a managed platform
- Upgrading from 3.x to 4.0
- Your first module
Content
- Content type blueprints
- Choice fields
- Pushing entries to a collection
- Collections filled from outside
- Public delivery API
- Event-sourced content types
- Field hints, sections and roles
- File fields
- Image variants
- Money fields
- Scheduling publish, unpublish and sensitivity
- SEO fields
- Site settings
- Token fields
- Uniqueness rules
- URL redirects
Security and access
- Security and compliance posture
- Scanning uploads for malware
- Where the admin keeps your session, and why
Tenancy
Operations
- Background jobs
- Backup and restore
- Connectors
- Inbound idempotency
- Migrations
- Reporting which modules an instance runs
- Webhooks
- Workflow runs and how long they are kept
Other