Skip to main content

How to Configure File Storage

Framework M supports pluggable file storage backends for handling document attachments and file uploads. This guide explains how to configure each backend.


Choosing a Backend

BackendBest for
localLocal development, single-server "Indie" deployments
memoryAutomated tests
s3Production deployments — works with AWS S3 and any S3-compatible API (MinIO, DigitalOcean Spaces, Backblaze B2, Cloudflare R2, etc.)

Local Storage

Files are written to a date-organized directory tree on the local filesystem. No external services are required.

[files]
storage_backend = "local"
storage_path = "./uploads" # relative or absolute path
max_upload_size = "10MB"
allowed_extensions = ["pdf", "doc", "docx", "xls", "xlsx", "png", "jpg", "jpeg", "gif", "txt"]

The local adapter writes to <storage_path>/YYYY/MM/DD/<random-prefix>_<filename> using atomic temp-file renames to prevent corruption.

note

Local storage is not suitable for multi-instance deployments. Use s3 instead.


S3 / S3-Compatible Storage

Step 1 — Set the backend

[files]
storage_backend = "s3"
max_upload_size = "100MB"
allowed_extensions = ["pdf", "png", "jpg", "mp4"]

Step 2 — Provide S3 credentials

Credentials can be set in framework_config.toml or (preferably in production) via environment variables. Environment variables override TOML values.

Via framework_config.toml

[files]
storage_backend = "s3"
s3_bucket = "my-app-files"
s3_region = "us-east-1"
# Optional — omit for IAM role / instance profile auth
s3_access_key = "AKIA..."
s3_secret_key = "wJalrX..."
# Optional — only for S3-compatible services
s3_endpoint = "https://s3.example.com"

Via environment variables

FRAMEWORK_M_S3_BUCKET=my-app-files
FRAMEWORK_M_S3_REGION=us-east-1
FRAMEWORK_M_S3_ACCESS_KEY=AKIA...
FRAMEWORK_M_S3_SECRET_KEY=wJalrX...
FRAMEWORK_M_S3_ENDPOINT=https://s3.example.com # S3-compatible only

Common Deployment Patterns

IAM roles eliminate static credentials entirely. boto3 picks up the role automatically:

[files]
storage_backend = "s3"
s3_bucket = "my-app-files"
s3_region = "us-east-1"
# No access_key / secret_key needed

MinIO (self-hosted)

[files]
storage_backend = "s3"
s3_bucket = "my-app-files"
s3_region = "us-east-1" # arbitrary, required by boto3
s3_endpoint = "https://minio.internal:9000"
s3_access_key = "minioadmin"
s3_secret_key = "minioadmin"

Cloudflare R2

[files]
storage_backend = "s3"
s3_bucket = "my-app-files"
s3_region = "auto"
s3_endpoint = "https://<account-id>.r2.cloudflarestorage.com"
s3_access_key = "..."
s3_secret_key = "..."

Presigned Uploads (large files, S3 only)

For files that exceed memory-safe limits, Framework M supports presigned uploads where the browser sends bytes directly to S3, bypassing the server.

POST /api/v1/file/upload/presign → { upload_url, upload_id, part_urls, key }
PUT <presigned_url> (browser → S3 directly)
POST /api/v1/file/upload/presign/complete → { key, file_url }

The /presign/complete endpoint verifies the upload server-side using HeadObject before confirming success. See File Storage Architecture for the full design rationale.

note

/upload/presign returns HTTP 400 when storage_backend is not s3.


Testing

Use the in-memory backend in tests — no filesystem or S3 required:

[files]
storage_backend = "memory"

Or override via mock in pytest:

with patch("framework_m_standard.adapters.web.file_upload_routes.get_file_config") as mock:
mock.return_value = {
"storage_backend": "memory",
"max_upload_size": 10 * 1024 * 1024,
"allowed_extensions": ["txt", "pdf"],
"storage_path": "./uploads",
}
...