agentleFS
igmarinigmarin/elixir-phoenix-skills/skills/phoenix-uploads/SKILL.md

phoenix-uploads

Skill4 starsChanged 4 months agoMITView on GitHub

MANDATORY for file upload features. Invoke before implementing upload or file serving functionality. Covers manual uploads, upload configuration, file validation, safe filenames, static paths, and template patterns. Trigger words: upload, file upload, allow_upload, consume_uploaded_entries, static_paths, file serving.

The file

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 2 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill before implementing ANY file upload functionality.

    line 18
  2. Distinctive:

    Follow these steps in order:

    line 33

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Phoenix File Uploads

Use this skill before implementing ANY file upload functionality.

RULES — Follow these with no exceptions

1. Use manual uploads (NOT auto_upload: true) for form submission patterns 2. Always add upload directory to static_paths() — files won't be accessible without this 3. Handle upload errors — display error_to_string/1 output in templates 4. Validate file types server-side — never trust client MIME types 5. Restart server after changing static_paths() — changes don't apply until restart

Implementation Workflow

Follow these steps in order:

  1. Add uploads to static_paths() — see Static Paths Configuration below
  2. Restart the server — static_paths() changes require a full restart to take effect
  3. Add allow_upload/3 in mount/3 — configure accepted types, entry limits, and file size
  4. Implement handle_event("validate", ...) — required to trigger change tracking
  5. Implement handle_event("save", ...) — consume entries, create directories, copy files
  6. Add the upload form to your template — include drop target, previews, and error display
  7. Verify — confirm upload directory exists, files are saved, and URLs resolve correctly

Upload Configuration

Manual Upload (Recommended)

allow_upload(:upload_name,
  accept: ~w(.jpg .jpeg .png .pdf),
  max_entries: 10,
  max_file_size: 10_000_000
)

Complete Upload Pattern

LiveView Module

@impl true
def mount(_params, _session, socket) do
  socket =
    socket
    |> assign(:uploaded_files, [])
    |> allow_upload(:photos,
         accept: ~w(.jpg .jpeg .png),
         max_entries: 5,
         max_file_size: 10_000_000
       )

  {:ok, socket}
end

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

@impl true
def handle_event("save", _params, socket) do
  uploaded_files =
    consume_uploaded_entries(socket, :photos, fn %{path: path}, entry ->
      dest = Path.join(["priv", "static", "uploads", safe_filename(entry.client_name)])
      File.mkdir_p!(Path.dirname(dest))
      File.cp!(path, dest)
      {:ok, ~s(/uploads/#{Path.basename(dest)})}
    end)

  {:noreply, assign(socket, :uploaded_files, uploaded_files)}
end

defp safe_filename(original_name) do
  ext = Path.extname(original_name)
  "#{Ecto.UUID.generate()}#{ext}"
end

Template

<.form for={%{}} phx-submit="save" phx-change="validate" id="upload-form">
  <div phx-drop-target={@uploads.photos.ref}>
    <.live_file_input upload={@uploads.photos} />
  </div>

  <%= for entry <- @uploads.photos.entries do %>
    <div>
      <.live_img_preview entry={entry} />
      <progress value={entry.progress} max="100"><%= entry.progress %>%</progress>

      <%= for err <- upload_errors(@uploads.photos, entry) do %>
        <p class="error"><%= error_to_string(err) %></p>
      <% end %>
    </div>
  <% end %>

  <button type="submit">Upload</button>
</.form>

def error_to_string(:too_large), do: "Too large"
def error_to_string(:too_many_files), do: "Too many files"
def error_to_string(:not_accepted), do: "Unacceptable file type"

Static Paths Configuration

# lib/my_app_web endpoint.ex
def static_paths do
  ~w(assets fonts images favicon.ico robots.txt uploads)
end

Common Pitfalls

❌ Don't✅ Do
Use auto_upload: true with form submissionUse manual uploads and consume on save
Forget to add uploads to static_paths()Add the dir to static_paths() (files 404 without it)
Expect static_paths() edits to hot-reloadRestart the server after changing static_paths()
Trust the client-reported MIME typeValidate file types server-side
Keep the client's original filenameGenerate a safe filename (e.g. Ecto.UUID.generate/0 + ext)
Swallow upload errors silentlyRender error_to_string/1 per entry in the template
Skip the "validate" handlerImplement handle_event("validate", ...) to track changes

Integration

PredecessorThis SkillSuccessor
phoenix-liveview-essentialsphoenix-uploadstesting-essentials
apply-phoenix-liveview-conventionsphoenix-uploadssecurity-essentials

Companion skills:

  • phoenix-liveview-essentials — LiveView lifecycle and form binding
  • security-essentials — validating and safely serving user-supplied files
  • testing-essentials — exercising uploads with Phoenix.LiveViewTest

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.