active_storage_validations: file upload rules you write like model validations
Do it like => validates :photos, attached: true, content_type: ['image/png', 'image/jpg', 'image/jpeg'], size: { less_than: 500.kilobytes }, limit: { min: 1, max: 3 }, aspect_ratio: :landscape, dimension: { width: { in: 800..1600 }
At a glance
- What is it?
- active_storage_validations is an MIT-licensed Ruby gem that adds validation options to Rails Active Storage attachments, so a file field can be constrained by content type, size, dimensions, aspect ratio, duration, page count and more through the same `validates` call you already use. The judgement is that the gem's real work is shelling out to system tools, and that the operational cost of those tools is the thing to plan for.
- Who is it for?
- active_storage_validations fits a Rails team that accepts user file uploads and needs the server to enforce the same constraints the browser-side file input advertises, in a form that reads like the rest of the model. It does not fit a deployment that cannot install system binaries, since the image, video, audio, PDF and spoofing checks all depend on command line tools being present, and it does not fit a team that treats client-side hints as sufficient.
- 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 7 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 October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The whole API is a validates call
The gem's pitch is brevity. A file attachment gets constraints through the same Rails validation syntax as an attribute, and the README's example is the entire introduction:
validates :avatar, attached: true, content_type: 'image/png'That is the design constraint the project is built around: no separate DSL, no custom validator class to inherit, no attachment-specific object. The options sit alongside `presence` and `uniqueness` in a call you would already be writing.
The set of validators is broad. The README's table of contents lists them: `attached`, `limit`, `content type`, `size`, `total size`, `dimension`, `duration`, `with audio`, `aspect ratio`, `processable file` and `pages`.
Each of those maps to a different question about the upload. `attached` asks whether anything is there at all. `limit` asks how many files may be attached. `content type` asks what the file claims to be. `size` asks how big one file is and `total size` asks how big the set is together. `dimension` and `aspect ratio` ask about the shape of an image or a video frame. `duration` and `with audio` are about media length and whether a video has a soundtrack. `processable file` asks whether the file can be opened and read at all, and `pages` asks how many pages a PDF has.
The `limit` option is the one that interacts with the others. A validation like `limit: { min: 1, max: 3 }` combined with `content_type` and `size` describes a field that accepts one to three files of a given type under a size cap, which is a rule set most upload forms need and Rails does not provide on its own.
The gem is MIT licensed with the text in `MIT-LICENSE` at the repository root, and it works with Rails 7.0.1 onwards and Ruby 3.3 onwards.
Metadata validators need command line tools, not gems
This is the part of the gem that decides whether it works in your environment, and it is documented with unusual care.
The image metadata validators, which are `dimension`, `aspect_ratio` and `processable_file`, need one of two image libraries:
gem 'ruby-vips', '>= 2.1.0'
# Or
gem 'mini_magick', '>= 4.9.5'Plus the corresponding command line tool on the system, `libvips` for `ruby-vips` or ImageMagick for `mini_magick`, and the documentation repeats the instruction for local, CI and production environments. The same shape applies to the other media types. Video and audio validators, meaning `dimension`, `aspect_ratio`, `processable_file`, `duration` and `with_audio`, need no extra gem but do need `ffmpeg` installed. The PDF validators, `dimension`, `aspect_ratio`, `processable_file` and `pages`, need `poppler`.
The project recommends libvips and gives the reason. Rails already defaults Active Storage variants to libvips, and the project's own image processor benchmarks in `benchmark/BASELINE.md` show cold metadata analysis is about 8 times faster than MiniMagick/ImageMagick on the same machine and fixtures, with warm validations using cached `asv_*` metadata being similar for both.
The caching point is the important one. The gem stores analysed metadata in keys prefixed `asv_`, so the first validation of a given file pays the analysis cost and later validations read the cache. That changes the performance shape from per-request to per-file, which is why the 8 times figure matters most for a first upload and why the same file uploaded twice is cheap.
The `processable_file` validator is a good example of a check that only the system tools can do. It is not asking what the extension says, it is asking whether the file can actually be decoded, and that means the decoder has to exist on the machine.
Content type spoofing needs a second opinion about the file
The `content_type` validator has an option called `spoofing_protection`, and it is the most security-relevant part of the gem.
Without it, the validator works from the declared content type, which is attacker-controlled. With it enabled, the gem runs an external analyzer to determine what the file actually is, and compares. Two backends are supported.
The default backend, `true` or `:file`, shells out to the UNIX `file` command, which is usually preinstalled. The alternative, `:magika`, uses the Google Magika CLI, installed with `brew install magika`, their install script, `cargo install --locked magika-cli`, or `pipx install magika`.
The documentation's recommendation is to prefer `:magika` when you can install the CLI, because it is generally more accurate than `file`, especially on textual or ambiguous formats. That is the right instinct: `file` is a magic-number sniffer, and a file with a text header and a misleading extension is exactly the case where it is least informative. Magika is a content-type classifier built for this problem.
Both backends are optional system tools rather than Ruby gems, which means they can silently be absent in a container image that was built for the application without them. The documentation repeats the same warning about CI and production that appears for ffmpeg and poppler, and points at the gem's own CI workflow as the reference for how to add these binaries to a Docker image.
The practical consequence for an adoption decision is that enabling spoofing protection means a subprocess per uncached upload, and a container that lacks the binary will not fail loudly at boot. It will fail at validation time, in production, on the first upload.
Every analyzer runs as a subprocess with a deadline
The configuration file is where the operational behaviour of the gem becomes explicit, and it is worth reading in full before deploying it.
# config/initializers/active_storage_validations.rb
ActiveStorageValidations.configure do |config|
# Infer HTML accept= on file_field from content_type validators (default: true)
# config.infer_file_field_accept = false
# Max time for external analyzer commands: ffprobe, pdfinfo, file, magika, ImageMagick identify, libvips
# (default: 10.seconds; set to nil to disable)
# config.command_timeout = 10.seconds
endTwo settings are documented here. `infer_file_field_accept` defaults to true and makes the gem infer the HTML `accept` attribute on a `file_field` from your `content_type` validators, which is a convenience that keeps the browser hint and the server rule in sync. `command_timeout` is the important one. It bounds every external analyzer command, and the list of what it covers is explicit: ffprobe, pdfinfo, file, magika, ImageMagick identify and libvips. The default is 10 seconds, and setting it to nil disables the limit.
The gem also emits two `ActiveSupport::Notifications` events, so the timing is observable rather than hidden. `analyze.active_storage_validations` fires every time an analyzer command runs, meaning on a cache miss, and its payload carries `analyzer`, `command`, `timeout`, `timed_out` and `duration`. `timeout.active_storage_validations` fires only when a command exceeds its deadline, with `analyzer`, `command` and `timeout` in the payload.
The commented-out subscribers in the configuration show how to consume them, logging a warning on timeout and the analysis duration respectively. A deployment that enables these validators without subscribing to the timeout event has no signal that a subprocess is hanging, which is the failure mode a 10 second default is there to bound.
The gem's own docs describe `command_timeout` as bounding metadata analysis, and the notification pair is the mechanism for noticing when that bound is hit.
Validators grouped by the tool they need
The table of contents groups the validators by media type, and that grouping is really a map of external dependencies.
The image group is `dimension`, `aspect_ratio` and `processable_file`, and it needs libvips or ImageMagick. The video and audio group adds `duration` and `with_audio` to the same three, and it needs ffmpeg. The PDF group is `dimension`, `aspect_ratio`, `processable_file` and `pages`, and it needs poppler.
The overlap is instructive. `dimension` appears in all three groups because the question is the same in each format, but the tool that answers it is different, and the gem has to dispatch to the right one based on what kind of file it is looking at. That dispatch is why the dependency situation is per-validator rather than per-gem, and why a deployment that enables only `content_type` and `size` needs no system binaries at all, while one that enables `dimension` for PDFs needs poppler specifically.
The `content_type`, `size`, `total size`, `limit` and `attached` validators need nothing external. They read what Active Storage already knows about the blob.
This is the practical way to scope an adoption. Start with the checks that need no system tools, since they carry no operational cost, and treat each media family you add as a separate infrastructure decision with its own binary, its own timeout and its own CI image change.
The repository layout supports the same division. There is a `config/` directory, a `benchmark/` directory holding the baseline comparison, a `docs/` directory, `gemfiles/` for testing against different dependency sets, `spec/` and `test/` directories, a `Rakefile`, a `.rubocop.yml` with a `.rubocop_todo.yml`, an `AGENTS.md` and a `CHANGES.md`. There is also a `build.sh` at the root.
Version 4 is a deliberate compatibility line
The release history shows a project that has just made a significant compatibility move, and the version numbers tell the story.
There are three releases in view: 4.0.0 on 2026-08-03, 4.1.0 on 2026-08-19 and 4.1.1 on 2026-09-12. The jump to 4.0.0 in August 2026 and the requirement stated in the documentation for Rails 7.0.1 onwards and Ruby 3.3 onwards belong to the same change, because dropping support for older Rubies is exactly the kind of change that a major version exists to signal.
The repository is not archived and the last push was on 2026-10-09, so the work is continuing, and a patch release followed the minor one within a month, which is the pattern of a project settling after a major bump.
There is a `CHANGES.md` at the root for the version history, and the README has an Upgrading section in its table of contents, which is where you would look before moving a major version in an existing application.
One structural detail worth knowing is the `asv_*` metadata cache mentioned earlier, because it is version-sensitive. If a caching scheme changes between major versions, the cached values your application already has may not be read by the new version, which changes when analysis happens and therefore how long a request takes. The documentation does not describe a cache migration path, so this is something to check in the upgrading notes rather than assume.
The maintainer runs a Patreon and a Buy Me A Coffee link in the README, and the README links to the author's RailsJazz site.
Editorial conclusion
active_storage_validations fits a Rails team that accepts user file uploads and needs the server to enforce the same constraints the browser-side file input advertises, in a form that reads like the rest of the model. It does not fit a deployment that cannot install system binaries, since the image, video, audio, PDF and spoofing checks all depend on command line tools being present, and it does not fit a team that treats client-side hints as sufficient. Verify first that the command line tools your chosen validators need are present in your CI and production images, that the system binaries match the versions you tested locally, since the gem's own CI workflow is the reference for that, and that the command timeout setting is appropriate, because every analyzer invocation is a subprocess and a hung `ffprobe` will block the request.
Frequently asked questions
How do I install active_storage_validations in a Rails app?
Add `gem 'active_storage_validations'` to the Gemfile and run `bundle`. The gem requires Rails 7.0.1 onwards and Ruby 3.3 onwards, and its I18n error messages are added to the application automatically on install.
Which validators need extra system tools to run?
The image validators `dimension`, `aspect_ratio` and `processable_file` need `libvips` with `ruby-vips` or ImageMagick with `mini_magick`. The video and audio validators add `duration` and `with_audio` and need `ffmpeg`. The PDF validators add `pages` and need `poppler`.
How does the content type spoofing protection work?
The `spoofing_protection` option on the `content_type` validator runs an external analyzer to determine the file's real type. The default backend uses the UNIX `file` command, and the `:magika` backend uses the Google Magika CLI, which the documentation recommends as generally more accurate, especially on textual or ambiguous formats.
What is the command_timeout setting for in active_storage_validations?
It bounds every external analyzer command, covering ffprobe, pdfinfo, file, magika, ImageMagick identify and libvips. The default is 10 seconds, and setting it to nil disables the limit. The gem also emits a `timeout.active_storage_validations` notification whenever a command exceeds its deadline.
Is there a way to see how long file analysis takes?
The gem emits an `analyze.active_storage_validations` notification every time an analyzer command runs, with `analyzer`, `command`, `timeout`, `timed_out` and `duration` in the payload. The configuration example shows how to subscribe to it and log the payload through Rails.logger.
Official sources
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.
[](https://hysenlabs.com/projects/igorkasyanchuk-active-storage-validations)Was this useful?