Stores files on Amazon S3 or any S3-compatible service (MinIO, Cloudflare R2, DigitalOcean Spaces, ...).
HTTP goes through the optional req dependency — newly generated Phoenix
apps already include it; add it otherwise:
{:req, "~> 0.5"}Request signing (SigV4) is implemented in-house — no AWS SDK or signing library required.
Configuration
config :attached,
storage_backends: [
s3_main: {Attached.StorageBackends.S3,
bucket: "my-bucket",
region: "eu-central-1",
access_key_id: System.fetch_env!("AWS_ACCESS_KEY_ID"),
secret_access_key: System.fetch_env!("AWS_SECRET_ACCESS_KEY")}
]The same module can back several named instances (e.g. a second bucket for backups) — each entry carries its own config.
Optional instance config keys:
:endpoint— base URL of an S3-compatible service, e.g."http://localhost:9000"(MinIO) or"https://<account>.r2.cloudflarestorage.com"(R2). When set, path-style addressing is used (<endpoint>/<bucket>/<key>); without it, virtual-host style against AWS (https://<bucket>.s3.<region>.amazonaws.com/<key>).:session_token— STS session token for temporary credentials.:url_expires_in— presigned URL lifetime in seconds (default300).:response_content_type— whentrue(the default),url/3looks up the original/variant content type in the database and bakes it into the presigned URL asresponse-content-type, so browsers receive the real MIME type instead of S3's stored default. Setfalseto skip the lookup (e.g. when no repo is configured).:req_options— extra options merged into everyReq.request/1(timeouts, instrumentation,plug: {Req.Test, ...}for tests).
URLs
url/3 returns a presigned S3 GET URL — files are served directly from S3,
Attached.Web.Plug is not involved. The lifetime defaults to
:url_expires_in and can be overridden per call with expires_in:.
Note: Attached.url/2,3 passes keys through Attached.Web.Signer.sign/1
before they reach this backend, so url/3 first unwraps that token back to
the raw storage key (verifying the HMAC when secret_key_base is
configured) and then presigns. The Signer's own :url_expires_in plays no
role for S3 — only the presign expiry does.
Limitations
upload/4andcompose/3buffer file contents in memory. Fine for typical attachment sizes; multipart upload for very large files is a candidate for a future version.compose/3concatenates by download + re-upload. S3's server-side compose (multipartUploadPartCopy) requires 5 MB minimum part sizes and is not used.