Storage

CIHex.pmDocumentationHex.pm Downloads

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

Installation

Add phoenix_contrib_storage to your list of dependencies in mix.exs:

def deps do
  [
    {:phoenix_contrib_storage, "~> 0.1.0"}
  ]
end

Configuration

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
end

File 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, :download

Direct 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 uploads

Storage 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.Blob

Storage.Attachment

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Development Setup

  1. Clone the repository
  2. Install dependencies: mix deps.get
  3. Run tests: mix test
  4. Check code quality: mix credo --strict
  5. Check types: mix dialyzer
  6. Format code: mix format

Code Quality

This project maintains high code quality standards:

License

MIT License. See LICENSE for details.