File storage
Shaperail provides built-in file upload, storage, and retrieval backed by the object_store crate. Files are validated on upload, stored through a pluggable backend, and tracked as metadata in your database.
Schema: declaring file fields
Use type: file on any schema field that should hold a stored file reference:
resource: avatars
version: 1
schema:
id: { type: uuid, primary: true, generated: true }
user_id: { type: uuid, ref: users.id, required: true }
avatar_url: { type: file, required: true }
created_at: { type: timestamp, generated: true }
A file field stores the storage path string in the database. The actual binary data lives in the configured storage backend.
Upload configuration on endpoints
Add an upload: block to any endpoint that accepts file uploads:
endpoints:
create:
method: POST
path: /avatars
auth: [member, admin]
input: [user_id, avatar_url]
upload:
field: avatar_url
storage: s3
max_size: 5mb
types: [jpg, png, webp]
| Key | Required | Description |
|---|---|---|
field | Yes | Schema field that stores the file reference. |
storage | Yes | Backend name: s3, gcs, azure, or local. |
max_size | Yes | Maximum upload size. Accepts kb, mb, gb, or plain bytes (e.g., 5mb, 100kb, 1gb, 1024). |
types | No | Allowed file types. Accepts extensions (jpg, png, pdf), full MIME types (image/png, application/pdf), or wildcards (image/*). Omit to allow all types. |
Upload endpoints accept multipart/form-data. Text inputs are read from text parts, and the file part must use the field named in upload.field.
When a request exceeds max_size or sends a disallowed MIME type, Shaperail returns a 422 Validation error with a structured FieldError identifying the problem.
Storage backends
Shaperail supports four backends. All implement the same interface: upload, download, delete, and signed_url.
| Backend | Value | Use case |
|---|---|---|
| Local filesystem | local | Development and testing. Default when no env var is set. |
| Amazon S3 | s3 | Production object storage on AWS. |
| Google Cloud Storage | gcs | Production object storage on GCP. |
| Azure Blob Storage | azure | Production object storage on Azure. |
Backend selection
Set the SHAPERAIL_STORAGE_BACKEND environment variable:
# Development (default if unset)
SHAPERAIL_STORAGE_BACKEND=local
# Production examples
SHAPERAIL_STORAGE_BACKEND=s3
SHAPERAIL_STORAGE_BACKEND=gcs
SHAPERAIL_STORAGE_BACKEND=azure
The backend is resolved once at startup via StorageBackend::from_env(). An unrecognized value causes an immediate startup error.
Configuration in shaperail.config.yaml
Configure backend-specific settings in the storage section of your project config:
storage:
backend: s3
local:
root_dir: ./uploads
s3:
bucket: my-app-uploads
region: us-east-1
# Credentials from AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY env vars
gcs:
bucket: my-app-uploads
# Credentials from GOOGLE_APPLICATION_CREDENTIALS env var
azure:
container: my-app-uploads
account: mystorageaccount
# Credentials from AZURE_STORAGE_KEY env var
The backend key in the config file is overridden by SHAPERAIL_STORAGE_BACKEND if set, allowing per-environment control without changing the config file.
Signed URL generation
Generate time-limited download URLs for stored files:
let url = upload_handler.signed_url("uploads/abc-photo.jpg", 3600).await?;
// Returns a pre-signed URL valid for 3600 seconds
All backends support signed URLs. The local backend returns file:// URLs (useful for development). S3, GCS, and Azure return standard pre-signed HTTPS URLs.
The expires_secs parameter controls how long the URL remains valid.
File metadata in the database
Every uploaded file produces a FileMetadata record with four values:
| Column | Type | Description |
|---|---|---|
path | String | Storage key relative to the backend root (e.g., uploads/uuid-filename.jpg). |
filename | String | Original filename as uploaded by the client. |
mime_type | String | Detected MIME type (e.g., image/png). |
size | u64 | File size in bytes. |
The type: file schema field stores the storage path. If you also want the other metadata persisted with the resource record, declare explicit companion fields using the same base name:
schema:
attachment: { type: file, required: true }
attachment_filename: { type: string }
attachment_mime_type: { type: string }
attachment_size: { type: bigint }
When those fields exist, Shaperail fills them automatically during upload. This keeps the schema explicit while still persisting the full file metadata with the resource record.
Filenames are sanitized on upload: only alphanumeric characters, dots, hyphens, and underscores are kept. Everything else is replaced with _. Directory traversal attempts like ../../../etc/passwd become .._.._.._etc_passwd.
Orphan cleanup on resource deletion
When a resource with file fields is deleted, Shaperail automatically deletes the associated files from storage. The UploadHandler::delete method removes the file at the stored path:
upload_handler.delete(&file_metadata.path).await?;
For soft-deleted resources (soft_delete: true), files are retained until the record is permanently purged. This ensures soft-deleted records can still be restored with their files intact.
Image processing
Shaperail includes built-in image processing for resize and thumbnail generation, powered by the image crate.
Thumbnails
Generate a thumbnail that fits within the given dimensions while preserving aspect ratio:
let thumb = upload_handler.create_thumbnail(
"uploads/uuid-photo.jpg", // original file path
200, // max width
200, // max height
"thumbnails", // storage prefix for output
).await?;
// thumb.path => "thumbnails/thumb-<uuid>.png"
Resizing
Resize an image to specific dimensions using Lanczos3 filtering:
let resized = upload_handler.resize_image(
"uploads/uuid-photo.jpg", // original file path
800, // target width
600, // target height
"resized", // storage prefix for output
).await?;
// resized.path => "resized/resized-<uuid>.png"
Both operations download the original from storage, process it in memory, and upload the result as a new PNG file. Each returns a FileMetadata with the new path, size, and MIME type.