Lucky handles file uploads through Latch, a Crystal library purpose-built for attaching files to your application. Latch is short for Lucky Attachment, and while it works with any Crystal framework it ships with first-class Lucky and Avram support.
A typical Latch setup involves three things:
Here is a complete example end-to-end:
# src/uploaders/avatar_uploader.cr
struct AvatarUploader
include Latch::Uploader
struct VersionsProcessor
include Latch::Processor::Magick
original resize: "2000x2000>"
variant thumb, resize: "200x200", crop: "200x200+0+0", gravity: "center"
end
extract dimensions, using: Latch::Extractor::DimensionsFromMagick
process versions, using: VersionsProcessor
end
# src/models/user.cr
class User < BaseModel
include Latch::Avram::Model
table do
attach avatar : AvatarUploader::StoredFile?
end
end
# src/operations/save_user.cr
class SaveUser < User::SaveOperation
attach avatar, process: true
before_save do
validate_file_size_of avatar_file, max: 5_000_000
validate_file_mime_type_of avatar_file, in: %w[image/png image/jpeg image/webp]
end
end
Saving a user with an attached file is then no different from any other Avram operation:
user = SaveUser.create!(params)
user.avatar.try(&.url) # => "/uploads/user/1/avatar/a1b2c3d4.jpg"
user.avatar.try(&.versions_thumb.url) # => ".../versions_thumb.jpg"
user.avatar.try(&.width) # => 2000
The remaining guides in this section walk through each piece in detail:
StoredFile looks like.Earlier versions of this guide recommended Shrine.cr, a port of Ruby’s Shrine library. That project is no longer actively maintained, and its Ruby-flavoured API never really fit Crystal’s macro-driven style.
Latch was written from the ground up for Crystal and leans on its macro
system for compile-time validation of processor options, generated
StoredFile accessors, and zero-runtime-cost integration with Avram. It is
not a port of Shrine.
For the sake of interoperability, however, Latch serializes StoredFile
objects in a format that is compatible with Shrine’s database
representation:
{
"id": "uploads/a1b2c3d4.jpg",
"storage": "store",
"metadata": {
"filename": "photo.jpg",
"size": 102400,
"mime_type": "image/jpeg",
"width": 2000,
"height": 1333
}
}
This means existing data uploaded by Shrine.cr (or even by Ruby’s Shrine) can be read back through Latch without a migration, as long as the underlying files remain in the same storage location.
These guides cover the most common cases for building file upload features in a Lucky app. For an exhaustive reference of every macro, option, and extension point, see the Latch README and the API docs. Each of the following pages links into the relevant section of the README where applicable.