Provides upload helpers for Phoenix LiveViews.
use PhxMediaLibrary.LiveUpload injects convenience functions into your
LiveView for managing media uploads backed by PhxMediaLibrary.
Usage
defmodule MyAppWeb.PostLive.Edit do
use MyAppWeb, :live_view
use PhxMediaLibrary.LiveUpload
def mount(%{"id" => id}, _session, socket) do
post = Posts.get_post!(id)
{:ok,
socket
|> assign(:post, post)
|> allow_media_upload(:images, model: post, collection: :images)
|> stream_existing_media(:media, post, :images)}
end
def handle_event("validate", _params, socket) do
{:noreply, socket}
end
def handle_event("save_media", _params, socket) do
case consume_media(socket, :images, socket.assigns.post, :images, notify: self()) do
{:ok, media_items} ->
{:noreply,
socket
|> stream_media_items(:media, media_items)
|> put_flash(:info, "Uploaded #{length(media_items)} file(s)")}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Upload failed: #{inspect(reason)}")}
end
end
def handle_event("delete_media", %{"id" => id}, socket) do
case delete_media_by_id(id, notify: self()) do
:ok -> {:noreply, stream_delete_by_dom_id(socket, :media, "media-#{id}")}
{:error, reason} -> {:noreply, put_flash(socket, :error, inspect(reason))}
end
end
def handle_event("cancel_upload", %{"ref" => ref}, socket) do
{:noreply, cancel_upload(socket, :images, ref)}
end
# Handle media lifecycle notifications
def handle_info({:media_added, media_items}, socket) do
# React to newly added media (e.g. update counters, trigger side-effects)
{:noreply, assign(socket, :media_count, length(media_items))}
end
def handle_info({:media_removed, _media}, socket) do
{:noreply, socket}
end
def handle_info({:media_error, _reason}, socket) do
{:noreply, socket}
end
endHow It Works
The module provides three categories of helpers:
1. Setup Helpers
allow_media_upload/3— wrapsPhoenix.LiveView.allow_upload/3with collection-aware defaults. Automatically derives:accept,:max_entries, and:max_file_sizefrom the collection configuration.stream_existing_media/4— loads existing media for a model/collection and streams them into a LiveView stream for display.
2. Consumption Helpers
consume_media/4— wrapsPhoenix.LiveView.consume_uploaded_entries/3and callsPhxMediaLibrary.add/2 |> PhxMediaLibrary.to_collection/2for each completed upload entry.
3. Management Helpers
stream_media_items/3— inserts newly created media items into an existing stream so the UI updates without a full reload.delete_media_by_id/1— fetches a media record by ID and deletes it along with its files.media_upload_errors/2— returns human-readable error strings for an upload's errors.media_entry_errors/2— returns human-readable error strings for a specific upload entry.
Event Notifications
Both consume_media/5 and delete_media_by_id/2 accept a :notify
option. When set to a pid (e.g. self()), lifecycle messages are sent
to that process:
{:media_added, [Media.t()]}— after successful upload consumption{:media_error, reason}— when upload consumption fails{:media_removed, Media.t()}— after successful media deletion
This allows parent LiveViews (or any process) to react to media
lifecycle events via handle_info/2.
Summary
Functions
Allows a media upload on the socket with collection-aware defaults.
Consumes completed uploads and persists them as media via PhxMediaLibrary.
Deletes a media item by its ID and removes its files from storage.
Returns whether the upload has any entries (pending or completed).
Returns whether an upload entry is an image based on its client MIME type.
Returns human-readable error strings for a specific upload entry.
Returns human-readable error strings for an upload.
Loads existing media for a model/collection and streams them.
Inserts newly created media items into an existing stream.
Translates an upload error atom into a human-readable string.
Functions
@spec allow_media_upload(Phoenix.LiveView.Socket.t(), atom(), keyword()) :: Phoenix.LiveView.Socket.t()
Allows a media upload on the socket with collection-aware defaults.
Wraps Phoenix.LiveView.allow_upload/3, automatically deriving the
:accept, :max_entries, and :max_file_size options from the
collection configuration defined on the model's schema.
Options
All options are passed through to allow_upload/3. The following are
specific to allow_media_upload/3:
:model— (required) the Ecto struct that owns the media. Used to look up collection configuration.:collection— (required) the collection atom to upload into.:accept— override the accept list. When not provided, it is derived from the collection's:acceptsMIME types. Falls back to:anywhen the collection has no type restrictions.:max_entries— override maximum entries. Derived from the collection's:max_filesor:single_filesetting.:max_file_size— maximum file size in bytes. Defaults to 10 MB.
Any other option (e.g. :auto_upload, :chunk_size, :progress) is
forwarded to allow_upload/3 unchanged.
Examples
# Derive everything from collection config
allow_media_upload(socket, :images, model: post, collection: :images)
# Override accept and max entries
allow_media_upload(socket, :avatar,
model: user,
collection: :avatar,
accept: ~w(.jpg .png),
max_entries: 1
)
@spec consume_media( Phoenix.LiveView.Socket.t(), atom(), Ecto.Schema.t(), atom(), keyword() ) :: {:ok, [PhxMediaLibrary.Media.t()]} | {:error, term()}
Consumes completed uploads and persists them as media via PhxMediaLibrary.
Wraps Phoenix.LiveView.consume_uploaded_entries/3. For each completed
entry, it calls PhxMediaLibrary.add/2 |> PhxMediaLibrary.to_collection/2
to store the file and create the database record.
Returns {:ok, [Media.t()]} with all successfully created media items,
or {:error, reason} if any entry fails (already-consumed entries are
still persisted — the error is for the first failure encountered).
Options
:disk— override the storage disk for these uploads.:custom_properties— a map of custom properties to attach to each media item.:responsive— whether to generate responsive images. Defaults tofalse.:notify— a pid to send lifecycle messages to. When set, sends{:media_added, media_items}on success or{:media_error, reason}on failure.
Examples
{:ok, media_items} = consume_media(socket, :images, post, :images)
{:ok, media_items} =
consume_media(socket, :images, post, :images,
custom_properties: %{"uploaded_by" => user.id},
responsive: true,
notify: self()
)
Deletes a media item by its ID and removes its files from storage.
Returns :ok on success, {:error, :not_found} if the media doesn't
exist, or {:error, reason} on failure.
Options
:notify— a pid to send{:media_removed, media}to on success.
Examples
case delete_media_by_id(media_id) do
:ok -> {:noreply, stream_delete_by_dom_id(socket, :media, "media-#{media_id}")}
{:error, reason} -> {:noreply, put_flash(socket, :error, inspect(reason))}
end
# With notification
case delete_media_by_id(media_id, notify: self()) do
:ok -> {:noreply, stream_delete_by_dom_id(socket, :media, "media-#{media_id}")}
{:error, reason} -> {:noreply, put_flash(socket, :error, inspect(reason))}
end
@spec has_upload_entries?(Phoenix.LiveView.UploadConfig.t()) :: boolean()
Returns whether the upload has any entries (pending or completed).
Useful for conditionally showing/hiding UI elements.
Examples
<div :if={has_upload_entries?(@uploads.images)}>
...upload preview area...
</div>
@spec image_entry?(Phoenix.LiveView.UploadEntry.t()) :: boolean()
Returns whether an upload entry is an image based on its client MIME type.
Useful for conditionally showing image previews vs. file icons.
Examples
<%= if image_entry?(entry) do %>
<.live_img_preview entry={entry} />
<% else %>
<.icon name="hero-document" />
<% end %>
@spec media_entry_errors( Phoenix.LiveView.UploadConfig.t(), Phoenix.LiveView.UploadEntry.t() ) :: [ String.t() ]
Returns human-readable error strings for a specific upload entry.
Translates the error atoms returned by Phoenix.Component.upload_errors/2
into user-friendly messages.
Examples
for entry <- @uploads.images.entries do
for msg <- media_entry_errors(@uploads.images, entry) do
...
end
end
@spec media_upload_errors(Phoenix.LiveView.UploadConfig.t()) :: [String.t()]
Returns human-readable error strings for an upload.
Translates the error atoms returned by Phoenix.Component.upload_errors/1
into user-friendly messages.
Examples
for msg <- media_upload_errors(@uploads.images) do
...
end
@spec stream_existing_media( Phoenix.LiveView.Socket.t(), atom(), Ecto.Schema.t(), atom() ) :: Phoenix.LiveView.Socket.t()
Loads existing media for a model/collection and streams them.
This is a convenience that queries existing media and feeds them into a LiveView stream so they can be rendered alongside new uploads.
The stream is configured with a :dom_id function that prefixes each
item's ID with "media-".
Examples
socket
|> stream_existing_media(:media, post, :images)
# Renders in template as @streams.media
@spec stream_media_items(Phoenix.LiveView.Socket.t(), atom(), [ PhxMediaLibrary.Media.t() ]) :: Phoenix.LiveView.Socket.t()
Inserts newly created media items into an existing stream.
Use this after consume_media/5 to update the UI without reloading.
Examples
{:ok, media_items} = consume_media(socket, :images, post, :images)
socket = stream_media_items(socket, :media, media_items)
Translates an upload error atom into a human-readable string.
Override this function in your own module if you need custom messages or i18n support.
Examples
translate_upload_error(:too_large)
#=> "File is too large"