Skip to content

File upload

FileUpload is the field people use to attach files to a record. The operator picks a file; the browser posts it to the panel’s /orbit-upload endpoint; the endpoint looks up this field on the resource form and uses its disk, directory, size limits, and accepted types. The stored path is then written back into form state.

That split matters: the field owns the rules, the panel owns the HTTP endpoint, and an UploadStorage owns where bytes land. Swap storage without touching the field.

app/orbit/resources/post_resource.py
from almasix.orbit.forms import FileUpload, set_upload_storage, FilesystemUploadStorage
set_upload_storage(FilesystemUploadStorage()) # default is in-memory
FileUpload.make("cover")
.image()
.directory("post-covers")
.max_size(2048)

panel.uploads(False) turns the endpoint off for a panel that should not accept files.

Each variation below includes the fluent API and light/dark screenshots of the rendered control.

A generic attachment field with no accept filter. Users can pick any file the browser allows. Add .helper_text() for size or type guidance until you tighten .accepted_file_types().

app/orbit/resources/example_resource.py
FileUpload.make('attachment')
.label('Attachment')
.helper_text('PDF or Office documents preferred.')

Orbit Basic file upload (light)

Orbit Basic file upload (dark)

.image() (alias .accepted_images()) sets accept to common image MIME types and turns on image preview (data-image-preview). Use for covers, galleries, and media that should show a thumbnail grid after selection.

app/orbit/resources/example_resource.py
FileUpload.make('cover')
.image()
.label('Cover image')

Orbit Image upload (light)

Orbit Image upload (dark)

.avatar() enables circular avatar chrome (or-file-avatar), forces image preview, and defaults accept to image/* when no types were set. Ideal for profile photos.

app/orbit/resources/example_resource.py
FileUpload.make('avatar')
.avatar()
.label('Avatar')

Orbit Avatar upload (light)

Orbit Avatar upload (dark)

.accepted_file_types([...]) joins MIME or extension tokens into the HTML accept attribute. Prefer explicit lists when you need PDFs only or mixed documents without enabling image preview.

app/orbit/resources/example_resource.py
FileUpload.make('contract')
.label('Contract')
.accepted_file_types(['application/pdf', '.docx'])

Orbit Accepted file types (light)

Orbit Accepted file types (dark)

.disk(), .directory(), and .visibility() tell the upload endpoint where to put the file. The default MemoryUploadStorage keeps files in a dict (tests and demos). FilesystemUploadStorage writes through almasix.filesystem disks — pass .disk("s3") once you have configured that disk.

app/orbit/resources/example_resource.py
FileUpload.make('logo')
.image()
.label('Logo')
.disk('s3')
.directory('brands')
.visibility('public')

Orbit Disk, directory, and visibility (light)

Orbit Disk, directory, and visibility (dark)

When the record already has files, the field renders a preview grid of those paths. Images become thumbnails; everything else is a name chip. .openable() and .downloadable() add actions on each card; the × button posts intent=delete back to the same endpoint.

app/orbit/resources/example_resource.py
FileUpload.make('assets')
.image()
.label('Assets')
.multiple()
.openable()
.downloadable()

Orbit Stored files (light)

Orbit Stored files (dark)

.max_size() / .min_size() take kilobytes and emit data-max-size / data-min-size for client and host validation. Field-level .max_size() is the same helper used across uploads.

app/orbit/resources/example_resource.py
FileUpload.make('resume')
.label('Resume')
.accepted_file_types(['application/pdf'])
.min_size(10)
.max_size(2048)

Orbit Size limits (light)

Orbit Size limits (dark)

.image_size(min_width=…, max_width=…, min_height=…, max_height=…) stores pixel bounds as data-image-* attributes for host-side checks after upload.

app/orbit/resources/example_resource.py
FileUpload.make('banner')
.image()
.label('Banner')
.image_size(min_width=1200, max_width=2400, min_height=400, max_height=800)

Orbit Image dimensions (light)

Orbit Image dimensions (dark)

.multiple() or .max_files(n) with n > 1 adds the multiple attribute. .min_files() / .max_files() also emit data attributes for count validation in the host.

app/orbit/resources/example_resource.py
FileUpload.make('gallery')
.image()
.label('Gallery')
.multiple()
.min_files(1)
.max_files(8)

Orbit Multiple files (light)

Orbit Multiple files (dark)

Toggle preview grid and action affordances: .image_preview(), .previewable(), .downloadable(), .openable(), and .reorderable(). Preview markup is a live region (or-file-preview); actions are data flags for Alpine/host handlers.

app/orbit/resources/example_resource.py
FileUpload.make('assets')
.image()
.label('Assets')
.multiple()
.image_preview()
.downloadable()
.openable()
.reorderable()

Orbit Preview and file actions (light)

Orbit Preview and file actions (dark)

.panel_layout() switches to panel-oriented chrome via data-panel-layout. .image_preview_height(px) sets data-preview-height for thumbnail sizing.

app/orbit/resources/example_resource.py
FileUpload.make('photos')
.image()
.label('Photos')
.panel_layout()
.image_preview_height(120)

Orbit Panel layout and preview height (light)

Orbit Panel layout and preview height (dark)

Fluent flags for how the host should treat files after pick: .store_files(False) skips persistence, .move_files() moves instead of copy, .preserve_filenames() keeps original names, .fetch_file_information(False) skips metadata fetch, and .prevent_file_path_tampering() marks the field for path hardening.

app/orbit/resources/example_resource.py
FileUpload.make('import')
.label('Import file')
.store_files(False)
.preserve_filenames()
.prevent_file_path_tampering()

Orbit Storage behavior flags (light)

Orbit Storage behavior flags (dark)

.image_editor() and .image_editor_aspect_ratios([...]) reserve a crop region next to the preview (or-file-image-editor). Aspect-ratio buttons are data on the field so a host editor can read them; the forms package itself does not crop pixels.

app/orbit/resources/example_resource.py
FileUpload.make('hero')
.image()
.label('Hero')
.image_editor_aspect_ratios(['1:1', '16:9'])

Orbit Image editor (light)

Orbit Image editor (dark)

Closures work on .label(), .helper_text(), .placeholder(), .visible(), .disabled(), and .required() where applicable — see Form closures.