floo.app.toml, then deploy:
floo.app.toml
floo services add storage --name files, provisions through the API and does not write floo.app.toml. Keep the declaration in Git for auditable configuration. Pick a name other than default if your app already has a [managed.default] section; the starter template uses it for Postgres. The deploy creates private, separate buckets for dev and prod. If your app has multiple services, attach storage to each service that needs it with managed = ["storage:files"]. See Managed services.
Environment variables
A named service appends its name:
[managed.files] gets STORAGE_BUCKET_FILES, STORAGE_S3_ENDPOINT_FILES, and the other keys with _FILES appended, as in the examples below. Only a service named default gets the unsuffixed keys in the table. floo does not set AWS_* variables, so your own AWS credentials stay separate. Pass the STORAGE_S3_* values to each client explicitly.
S3 keys exist only for storage services created on or after v2026.09.27.1 (September 27, 2026). An older service has no S3 keys: keep using the Google SDK below, or provision a new named service and migrate and verify its data before removing the old one. Legacy STORAGE_URL values target an admin-only endpoint that apps cannot call; use the SDK variables instead.
Use it from code
The S3 examples need boto3/botocore 1.36 or later, or a current AWS SDK in other languages: older versions do not acceptrequest_checksum_calculation.
floo’s S3 interface uses the XML API with HMAC credentials. It supports object get, put, list, delete, multipart upload, and presigned SigV4 URLs. Configure the endpoint and credentials explicitly.
Rails Active Storage
Addgem "aws-sdk-s3", require: false and configure the S3 service:
config/storage.yml
config.active_storage.service = :amazon in the environment config. See Rails and the Active Storage S3 service.
Google SDK alternative
The Google storage SDK still uses Application Default Credentials in a deployed app. It needs onlySTORAGE_BUCKET_FILES:
Python
Uploads from a browser
A backend or agent with an ADMIN-scoped floo API key can request a signed upload policy. Keep that key on the server. Send this request to the floo API:url and fields. Give those to the browser, which submits every returned field plus the file as a multipart form:
Browser
{"method":"GET","object_path":"uploads/photo.png"} to the same endpoint and use the returned url. GET expiry is 60–3600 seconds (default 900). PUT and POST requests both return a POST policy, require an uploads/ path and content_type, expire in 60–900 seconds, and allow 1–100 MiB (max_bytes defaults to 10 MiB). The admin endpoint refuses new URLs and policies during a spend-cap hard stop or when prepaid spending authority is exhausted.
An app can also sign its own S3 GET URL with its injected keys. Using the Python client above:
Python
RAG and AI ingestion
Store source documents under a prefix such assources/, then load them into your chunking and embedding pipeline. LangChain’s S3FileLoader and S3DirectoryLoader accept endpoint and credentials directly:
Python
S3FileLoader(bucket, key, ...) with the same keyword arguments. LlamaIndex S3Reader uses bucket, prefix or key, s3_endpoint_url, aws_access_id, and aws_access_secret. Feed the loaded documents into your parser, splitter, and index; the bucket stores the source files, not the index.
Large files and multipart
Use an S3 client’s multipart upload for files larger than the signed POST policy’s 100 MiB ceiling. XML API multipart uploads upload parts and then assemble the object. Unfinished parts count toward storage until aborted or cleaned up. Multipart uploads have different checksum behavior from single PUTs; do not rely on a multipart ETag as an MD5 hash.Deleting, versions, and restore
ls, usage, and rm require CLI v2026.09.27 or later. For the API, use /v1/apps/{app_id}/managed-services/{service_id} as the base path. Object listing and usage require a READ-scoped key; delete, versions, and restore require an ADMIN-scoped key:
Use
env=prod for the production bucket. Delete returns 409 if the bucket cannot guarantee recovery. See the CLI reference for flags.
Deleted and overwritten versions remain restorable for 30 days. Restore copies a selected generation over the live path. Use versions to get its generation ID before restoring.
Previews and dev versus prod
Dev and prod have separate buckets and keys. Dev and preview keys can reach only that app’s dev or preview buckets, never prod. Prod keys can reach only prod.floo dev injects STORAGE_BUCKET and the S3 endpoint, key ID and region locally but withholds the write-only secret, and your own local Google credentials have no access to floo’s buckets. Run storage code against your deployed dev environment for now.