Paperclip: Rails attachments before Active Storage

For most of the 2010s, adding an avatar to a Rails model meant one line: has_attached_file :avatar. Paperclip was the default answer to file uploads until Rails grew its own. Here is where it stands, and how to move on.
What Paperclip did
Paperclip, published by thoughtbot, stored attachment metadata in columns on your own table and processed images with ImageMagick:
class User < ActiveRecord::Base
has_attached_file :avatar,
styles: { medium: "300x300>", thumb: "100x100#" },
default_url: "/images/missing.png"
validates_attachment_content_type :avatar, content_type: /\Aimage\/.*\z/
end
A migration added four columns per attachment: avatar_file_name, avatar_content_type, avatar_file_size and avatar_updated_at. Each "style" was generated when the file was uploaded, using ImageMagick geometry strings, and stored on the local filesystem or Amazon S3. In the view, user.avatar.url(:thumb) returned the right path.
The design was simple to understand and easy to query, which explains its popularity. Its weaknesses grew with time: styles were processed synchronously during the request unless you added a gem such as delayed_paperclip, adding a new style meant reprocessing every file, and content-type spoofing became a security concern, which is why later versions required an explicit content-type validation (or an explicit opt-out) on every attachment.
The Rails 3 transition
When Rails 3 arrived in 2010, Paperclip, like most plugins, had to adapt to Bundler, the new Railtie system and Active Record's new query interface. For a while users ran a dedicated Rails 3 branch or community forks installed from Git, before released gem versions caught up. Paperclip then followed Rails through versions 4 and 5, and remained one of the most widely used attachment libraries until Active Storage.
Deprecation
In 2018, after Active Storage shipped with Rails 5.2, thoughtbot announced that Paperclip was deprecated and recommended Active Storage for new projects. The repository carries a deprecation notice and a migration guide. According to RubyGems.org, its last release is 6.1.0, from July 2018. No fixes, including security fixes, are published for it any more.
A community fork, kt-paperclip, keeps the same API working on newer Ruby and Rails versions and is still released (8.0.1 in October 2026, per RubyGems.org). It is a sensible bridge for an application that cannot migrate yet, but it is a fork with its own, smaller maintainer base; treat it as a way to buy time rather than a long-term home.
Migrating to Active Storage
Active Storage stores attachments in two shared tables rather than in columns on your model, so migration involves copying data, not just changing a line. The usual approach:
- Add Active Storage with
bin/rails active_storage:installand configure a service that points at your existing bucket or a new one. - Declare the new attachment under a temporary name (for example
has_one_attached :avatar_file) next to the Paperclip one. - Backfill in a background job: for each record, either create an
ActiveStorage::Blobpointing at the existing object key and checksum, or download and re-attach the original file. Pointing at existing keys avoids copying data but requires care with key formats; re-attaching is slower but simpler. - Switch reads to the new attachment, replace Paperclip styles with Active Storage variants, and check every view and serializer.
- Remove Paperclip and its columns once nothing reads them.
Paperclip's own migration guide in its repository describes the data mapping in detail, and the Active Storage Overview covers variants, direct uploads and services.
Or Shrine
If you prefer to keep attachment data on your own table, Shrine stores it in a single JSON column and offers explicit derivatives, validation and backgrounding. Its documentation includes a guide for migrating from Paperclip, and it can be a smoother path for applications that rely heavily on Paperclip's per-model columns. Our file uploads guide compares the options.
Checklist for legacy Paperclip apps
- Is every attachment validated for content type and size? If not, fix that first.
- Are ImageMagick and its delegates up to date on the servers? Paperclip passes uploaded files to them.
- Run
bundle-audit check --update; old Paperclip versions have published advisories. - Decide between kt-paperclip as a stopgap and a migration with a deadline.
Category guidesFile uploads
Choosing a file attachment solution for Rails: Active Storage, Shrine and CarrierWave compared, image variants with image_processing, validation, and migrating away from Paperclip.
Plugin historiesacts_as_list
How acts_as_list keeps Active Record rows in order, its history since it left Rails core in Rails 2.0, its status in 2026, and alternatives such as positioning and ranked-model.
GuidesFrom plugins to engines
How Rails extensions moved from vendor/plugins to gems, Railties and engines between Rails 2 and Rails 4, and what that means when you read old tutorials or upgrade an old app.