Rails Active Storage in 2026: File Uploads, S3 Integration and Interview Questions
Master Rails Active Storage for file uploads with S3 and direct uploads. Complete tutorial with code examples and common interview questions about file handling in Ruby on Rails.

Rails Active Storage provides a unified API for attaching files to Active Record models, supporting local disk storage and cloud services like Amazon S3, Google Cloud Storage, and Azure Storage. Released with Rails 5.2 and refined through Rails 8.1, Active Storage handles the complexity of file uploads, transformations, and cloud integration behind a simple interface.
With direct uploads enabled, browsers upload files straight to S3 using presigned URLs. The Rails server handles two small JSON requests instead of proxying large files, reducing memory usage and avoiding request timeouts on platforms like Heroku.
Setting Up Active Storage with S3 in Rails 8
Active Storage ships with Rails. The installation creates three tables: active_storage_blobs for file metadata, active_storage_attachments for polymorphic associations, and active_storage_variant_records for tracking generated variants.
# Terminal commands
bin/rails active_storage:install
bin/rails db:migrateFor S3 integration, add the AWS SDK gem to the Gemfile. The require: false option delays loading until Active Storage needs it.
# Gemfile
gem "aws-sdk-s3", require: falseConfigure the storage service in config/storage.yml. Store credentials in Rails credentials or environment variables, never in version control.
# config/storage.yml
amazon:
service: S3
access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
region: us-east-1
bucket: myapp-production-uploadsSet the storage service per environment. Development typically uses local disk, production uses S3.
# config/environments/production.rb
config.active_storage.service = :amazonAttaching Files to Models with has_one_attached and has_many_attached
Active Storage uses two macros for attachments: has_one_attached for single files like avatars, and has_many_attached for collections like photo galleries.
# app/models/user.rb
class User < ApplicationRecord
has_one_attached :avatar
has_many_attached :documents
endAttaching files happens through assignment. Active Storage accepts ActionDispatch::Http::UploadedFile from form submissions, File objects, or blobs.
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def update
@user = User.find(params[:id])
@user.avatar.attach(params[:avatar]) if params[:avatar].present?
@user.update(user_params)
redirect_to @user
end
private
def user_params
params.require(:user).permit(:name, :email, documents: [])
end
endQuerying attachment presence uses attached?. For eager loading attachments to avoid N+1 queries, use the with_attached_* scope that Active Storage generates automatically.
# Checking attachment presence
if @user.avatar.attached?
# Process avatar
end
# Eager loading to prevent N+1 queries
User.with_attached_avatar.where(active: true)Direct Uploads: Browser-to-S3 File Transfer
Direct uploads bypass the Rails server entirely. The browser requests a presigned URL from Rails, then uploads the file directly to S3. This approach handles large files without server timeouts and reduces memory consumption.
Enable direct uploads by adding the direct_upload: true option to file fields.
<%# app/views/users/_form.html.erb %>
<%= form_with model: @user do |form| %>
<%= form.file_field :avatar, direct_upload: true %>
<%= form.file_field :documents, multiple: true, direct_upload: true %>
<%= form.submit %>
<% end %>The @rails/activestorage JavaScript library handles the upload flow. Rails 8 applications using import maps get this automatically.
import * as ActiveStorage from "@rails/activestorage"
ActiveStorage.start()S3 buckets require CORS configuration to accept direct uploads from browsers. This policy allows uploads from any origin during development; restrict it in production.
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["PUT"],
"AllowedOrigins": ["https://myapp.com"],
"ExposeHeaders": ["Origin", "Content-Type", "Content-MD5", "Content-Disposition"],
"MaxAgeSeconds": 3600
}
]Ready to ace your Ruby on Rails interviews?
Practice with our interactive simulators, flashcards, and technical tests.
Image Variants with libvips Processing
Active Storage generates image variants on-demand using the image_processing gem. Rails 8 defaults to libvips, which processes images faster than ImageMagick while using less memory.
# Gemfile
gem "image_processing", "~> 1.13"Define variants inline or as named methods. Variants are generated lazily when first requested and cached for subsequent requests.
# Inline variant in view
<%= image_tag @user.avatar.variant(resize_to_limit: [200, 200]) %>
# Named variant for reuse
# app/models/user.rb
class User < ApplicationRecord
has_one_attached :avatar do |attachable|
attachable.variant :thumbnail, resize_to_limit: [100, 100]
attachable.variant :medium, resize_to_limit: [300, 300]
end
end
# Using named variant
<%= image_tag @user.avatar.variant(:thumbnail) %>The July 2026 security update (CVE-2026-66066) blocks untrusted libvips loaders by default. Applications processing BMP, ICO, or PSD files need explicit configuration, and libvips must be version 8.13 or higher.
Validating File Uploads in Rails 8
Active Storage does not include built-in validations. Use the active_storage_validations gem or implement custom validators.
# Gemfile
gem "active_storage_validations"
# app/models/user.rb
class User < ApplicationRecord
has_one_attached :avatar
validates :avatar,
content_type: [:png, :jpg, :jpeg, :webp],
size: { less_than: 5.megabytes }
endFor custom validation without external gems, implement an Active Model validator.
# app/models/user.rb
class User < ApplicationRecord
has_one_attached :avatar
validate :acceptable_avatar
private
def acceptable_avatar
return unless avatar.attached?
allowed_types = ["image/png", "image/jpeg", "image/webp"]
unless allowed_types.include?(avatar.content_type)
errors.add(:avatar, "must be PNG, JPEG, or WebP")
end
if avatar.byte_size > 5.megabytes
errors.add(:avatar, "must be less than 5MB")
end
end
endS3 Alternatives: Cloudflare R2 and Backblaze B2
S3-compatible services like Cloudflare R2 offer significant cost savings. R2 provides 10GB free storage with zero egress fees. Configure these services using the S3 adapter with a custom endpoint.
# config/storage.yml
cloudflare_r2:
service: S3
access_key_id: <%= ENV["R2_ACCESS_KEY_ID"] %>
secret_access_key: <%= ENV["R2_SECRET_ACCESS_KEY"] %>
region: auto
bucket: myapp-uploads
endpoint: https://<ACCOUNT_ID>.r2.cloudflarestorage.comThe Rails documentation on Active Storage covers additional configuration options for different storage providers.
Common Interview Questions About Active Storage
Technical interviews for Rails positions frequently include questions about file handling. These questions assess understanding of storage patterns, performance considerations, and security practices.
How does Active Storage differ from CarrierWave or Shrine?
Active Storage ships with Rails and requires no external dependencies beyond the cloud SDK gems. CarrierWave and Shrine are standalone gems with different philosophies. CarrierWave uses uploader classes for processing logic. Shrine emphasizes a plugin architecture with explicit configuration. Active Storage favors convention and integrates tightly with Action View helpers and Active Record associations. For more foundational concepts, the Active Storage module questions on SharpSkill cover the full range of interview topics.
What happens to attachments when a record is destroyed?
By default, Active Storage purges attachments asynchronously when the parent record is destroyed. The dependent option controls this behavior.
# Purge attachments synchronously on destroy
has_one_attached :avatar, dependent: :purge_later # default
has_one_attached :avatar, dependent: :purge # synchronous
has_one_attached :avatar, dependent: false # keep filesHow do direct uploads handle network failures?
The JavaScript library emits events during the upload lifecycle. Applications can listen for direct-upload:error to handle failures and provide user feedback.
addEventListener("direct-upload:error", (event) => {
const { id, error } = event.detail
const element = document.getElementById(`direct-upload-${id}`)
element.classList.add("upload-error")
element.setAttribute("title", error)
})How would you prevent N+1 queries when displaying multiple users with avatars?
Active Storage generates a with_attached_* scope for each attachment. This scope eager-loads the blob and attachment records.
# N+1 problem: separate query per avatar
User.all.each { |u| u.avatar.filename } # Bad
# Solution: eager load attachments
User.with_attached_avatar.each { |u| u.avatar.filename } # GoodFor more Ruby on Rails interview questions, including ActiveRecord optimization and testing strategies, SharpSkill provides focused preparation materials.
Mirroring for Redundancy and Migration
Active Storage supports mirroring uploads to multiple services simultaneously. This pattern works for redundancy or when migrating between storage providers.
# config/storage.yml
production:
service: Mirror
primary: amazon
mirrors:
- cloudflare_r2
amazon:
service: S3
# ... S3 config
cloudflare_r2:
service: S3
# ... R2 configWith mirroring, uploads go to all services, but downloads come from the primary. After migration completes, switch the primary and remove the mirror.
Performance Optimization for Production Deployments
Several techniques improve Active Storage performance in production environments.
Proxy mode for private files: Active Storage can proxy downloads through Rails or redirect to signed URLs. Redirects are faster but require public files or signed URL support.
# config/environments/production.rb
config.active_storage.resolve_model_to_route = :rails_storage_proxy # proxy
config.active_storage.resolve_model_to_route = :rails_storage_redirect # redirectVariant tracking: Rails 8.1 tracks generated variants in active_storage_variant_records. This table prevents regenerating the same variant repeatedly.
Background purging: Configure Active Job to process ActiveStorage::PurgeJob in a dedicated queue to avoid blocking other jobs.
# config/application.rb
config.active_storage.queues.purge = :low_priorityStart practicing!
Test your knowledge with our interview simulators and technical tests.
Key Takeaways for Rails Active Storage Implementation
- Install Active Storage with
bin/rails active_storage:installand configure S3 credentials inconfig/storage.ymlusing Rails credentials - Enable direct uploads with
direct_upload: trueon file fields to bypass server file handling and reduce memory pressure - Use
with_attached_*scopes when loading multiple records with attachments to prevent N+1 queries - Add the
active_storage_validationsgem or implement custom validators since Active Storage has no built-in file validation - Consider S3-compatible alternatives like Cloudflare R2 for reduced egress costs
- Update to Rails 8.1.3.1 or later for the CVE-2026-66066 security patch affecting libvips image processing
- Use mirroring when migrating between storage providers to maintain file availability during transition
Can you spot the bug in Ruby on Rails?
One real snippet, one hidden bug, one attempt a day. No account needed to try.

Written by
Anthony Fillion-MailletFounder of SharpSkill
Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.
Updated on September 9, 2026
Tags
Share
Related articles

Rails Background Jobs in 2026: Sidekiq vs Good Job and Interview Questions
Compare Sidekiq, Good Job, and Solid Queue for Rails background jobs. Learn when to choose each option and prepare for technical interview questions about Active Job.

Rails Service Objects in 2026: Design Patterns, PORO and Technical Interview Questions
Master Rails service objects with PORO patterns, Result monads, and clean architecture. Includes real interview questions and production-ready code examples for Rails 8.

Rails Credentials and Secrets in 2026: Secure Environment Variables Management
Master Rails 8 credentials system for secure secret management. Learn encrypted credentials, environment-specific keys, deployment strategies, and best practices for API keys and database passwords.