Storage
ActiveStorage-like file storage for Phoenix. All things file uploads for your Phoenix app following the design principles applied in Rails ActiveStorage but adapted to the Phoenix framework.
Features
- Multiple storage backends: Local filesystem, S3, and more
- Direct uploads: Signed URLs for client-side uploads
- Image processing: Built-in support for variants and transformations
- Ecto integration: Seamless attachment associations with your schemas
- Phoenix LiveView support: Helper functions for file uploads
- Configurable: Easy configuration for different environments
Installation
Add phoenix_contrib_storage to your list of dependencies in mix.exs:
def deps do
[
{:phoenix_contrib_storage, "~> 0.1.0"}
]
endConfiguration
Add to your config/config.exs:
config :phoenix_contrib_storage,
repo: MyApp.Repo,
default_service: :local,
services: %{
local: {Storage.Services.Local, root: "priv/storage"},
s3: {Storage.Services.S3, bucket: "my-bucket", region: "us-east-1"}
}Database Setup
Run the migrations to create the necessary tables:
mix ecto.gen.migration create_storage_tables
Copy the contents from priv/repo/migrations/ or use the provided migration files.
Usage
Basic File Upload
# Upload a file
{:ok, blob} = Storage.put_file("/path/to/file.jpg", filename: "avatar.jpg")
# Get file URL
url = Storage.Blob.url(blob)Schema Integration
defmodule MyApp.User do
use Ecto.Schema
use Storage.Attachment
schema "users" do
field :name, :string
# Single file attachment
has_one_attached :avatar
# Multiple file attachments
has_many_attached :documents
timestamps()
end
end
# Usage
user = MyApp.Repo.get!(User, 1)
# Attach files
Storage.Attachment.attach_one(user, :avatar, blob)
Storage.Attachment.attach_many(user, :documents, [blob1, blob2])
# Check if attached
user.avatar_attached?(user) # true/false
# Get attachments
avatar = user.avatar(user)
documents = user.documents(user)Phoenix LiveView Integration
defmodule MyAppWeb.UserLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
socket =
socket
|> allow_upload(:avatar, Storage.LiveView.upload_options(
accept: ~w(.jpg .jpeg .png),
max_entries: 1,
max_file_size: 5_000_000
))
{:ok, socket}
end
def handle_event("save", %{"user" => user_params}, socket) do
uploaded_files =
consume_uploaded_entries(socket, :avatar, fn %{path: path}, entry ->
{:ok, Storage.LiveView.consume_uploaded_entry(path, entry)}
end)
# Use uploaded_files with your changeset...
{:noreply, socket}
end
endFile Serving
Add to your router:
# Basic file serving
get "/storage/:key", Storage.Controller, :serve
# With filename for SEO-friendly URLs
get "/storage/:key/:filename", Storage.Controller, :serve_with_filename
# Force download
get "/storage/:key/download", Storage.Controller, :downloadDirect Uploads (S3)
# Generate signed URL for direct upload
{:ok, upload_url} = Storage.signed_url_for_direct_upload(
filename: "document.pdf",
content_type: "application/pdf"
)
# Use in your frontend for direct uploadsStorage Services
Local Filesystem
config :phoenix_contrib_storage,
services: %{
local: {Storage.Services.Local, root: "priv/storage"}
}Amazon S3
config :phoenix_contrib_storage,
services: %{
s3: {Storage.Services.S3,
bucket: "my-bucket",
region: "us-east-1",
access_key_id: "...",
secret_access_key: "..."
}
}API Reference
Storage
Storage.put_file/2- Upload a file and create a blobStorage.get_file/1- Retrieve file dataStorage.delete_file/1- Delete a fileStorage.signed_url_for_direct_upload/1- Generate signed upload URL
Storage.Blob
Storage.Blob.url/2- Generate URL for a blobStorage.Blob.image?/1- Check if blob is an imageStorage.Blob.human_size/1- Get human-readable file size
Storage.Attachment
has_one_attached/1- Define single file attachmenthas_many_attached/1- Define multiple file attachmentsattach_one/3- Attach single file to recordattach_many/3- Attach multiple files to record
Contributing
- Fork it
-
Create your feature branch (
git checkout -b my-new-feature) -
Commit your changes (
git commit -am 'Add some feature') -
Push to the branch (
git push origin my-new-feature) - Create new Pull Request
Development Setup
- Clone the repository
-
Install dependencies:
mix deps.get -
Run tests:
mix test -
Check code quality:
mix credo --strict -
Check types:
mix dialyzer -
Format code:
mix format
Code Quality
This project maintains high code quality standards:
- Tests: Comprehensive test suite with >90% coverage
- Linting: Code linting with Credo in strict mode
- Type Checking: Static analysis with Dialyzer
- Formatting: Consistent code formatting with
mix format - Documentation: All public functions are documented
- CI/CD: Automated testing, linting, and type checking on all PRs
License
MIT License. See LICENSE for details.