Skip to main content
The struct representing an upload configuration.

Overview

The Phoenix.LiveView.UploadConfig module defines the configuration structure for file uploads in LiveView. It is created by allow_upload/3 and manages upload entries, validation, and processing.

Type Definition

@type t :: %Phoenix.LiveView.UploadConfig{
  name: atom() | String.t(),
  cid: :unregistered | nil | integer(),
  client_key: String.t(),
  max_entries: pos_integer(),
  max_file_size: pos_integer(),
  entries: list(),
  entry_refs_to_pids: %{String.t() => pid() | :unregistered | :done},
  entry_refs_to_metas: %{String.t() => map()},
  accept: list() | :any,
  acceptable_types: MapSet.t(),
  acceptable_exts: MapSet.t(),
  external: function() | false,
  allowed?: boolean(),
  errors: list(),
  ref: String.t(),
  auto_upload?: boolean(),
  writer: function(),
  progress_event: function() | nil
}

Struct Fields

name
atom() | String.t()
The upload name provided to allow_upload/3
ref
String.t()
Unique reference for this upload configuration
entries
list(UploadEntry.t())
List of upload entries (files)
max_entries
pos_integer()
Maximum number of files allowed. Default: 1
max_file_size
pos_integer()
Maximum file size in bytes. Default: 8,000,000 (8MB)
chunk_size
pos_integer()
Size of chunks for upload streaming. Default: 64,000 bytes
chunk_timeout
pos_integer()
Timeout for chunk uploads in milliseconds. Default: 10,000ms
accept
list() | :any
List of accepted file types or :any. Examples: [".jpg", ".png", "image/*"]
acceptable_types
MapSet.t()
Set of acceptable MIME types
acceptable_exts
MapSet.t()
Set of acceptable file extensions
external
function() | false
External upload function for third-party storage, or false for local uploads
allowed?
boolean()
Whether the upload is currently allowed
errors
list()
List of validation errors as {ref, error} tuples
auto_upload?
boolean()
Whether files should be uploaded automatically. Default: false
progress_event
function() | nil
Optional callback for upload progress updates
writer
function()
Function that returns the writer module and options for handling uploads

Error Types

Upload validation can produce the following errors:
:too_large
atom
File exceeds max_file_size
:too_many_files
atom
Number of files exceeds max_entries
:not_accepted
atom
File type not in the accept list
:external_client_failure
atom
Error during external upload

Default Values

@default_max_entries 1
@default_max_file_size 8_000_000  # 8 MB
@default_chunk_size 64_000        # 64 KB
@default_chunk_timeout 10_000     # 10 seconds

Usage

The upload config is created with allow_upload/3:
def mount(_params, _session, socket) do
  socket = 
    socket
    |> allow_upload(:avatar,
      accept: ~w(.jpg .jpeg .png),
      max_entries: 1,
      max_file_size: 5_000_000
    )
  
  {:ok, socket}
end
Access the config in your assigns:
def render(assigns) do
  ~H"""
  <%= for entry <- @uploads.avatar.entries do %>
    <div>
      <%= entry.client_name %> - <%= entry.progress %>%
    </div>
  <% end %>
  
  <%= for err <- @uploads.avatar.errors do %>
    <div class="error"><%= error_to_string(err) %></div>
  <% end %>
  """
end

Accept Formats

The :accept option supports:

File Extensions

Must start with a period and have a known MIME type:
accept: ~w(.jpg .jpeg .png .gif)

MIME Types

accept: ["image/jpeg", "image/png"]

MIME Type Wildcards

accept: ["image/*", "video/*", "audio/*"]

Any File Type

accept: :any

External Uploads

For uploads to external storage (S3, GCS, etc.):
def mount(_params, _session, socket) do
  socket =
    socket
    |> allow_upload(:avatar,
      accept: ~w(.jpg .jpeg .png),
      max_entries: 1,
      external: &presign_upload/2
    )
  
  {:ok, socket}
end

defp presign_upload(entry, socket) do
  {:ok, meta, socket} = 
    Phoenix.LiveView.Upload.external_upload_meta(entry, socket)
  
  # Generate presigned URL for your storage service
  presigned_url = MyApp.Storage.generate_presigned_url(entry, socket)
  
  {:ok, %{uploader: "S3", url: presigned_url}, socket}
end

Custom Writer

Provide a custom writer for advanced upload handling:
def mount(_params, _session, socket) do
  socket =
    socket
    |> allow_upload(:avatar,
      accept: :any,
      writer: fn _name, _entry, _socket ->
        {MyApp.CustomUploadWriter, [bucket: "uploads"]}
      end
    )
  
  {:ok, socket}
end

Progress Callback

Track upload progress with a custom callback:
def mount(_params, _session, socket) do
  socket =
    socket
    |> allow_upload(:documents,
      accept: ["application/pdf"],
      progress: &handle_progress/3
    )
  
  {:ok, socket}
end

defp handle_progress(:documents, entry, socket) do
  if entry.done? do
    # Upload complete
    {:noreply, put_flash(socket, :info, "Upload complete!")}
  else
    # Still uploading
    {:noreply, socket}
  end
end

Auto Upload

Enable automatic upload on file selection:
def mount(_params, _session, socket) do
  socket =
    socket
    |> allow_upload(:avatar,
      accept: ~w(.jpg .jpeg .png),
      auto_upload: true
    )
  
  {:ok, socket}
end

Validation Example

def error_to_string(:too_large), do: "File is too large"
def error_to_string(:too_many_files), do: "You have selected too many files"
def error_to_string(:not_accepted), do: "File type not accepted"

def render(assigns) do
  ~H"""
  <form phx-change="validate" phx-submit="save">
    <.live_file_input upload={@uploads.avatar} />
    
    <%= for entry <- @uploads.avatar.entries do %>
      <div>
        <progress value={entry.progress} max="100"><%= entry.progress %>%</progress>
        <button type="button" phx-click="cancel-upload" phx-value-ref={entry.ref}>×</button>
      </div>
    <% end %>
    
    <%= for {_ref, error} <- @uploads.avatar.errors do %>
      <p class="error"><%= error_to_string(error) %></p>
    <% end %>
    
    <button type="submit">Upload</button>
  </form>
  """
end

def handle_event("validate", _params, socket) do
  {:noreply, socket}
end

def handle_event("cancel-upload", %{"ref" => ref}, socket) do
  {:noreply, cancel_upload(socket, :avatar, ref)}
end

def handle_event("save", _params, socket) do
  uploaded_files =
    consume_uploaded_entries(socket, :avatar, fn %{path: path}, entry ->
      dest = Path.join("uploads", entry.client_name)
      File.cp!(path, dest)
      {:ok, ~p"/uploads/#{entry.client_name}"}
    end)
  
  {:noreply, socket |> put_flash(:info, "Files uploaded!")}
end

Build docs developers (and LLMs) love