Introducing AshStorage! Attachment and file management that slots directly into your resources ![]()
I had hoped to get this to a releasable state before sharing. It’s not quite there yet but I’m announcing it anyway as I know the Ash community is waiting.
It isn’t on hex yet, but I’ve gotten quite a bit of the roadmap done and its in a decent place to accept contributions from the community, and for beta testers to give it a shot.
Examples:
defmodule MyApp.Post do
use Ash.Resource,
extensions: [AshStorage]
storage do
service {AshStorage.Service.S3, bucket: "my-app-uploads"}
blob_resource MyApp.StorageBlob
attachment_resource MyApp.StorageAttachment
has_one_attached :cover_image do
analyzer MyApp.ImageDimensions,
write_attributes: [width: :image_width, height: :image_height]
variant :thumbnail, {MyApp.Resize, width: 200, height: 200}, generate: :eager
variant :hero, {MyApp.Resize, width: 1200, height: 630}, generate: :oban
variant :webp, {MyApp.Convert, format: :webp}
end
has_many_attached :documents do
analyzer MyApp.FileInfo
analyzer MyApp.PdfPageCount, analyze: :oban
variant :preview, MyApp.PdfThumbnail, generate: :oban
end
end
attributes do
uuid_primary_key :id
attribute :title, :string, allow_nil?: false, public?: true
attribute :image_width, :integer, public?: true
attribute :image_height, :integer, public?: true
end
actions do
create :create do
accept [:title]
argument :cover_image, Ash.Type.File, allow_nil?: true
change {AshStorage.Changes.HandleFileArgument,
argument: :cover_image, attachment: :cover_image}
end
end
end
defmodule MyApp.StorageBlob do
use Ash.Resource,
extensions: [AshStorage.BlobResource, AshOban]
postgres do
table "storage_blobs"
repo MyApp.Repo
end
oban do
triggers do
trigger :run_pending_analyzers do
action :run_pending_analyzers
read_action :read
where expr(pending_analyzers == true)
scheduler_cron("* * * * *")
end
trigger :run_pending_variants do
action :run_pending_variants
read_action :read
where expr(pending_variants == true)
scheduler_cron("* * * * *")
end
trigger :purge_blob do
action :purge_blob
read_action :read
where expr(pending_purge == true)
scheduler_cron("* * * * *")
end
end
end
attributes do
uuid_primary_key :id
end
end
# Create with file — dimensions written to parent, thumbnail generated instantly
post = Ash.create!(Post, %{title: "Hello", cover_image: upload})
post.image_width #=> 1920
# Variant URLs — thumbnail ready, hero generating in background
post = Ash.load!(post, [:cover_image_thumbnail_url, :cover_image_hero_url])
post.cover_image_thumbnail_url #=> "https://my-app-uploads.s3.amazonaws.com/..."
# On-demand — webp variant generated on first request
post = Ash.load!(post, :cover_image_webp_url)






















