{"id":19210890,"url":"https://github.com/mathieuprog/uploader","last_synced_at":"2025-07-15T23:37:53.360Z","repository":{"id":62430681,"uuid":"219475439","full_name":"mathieuprog/uploader","owner":"mathieuprog","description":"File upload library for Elixir","archived":false,"fork":false,"pushed_at":"2019-12-05T06:40:10.000Z","size":29,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-13T22:39:35.158Z","etag":null,"topics":["ecto","elixir","elixir-lang","file-upload","upload"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mathieuprog.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-11-04T10:30:58.000Z","updated_at":"2022-09-07T09:30:57.000Z","dependencies_parsed_at":"2022-11-01T20:30:55.775Z","dependency_job_id":null,"html_url":"https://github.com/mathieuprog/uploader","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/mathieuprog/uploader","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mathieuprog%2Fuploader","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mathieuprog%2Fuploader/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mathieuprog%2Fuploader/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mathieuprog%2Fuploader/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mathieuprog","download_url":"https://codeload.github.com/mathieuprog/uploader/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mathieuprog%2Fuploader/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265467646,"owners_count":23770757,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ecto","elixir","elixir-lang","file-upload","upload"],"created_at":"2024-11-09T13:39:38.805Z","updated_at":"2025-07-15T23:37:53.333Z","avatar_url":"https://github.com/mathieuprog.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Uploader\n\n`Uploader` helps you with storing file uploads.\n\nThere are numerous file upload libraries for Elixir already available, however\nthis library was made in order to give the user a finer control over how `Plug.Upload`\nstructs are casted into schema fields, over the filenames that should be generated,\nthe strategy to apply for existing file paths, etc.\n\nImagine a single image upload that must to be stored multiple times with filenames\nin different languages for SEO purposes. The writing of this library was motivated\nto address such advanced use cases.\n\n## Define the Schema fields holding uploads\n\n```elixir\ndefmodule MyApp.User do\n  use Ecto.Schema\n  use Uploader.Ecto.UploadableFields\n\n  schema \"users\" do\n    uploadable_field :avatar_image\n  end\nend\n```\n\nYou may import `:uploader`'s formatter configuration by importing\n`uploader` into your `.formatter.exs` file (this allows for example to keep\n`uploadable_field :avatar_image` without parentheses when running `mix format`).\n\n```elixir\n[\n  import_deps: [:ecto, :phoenix, :uploader],\n  #...\n]\n```\n\n## Cast `Plug.Upload` structs\n\nIn order to cast file uploads (`Plug.Upload` structs) into filenames, you must\ncall `cast_with_upload/3` from the `Uploader.Ecto.Changeset` module for the\ngiven Schema struct and params. Note that, in addition to casting file uploads,\n`cast_with_upload/3` implicitly calls `Ecto.Changeset.cast/4` for the given params\nand returns the changeset.\n\n```elixir\nimport Uploader.Ecto.Changeset\n\nschema \"user\" do\n  uploadable_field :avatar_image\nend\n\n@required_fields ~w(avatar_image)a\n@optional_fields ~w()a\n\ndef changeset(user, attrs) do\n  user\n  |\u003e cast_with_upload(attrs, @required_fields ++ @optional_fields)\n  |\u003e validate_required(@required_fields)\nend\n```\n\n## Copy the uploaded files\n\nThis library expects the storing of files to happen in a transaction. This is done\nwith a call to `Uploader.store_files/1` given a Schema struct.\n\n```elixir\nMulti.new()\n|\u003e Multi.insert(:user, user_changeset)\n|\u003e Multi.run(:upload_files, fn _repo, %{user: user} -\u003e\n  Uploader.store_files(user)\nend)\n|\u003e Repo.transaction()\n|\u003e case do\n  {:ok, %{user: user}} -\u003e\n    {:ok, user}\n\n  {:error, :user, %Ecto.Changeset{} = changeset, _changes} -\u003e\n    {:error, changeset}\n\n  {:error, :upload_files, {:file_path_exists, file_path}, _changes} -\u003e\n    raise \"file upload failed: path \\\"#{file_path}\\\" already exists\"\nend\n```\n\n## Print URLs to uploaded files\n\nA view helper is provided in order to print the URL of uploaded files.\n\nOpen up the entrypoint defining your web interface, such as `MyAppWeb`, and\nadd the line below into the `view` function's `quote` block.\n\n```elixir\ndef view do\n  quote do\n    # some code\n    import Uploader.HTML.UploadHelpers\n  end\nend\n```\n\nThe helper below prints the URL for an uploaded file:\n\n```elixir\n\u003c%= upload_url(user, :avatar_image) %\u003e\n```\n\nYou may also prepend the URL with a base URL:\n\n```elixir\n\u003c%= upload_url(user, :avatar_image, base_url: upload_path()) %\u003e\n```\n\n## Field options\n\nThe `uploadable_field/2` macro may optionally receive options to alter the way\n`Plug.Upload` structs are casted, how filenames are generated, etc. Here is a\nlist of options:\n\n* `:cast`: a function that casts a `Plug.Upload` struct into the value to be\nstored in the database.\n* `directory`: the base directory containing the file uploads.\n* `filename`: a function that generates the filename based on the given struct.\n* `on_file_exists`: specifies the strategy to apply if the file path already\nexists. Its value may be:\n    * `:overwrite`: overwrite the file if the file path already exists\n    * `:compare_hash`: do not copy the file if the file path already exists;\n    if the hashes of the files' content are not equal, return an error.\n* `print`: a function that prints the field (typically used by the view).\n* `type`: the field type.\n\n### Example case for custom options\n\nA CMS allows the upload of a blog post's cover image. The blog post is available\nin multiple languages and the cover image must be stored multiple times in\ndifferent languages for SEO purposes.\n\nFor example, the cover image for my post about my favorite books must have the\nname \"my-favorite-books.jpg\" in English, \"mes-livres-preferes.jpg\" in French,\netc. We should then store all the images' filenames and retrieve the right filename\naccording to the language used by the reader.\n\nThe image upload (`Plug.Upload`) must be casted into a map of filenames in\ndifferent languages:\n\n```elixir\n%{\n  \"en\" =\u003e \"my-favorite-books.jpg\",\n  \"fr\" =\u003e \"mes-livres-preferes.jpg\",\n  \"nl\" =\u003e \"mijn-lievelingsboeken.jpg\"\n}\n```\n\nWe want those filenames to have the same name as the post's URL slug (which are\nheld by another Schema field `:slug`) with the file extension appended. I.e. if\nthe English URL slug for that post is `my-favorite-books`, the cover image will\nbe named `my-favorite-books.jpg` in English.\n\nBelow are the Schema fields with the custom options to make that work.\n\nThe code below uses the `translatable_field/1` macro from the `i18n_helpers`\npackage. A translatable field holds a map of values for different languages\nas the map shown above, and generates a virtual field (field prepended by\n\"translated_\") holding the translated field. For example the `:slug` field\nmay contain a map such as `%{\"en\" =\u003e \"favorite-books\", \"fr\" =\u003e \"livres-preferes\"}`\nand `:translated_slug` will contain `livres-preferes` if the struct has been\ntranslated in French. See `i18n_helpers`'s [readme](https://github.com/mathieuprog/i18n_helpers/blob/master/README.md).\n\n```elixir\ntranslatable_field :slug\ntranslatable_field :cover_image\n\nuploadable_field :cover_image,\n  cast: \u0026User.cast_cover_image/2,\n  directory: @uploads_directory,\n  filename: \u0026User.filename_cover_image/2,\n  on_file_exists: :compare_hash,\n  print: \u0026User.print_cover_image/2\n```\n\n```elixir\n# Cast the Plug.Upload struct into a map of filenames per language.\n\ndef cast_cover_image(\n      %Plug.Upload{filename: uploaded_filename},\n      %Ecto.Changeset{} = changeset\n    ) do\n  slugs = fetch_field!(changeset, :slug)\n  extension = Path.extname(uploaded_filename) |\u003e String.downcase()\n  Enum.map(slugs, fn {language, slug} -\u003e {language, slug \u003c\u003e extension} end) |\u003e Enum.into(%{})\nend\n\n# Return the list of filenames to be stored.\n\ndef filename_cover_image(%User{cover_image: cover_image}, _field_name) do\n  Map.values(cover_image)\nend\n\n# Return the filename to be printed.\n\ndef print_cover_image(%User{translated_cover_image: translated_cover_image}, _field_name) do\n  translated_cover_image\nend\n```\n\n## Installation\n\nAdd `uploader` for Elixir as a dependency in your `mix.exs` file:\n\n```elixir\ndef deps do\n  [\n    {:uploader, \"~\u003e 0.2.0\"}\n  ]\nend\n```\n\n## HexDocs\n\nHexDocs documentation can be found at [https://hexdocs.pm/uploader](https://hexdocs.pm/uploader).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmathieuprog%2Fuploader","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmathieuprog%2Fuploader","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmathieuprog%2Fuploader/lists"}