Library / SDK
shrinerb/shrine avatar
shrinerb/shrine

Shrine: a plugin-based file attachment toolkit for Ruby

File Attachment toolkit for Ruby applications

3,285 stars277 forksRubyMIT

At a glance

What is it?
Shrine handles file attachments in Ruby applications through storages, uploaders and a plugin system you enable piece by piece. It is aimed at teams that want direct uploads, background processing and metadata validation without adopting a framework's built-in attachment layer.
Who is it for?
Shrine fits Ruby teams that need attachment behaviour they can assemble themselves: separate cache and store storages, uploader classes registered on models, and plugins such as activerecord, cached_attachment_data and restore_cached_data.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 10 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Shrine solves, and who it is written for

Shrine is a toolkit for handling file attachments in Ruby applications, and the README frames it as a set of pieces rather than a finished attachment layer. The problem it addresses is the gap between a file arriving from a browser and a record in a database pointing at a stored object. Shrine covers that gap with storages, uploader classes and plugins, and it leaves the choice of ORM, cloud provider and background library to you. The README lists persistence integrations for Sequel, ActiveRecord, ROM, Hanami and Mongoid, storage backends for disk, AWS S3, Google Cloud and Cloudinary, and background processing that supports any backgrounding library. That breadth is the point: the same attachment code can sit behind ActiveRecord in one app and Sequel in another. The intended reader is a Ruby developer who is willing to write an uploader class and a couple of initializer lines, and who wants the attachment behaviour to be explicit in the codebase rather than implied by a framework. If you would rather not think about cache versus store, this is not the library for you.

How storages, uploaders and plugins fit together

The architecture visible in the README is a two-stage storage model. Shrine.storages is a hash with a cache entry and a store entry. The cache is temporary and the store is permanent, and the comments in the initializer example say exactly that. A file moves from cache to store when the record is saved, which is why the form in the README carries a hidden field holding cached_image_data alongside the file field. The virtual attribute added by include ImageUploader::Attachment(:image) accepts the uploaded file from params, and the underlying <name>_data column holds the serialized attachment. Uploader classes subclass Shrine and carry the plugins and uploading logic, so behaviour such as processing or validation lives in the uploader rather than in the model. Plugins are loaded with Shrine.plugin, and the README's own setup loads activerecord for the ORM integration, cached_attachment_data to keep the cached file across form redisplays, and restore_cached_data to extract metadata for assigned cached files. The plugin system is described as the mechanism that lets you load only the functionality you need, which means the default surface is small and the rest is opt-in.

Installing Shrine and attaching a first file

The README gives the install as a single Bundler command. Run it in the application directory and Bundler adds the gem to your Gemfile and installs it.

bash
bundle add shrine

Next, create config/initializers/shrine.rb. The README's example sets up a filesystem cache and store and loads three plugins. The prefix values are what separate temporary uploads from permanent ones, so keep uploads/cache and uploads distinct.

rb
require "shrine"
require "shrine/storage/file_system"

Shrine.storages = {
  cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), # temporary
  store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"),       # permanent
}

Shrine.plugin :activerecord           # loads Active Record integration
Shrine.plugin :cached_attachment_data # enables retaining cached file across form redisplays
Shrine.plugin :restore_cached_data    # extracts metadata for assigned cached files

The attachment needs a column. For an image attachment on a photos table, the README generates a migration adding image_data, typed as text or jsonb. If you choose jsonb, the README suggests considering a gin index for fast key-value pair searchability within image_data.

bash
$ rails generate migration add_image_data_to_photos image_data:text # or :jsonb

Then write an uploader class, which the README places in app/uploaders, and register the attachment on the model. The include line is what adds the image virtual attribute to Photo.

rb
class ImageUploader < Shrine
  # plugins and uploading logic
end

class Photo < ActiveRecord::Base
  include ImageUploader::Attachment(:image) # adds an `image` virtual attribute
end

The form needs both a hidden field for the cached data and a file field. The hidden field's value is @photo.cached_image_data, which is what survives a redisplay when the record fails validation.

erb
<%= form_for @photo do |f| %>
  <%= f.hidden_field :image, value: @photo.cached_image_data, id: nil %>
  <%= f.file_field :image %>
  <%= f.submit %>
<% end %>

In the controller, permit the attribute and create the record. The README's example relies on strong parameters to pass the uploaded file through, and the comment states that creating the record attaches the uploaded file.

rb
class PhotosController < ApplicationController
  def create
    Photo.create(photo_params) # attaches the uploaded file
    # ...
  end

  private

  def photo_params
    params.require(:photo).permit(:image)
  end
