Form File Uploads
Forms can collect files — resumes, invoices, photos, documents — and persist them to your own storage instead of a third-party CDN. Attachment fields render a file picker (and optional drag-and-drop zone), validate type and size before upload, show progress and previews, and submit a canonical { url, name, size, mimeType } metadata object. Files land in a configured app.buckets[] destination on your S3, local disk, or database storage.
buckets:
- name: applications
default: true
backend: s3
config: { bucket: $env.S3_BUCKET, region: us-east-1 }
forms:
- id: 1
name: apply
title: Job Application
path: /apply
submitTo: { table: applications }
fields:
- { kind: standalone, name: full_name, inputType: single-line-text, required: true }
- kind: standalone
name: resume
inputType: attachment
label: Resume
required: true
accept: 'application/pdf,.doc,.docx'
maxFileSize: 10485760 # 10 MB
dropZone: true
Attachment Field Configuration
Attachment behavior is configured the same way on both field kinds: a kind: standalone field with inputType: attachment, or a kind: table-field whose underlying column is single-attachment / multiple-attachments. The upload props are shared.
| Property | Description |
|---|---|
accept |
Comma-separated MIME types or extensions restricting the file dialog (e.g. image/*, application/pdf, .doc,.docx). |
maxFileSize |
Maximum size in bytes per file. Larger files are rejected with an inline error before upload starts. |
maxFiles |
Maximum number of files (multiple-file fields only). Selecting more surfaces an inline error. |
dropZone |
Render a drag-and-drop area alongside the picker, accepting the same files. |
Single vs multiple. A single-file attachment submits one FileMetadata object (or null when empty); a multiple-file attachment submits a FileMetadata[] array (or []). For table-bound fields the multiplicity is inferred from the column type (single-attachment vs multiple-attachments); for standalone fields it follows the upload UI presented by inputType: attachment and maxFiles.
Bucket Binding
The storage destination comes from app.buckets[]. A form inherits a default bucket: the bucket whose default: true flag is set, or the only bucket when exactly one is defined. Files are uploaded there on submit, and the form payload carries canonical metadata for each file. Per-field bucket overrides are reserved for a follow-up release.
buckets:
- name: documents
default: true
backend: local
config: { path: ./storage/documents }
forms:
- id: 2
name: invoice-upload
title: Upload Invoice
submitTo: { automation: process-invoice }
fields:
- { kind: standalone, name: vendor, inputType: single-line-text, required: true }
- kind: standalone
name: document
inputType: attachment
accept: 'application/pdf,image/*'
maxFileSize: 20971520 # 20 MB
dropZone: true
Upload UX
The attachment field renders a complete upload experience:
| Behavior | Description |
|---|---|
| Type filter | accept restricts both the browser dialog and drag-and-drop; mismatched files surface an inline error. |
| Size validation | maxFileSize rejects oversized files before upload begins. |
| Progress indicator | A progress bar or spinner is shown while each file uploads. |
| Preview | Image files render a thumbnail after upload; non-image files render filename + size. |
| Remove | Selected files can be removed before submit (keyboard-accessible); removing one of many does not affect the others. |
| Required enforcement | A required attachment field with no file blocks submission with an inline error. |
File Metadata Shape
When the form submits, each attachment value is normalized to a canonical metadata object — the same shape consumed by submitTo.table, submitTo.automation triggers, and the form_submissions ledger data payload.
type FileMetadata = {
url: string // canonical URL into the bucket (signed when the bucket is private)
name: string // original filename
size: number // bytes
mimeType: string // detected MIME type
}
A single-file field submits FileMetadata | null; a multiple-file field submits FileMetadata[]. Routing the upload to an automation makes the same metadata available in the trigger payload — for example to run an AI extraction over {{trigger.data.document.url}}.
Related Pages
- Form Fields — the field kinds attachment uploads extend.
- Forms Overview —
submitTotargets that receive file metadata. - Submissions — file metadata stored in the ledger
datapayload. - Buckets — configuring S3, local, and database storage backends.