PhxMediaLibrary uses tagged tuples for all operations that can fail, and provides structured exception types with rich metadata for programmatic error handling.
Tagged Tuples
All fallible functions return {:ok, result} or {:error, reason}:
case PhxMediaLibrary.to_collection(adder, :images) do
{:ok, media} ->
# Success — media was stored and persisted
{:error, :invalid_mime_type} ->
# File type not accepted by collection's :accepts list
{:error, {:file_too_large, actual_size, max_size}} ->
# File exceeds collection's :max_size limit
{:error, :content_type_mismatch} ->
# File content doesn't match declared MIME type (magic bytes check)
{:error, :file_not_found} ->
# Source file doesn't exist on disk
{:error, changeset} ->
# Ecto validation error — inspect changeset.errors for details
endBang Versions
Functions that return tagged tuples have ! bang counterparts that raise on
error:
# Returns {:ok, media} or {:error, reason}
{:ok, media} = PhxMediaLibrary.to_collection(adder, :images)
# Raises PhxMediaLibrary.Error on failure
media = PhxMediaLibrary.to_collection!(adder, :images)Common Error Reasons
| Error | When it occurs |
|---|---|
:invalid_mime_type | File MIME type not in collection's :accepts list |
{:file_too_large, actual, max} | File size exceeds collection's :max_size |
:content_type_mismatch | Magic-bytes detection doesn't match declared MIME type |
:file_not_found | Source file path doesn't exist |
:not_found | Media record not found in database |
%Ecto.Changeset{} | Database validation failure |
Custom Exception Types
PhxMediaLibrary provides three structured exception types, each with fields designed for programmatic handling, logging, and user-facing messages.
PhxMediaLibrary.Error
The base exception for general errors:
%PhxMediaLibrary.Error{
message: "something went wrong",
reason: :invalid_source,
metadata: %{}
}| Field | Type | Description |
|---|---|---|
:message | String.t() | Human-readable error description |
:reason | atom() | Machine-readable error identifier |
:metadata | map() | Additional context |
PhxMediaLibrary.StorageError
Raised when a storage backend operation fails:
%PhxMediaLibrary.StorageError{
message: "failed to write file",
operation: :put,
path: "images/1/photo.jpg",
adapter: PhxMediaLibrary.Storage.Local
}| Field | Type | Description |
|---|---|---|
:message | String.t() | Human-readable error description |
:operation | atom() | The storage operation that failed (:put, :get, :delete, :exists?, :url) |
:path | String.t() | The storage path involved |
:adapter | module() | The storage adapter that raised the error |
PhxMediaLibrary.ValidationError
Raised when a pre-storage validation fails. Automatically formats file sizes in human-readable units:
%PhxMediaLibrary.ValidationError{
message: "File size 15.0 MB exceeds maximum of 10.0 MB",
field: :size,
value: 15_000_000,
constraint: 10_000_000
}| Field | Type | Description |
|---|---|---|
:message | String.t() | Human-readable description (auto-formatted for sizes) |
:field | atom() | The field that failed validation (:size, :mime_type, :content_type) |
:value | term() | The actual value that was rejected |
:constraint | term() | The constraint that was violated |
Rescuing Exceptions
try do
PhxMediaLibrary.to_collection!(adder, :images)
rescue
e in PhxMediaLibrary.ValidationError ->
Logger.warning("Validation failed on #{e.field}: #{e.message}")
e in PhxMediaLibrary.StorageError ->
Logger.error("Storage #{e.operation} failed for #{e.path}: #{e.message}")
e in PhxMediaLibrary.Error ->
Logger.error("Media error: #{e.message} (#{e.reason})")
endContent-Based MIME Detection
PhxMediaLibrary uses magic-bytes inspection to verify uploaded files are what
they claim to be. This prevents attacks like renaming an executable to .jpg.
How It Works
- The detector reads the first bytes of the file content
- It matches against known magic byte signatures (50+ formats)
- If a match is found, that becomes the detected MIME type
- If no match, it falls back to extension-based detection
- If
:verify_content_typeistrue(the default), the detected type is compared to the declared type — mismatches are rejected
Supported Format Categories
- Images — JPEG, PNG, GIF, WebP, BMP, TIFF, ICO, SVG, AVIF, HEIC, and more
- Documents — PDF, Office formats (docx, xlsx, pptx), RTF
- Audio — MP3, WAV, FLAC, OGG, AAC, MIDI
- Video — MP4, WebM, AVI, MKV, MOV
- Archives — ZIP, GZIP, TAR, RAR, 7z, BZIP2, XZ, ZSTD
- Executables — ELF, Mach-O, PE/EXE (detected and rejectable)
Disabling Verification
Per-collection:
collection :raw_uploads, verify_content_type: falseGlobally:
config :phx_media_library,
verify_content_type: falseCustom Detector
Implement the PhxMediaLibrary.MimeDetector behaviour to provide your own
detection logic:
defmodule MyApp.MimeDetector do
@behaviour PhxMediaLibrary.MimeDetector
@impl true
def detect(content, filename) do
# Your custom detection logic
{:ok, "application/octet-stream"}
end
end
# config/config.exs
config :phx_media_library,
mime_detector: MyApp.MimeDetectorThe callback receives the raw binary content (at least the first few KB) and the
original filename, and must return {:ok, mime_type} or
{:error, :unrecognized}.