end

To display the result, the README uses image_url on the attachment. After the record is saved you should see the file served from the store prefix, and the image_data column should contain the serialized attachment rather than a bare filename.

erb
<%= image_tag @photo.image_url %>

What the setup does not decide for you

The README's own setup is deliberately incomplete, and that is the main limitation to weigh. It shows ImageMagick and libvips as the two processing backends, reached through the image_processing gem, but the initializer example loads neither, so thumbnails require an explicit plugin choice and the corresponding system binaries in your image. The same applies to cloud storage: the README links to separate pages for S3 and to third-party repositories for Google Cloud and Cloudinary, so an S3 deployment depends on a storage plugin documented outside the README. Direct uploads are split into simple, presigned and resumable variants, and the resumable path points at Uppy and at uppy-s3_multipart or tus-ruby-server, which means a client-side JavaScript component and additional server-side code. Background processing is described as supporting any backgrounding library, which is flexibility purchased with integration work. None of this is a defect in the design, but it means the README is a starting point rather than a complete deployment guide, and the parts it leaves out are the parts that touch your infrastructure.

Shrine against Active Storage

The README lists Active Storage among similar libraries, and the difference is where the attachment logic lives. Active Storage ships with Rails and generates the tables, models and controllers for you, so attachments work after a single install task and you do not write an uploader class. Shrine asks you to define Shrine.storages, write an uploader subclass, add a data column and register the attachment on the model. In exchange, the storage configuration is visible in an initializer instead of hidden behind framework conventions, the ORM integration is a plugin you choose rather than a fixed assumption, and the plugin system lets you leave out processing, validation or direct upload until you need them. The README also notes that Shrine borrows the idea of backends, here called storages, from Refile, and the plugin system implementation from Roda. For a team already committed to Rails and to a single cloud provider, Active Storage is less code. For a team that wants the attachment layer to be an explicit part of the application, Shrine is the more transparent option.

Maintenance, upgrades and the MIT licence

The repository is not archived, and the last push was on 2026-09-20, so the project is being worked on as of that date. The repository carries a CHANGELOG.md at the top level, which is where upgrade notes belong, and the README points at a wiki and two discussion forums for questions. That combination matters for cost: because functionality arrives through plugins, an upgrade can change behaviour in a plugin you enabled without changing the core gem, so the changelog is the file to read before bumping the version. The gem is released under the MIT License, per the README and the LICENSE.txt file in the repository. MIT is permissive and imposes no copyleft obligation on your application, but it also means the project offers no warranty, and the README does not describe a commercial support arrangement. If your organisation requires a support contract or an indemnity, that requirement is not met by this licence. Nothing here is legal advice; read LICENSE.txt yourself if the distinction matters to you.

Editorial conclusion

Shrine fits Ruby teams that need attachment behaviour they can assemble themselves: separate cache and store storages, uploader classes registered on models, and plugins such as activerecord, cached_attachment_data and restore_cached_data. Teams that want attachments generated for them by a framework, with no uploader class and no storage configuration, should stay with their framework's built-in layer, and anyone storing files on S3 or GCS should confirm their storage gem's own documentation before committing. Verify first that the extraction and processing gems your uploader needs are available in your deployment image, because the README shows the plugin calls but not the external tools behind them.

Frequently asked questions

How do I install Shrine in a Ruby application?

The README gives a single command, bundle add shrine, followed by a config/initializers/shrine.rb file that sets Shrine.storages and loads the plugins you need. You then add a data column to the table, write an uploader class and register the attachment on the model.

Does Shrine work with ActiveRecord and other ORMs?

Yes. The README's setup loads Shrine.plugin :activerecord for the Active Record integration, and the README also lists Sequel, ROM, Hanami and Mongoid integrations. The ORM integration is a plugin, so it is not loaded unless you ask for it.

Can Shrine store files on cloud services instead of disk?

The README lists disk, AWS S3, Google Cloud and Cloudinary among the storage options. Disk and S3 are documented on the project site, while the Google Cloud and Cloudinary storages are linked as separate repositories, so their configuration lives outside the README.

What does Shrine use the cache storage for?

The cache storage is temporary and the store storage is permanent, according to the comments in the README's initializer example. The cached_attachment_data plugin retains the cached file across form redisplays, and the form carries the cached data in a hidden field.

Official sources

  1. Issues
  2. License: MIT
  3. Project website
  4. README
  5. shrinerb/shrine on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/shrinerb-shrine.svg)](https://hysenlabs.com/projects/shrinerb-shrine)