Library / SDK
rails/propshaft avatar
rails/propshaft

Propshaft: the Rails asset pipeline that does less on purpose

Deliver assets for Rails

1,047 stars122 forksRubyMIT

At a glance

What is it?
Propshaft is the asset pipeline Rails 8 ships by default, and its whole argument is subtraction: no transpiling, no concatenation, no requirement that every asset be explicitly included, just a load path, digest stamping and a development server. What it gives up is Sprockets' preprocessing, and the documentation is blunt about who that hurts.
Who is it for?
Propshaft fits a Rails team that already compiles its JavaScript and CSS with a Node based bundler and wants the asset pipeline to stop being a second build system, or that is starting a new Rails 8 application and would rather not configure Sprockets at all.
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 51 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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The premise: bundling is no longer the point

Propshaft is an asset pipeline library for Rails, and the README opens by explaining the era it was designed for. Bundling assets to save on HTTP connections is no longer urgent, JavaScript and CSS are either compiled by dedicated Node.js bundlers or served directly to the browsers, and increases in bandwidth have made the need for minification less pressing. Those factors allow for a dramatically simpler and faster asset pipeline compared to previous options like Sprockets.

That is a design brief written as a list of things that stopped being true, and each entry is a feature that Propshaft therefore does not have. It does not bundle. It does not minify. It does not transpile.

So what it does provide is four things, and the list in the README is short enough to be a complete specification.

A configurable load path. You can register directories from multiple places in your app and in gems, and reference assets from all of these paths as though they were one.

Digest stamping. All assets in the load path are copied, or compiled, in a precompilation step for production, and that step also stamps all of them with a digest hash, so you can use long-expiry cache headers for better performance. The digested assets can still be referred to through their logical path, because the processing leaves a manifest file that provides a way to translate.

A development server. There is no need to precompile assets in development. You refer to them via the same asset_path helpers and they are served by a development server.

And basic compilers. Propshaft was explicitly not designed to provide full transpiler capabilities, and the documentation says you can get that better elsewhere. But it does offer a simple input to output compiler setup, which by default is used to translate url(asset) function calls in CSS to url(digested-asset) instead, and source mapping comments likewise.

That last one is the whole of the compilation story, and understanding it is what makes the rest make sense. Propshaft is not a compiler, it is a copier with a naming scheme and a small rewriting step bolted on for the CSS case.

The primary language is Ruby, the licence is MIT, and the recent releases are v1.3.2 from 2026-04-17, v1.3.0 from 2025-09-23 and v1.2.1 from 2025-07-18. The repository is not archived, with the last push on 2026-09-02.

The load path, and the behaviour change from Sprockets

The Usage section contains the single most important difference from Sprockets, and it is easy to miss because it is stated as a fact rather than as a warning.

Propshaft makes all the assets from all the paths it has been configured with, through config.assets.paths, available for serving, and copies all of them into public/assets when precompiling. This is unlike Sprockets, which did not copy over assets that had not been explicitly included in one of the bundled assets.

So the change is not that Propshaft is more selective. It is the opposite. Everything on the path gets served, whether or not you referenced it. In a Sprockets application, the set of assets that reached public/assets was the set you had bundled, which is a small curated list. In a Propshaft application, the set is the set of files in the load path.

That has an upside and a downside. The upside is that you do not have to declare what you use, so a gem that ships assets and a helper that references a file you forgot to include both just work. The downside is that anything in a load path is public, including files that were previously invisible because nothing bundled them, and it is also more files in public/assets.

The escape valve is config.assets.excluded_paths, and the documentation gives the exact motivating case. It is useful if you are using app/assets/stylesheets exclusively as a set of inputs to a compiler like Dart Sass for Rails, and you do not want these input files to be part of the load path. The parenthetical is important: you need to add full paths, like Rails.root.join("app/assets/stylesheets").

That last detail is a real trap. An excluded path that is not exactly the path Rails would have computed will silently not exclude anything, and you will end up with your Sass input files published to the web.

The path configuration is where gems come in, since the load path can be extended by gems as well as by the application. The README notes the load path is configurable from multiple places in your app and gems, and the effect is that all of them are addressed as one namespace, so a helper in a gem can reference an asset that ships in another gem.

The reference mechanism itself is the standard Rails one. Assets are referenced through their logical path using the normal helpers, asset_path, image_tag, javascript_include_tag, and all the other asset helper tags, and those logical references are automatically converted into digest-aware paths in production when assets:precompile has been run, through a JSON mapping file found in public/assets/.manifest.json. So the application code never mentions a digest, and the digest is a deployment detail.

Referencing digested assets from CSS and from JavaScript

The reference handling is where the differences between the two languages show, and the asymmetry is instructive.

For CSS, Propshaft handles it automatically. It will convert asset references in CSS to use the digested file names, so background: url("/bg/pattern.svg") is converted to background: url("/assets/bg/pattern-2169cbef.svg") before the stylesheet is served. That works because the CSS is a file Propshaft has in its load path, and it processes it on the way through.

For JavaScript, you have to trigger the transformation manually, and the mechanism is a pseudo-method called RAILS_ASSET_URL:

javascript
export default class extends Controller {
  init() {
    this.img = RAILS_ASSET_URL("/icons/trash.svg")
  }
}

That turns into:

javascript
export default class extends Controller {
  init() {
    this.img = "/assets/icons/trash-54g9cbef.svg"
  }
}

The reason for the asymmetry is straightforward once you see it. The CSS is processed by Propshaft's own pipeline, so Propshaft can rewrite it. A JavaScript file is likely to be built by a Node bundler before Propshaft ever sees it, or to be served directly, so Propshaft has no opportunity to rewrite it and instead provides a function the JavaScript calls at run time, which the client side resolves.

That has a consequence for how you structure things. If a JavaScript file is bundled by your own tool, the RAILS_ASSET_URL call is inside your source and your bundler may transform or tree-shake it, so the mechanism only works if your bundler leaves the call intact. If the JavaScript is served directly and Propshaft processes it, the substitution is done at serve time.

The bypass mechanism is the other half. If you need to put multiple files that refer to each other through Propshaft, like a JavaScript file and its source map, you have to digest these files in advance to retain stable file names. Propshaft looks for the specific pattern of -[digest].digested.js as the postfix to any asset file as an indication that the file has already been digested.

That postfix convention is the mechanism, and it is a naming convention rather than a flag. If you pre-digest files yourself, you name them with that pattern and Propshaft will not digest them again, so the references between them stay valid. The alternative would be for Propshaft to rewrite the map reference, which it does not attempt to do.

Migrating from Sprockets, and who should not bother

The last section of the README is the migration guidance, and it is unusually honest about the fact that this is a one way door for some projects.

Propshaft does a lot less than Sprockets, by design, so it might well be a fair bit of work to migrate if it is even desirable. That is particularly true if you rely on Sprockets to provide any form of transpiling, like CoffeeScript or Sass, or if you rely on any gems that do. The fix is to either stop transpiling or to use a Node-based transpiler, like those in jsbundling-rails and cssbundling-rails.

So the decision rule is short. If you are already bundling JavaScript and CSS through a Node-based setup, then Propshaft is going to slot in easily, because the thing you lose, transpiling, is not something you were using Propshaft for in the first place. If you were using Sprockets as your Sass compiler, then you have to change your build before you can change your pipeline, and the migration is the bundler move rather than the asset pipeline move.

The repository layout backs up that this is a well-run Rails component rather than a side project. There is a Rakefile, a Gemfile and Gemfile.lock, a propshaft.gemspec, a .rubocop.yml, a bin/ directory, a test/ directory, a benchmarks/ directory, a gemfiles/ directory for testing against multiple dependency sets, a .devcontainer/ for a reproducible development environment, and an UPGRADING.md at the root.

The benchmarks/ directory is worth a second look, because a library whose entire claim is that it is faster than the alternative has an interest in measuring that claim, and shipping the benchmarks lets you check it rather than take it on faith. The gemfiles/ directory is the other useful detail, since it is how the library is tested against several Rails and Ruby combinations, which is the part that tends to break silently in a pipeline gem.

The licence is MIT, under a file named MIT-LICENSE rather than LICENSE, and the project is versioned with plain semantic versioning, v1.3.2 being the most recent release on 2026-04-17. Unlike most of the libraries that get a weekly prerelease line, Propshaft is on a stable 1.x line with a moderate release cadence, which is the right choice for something every Rails 8 application depends on.

One thing the README does not discuss is how Propshaft handles assets that need to be transformed in ways beyond the CSS url rewriting, such as a bundler that outputs into the load path. The answer in practice is that the Node bundler writes to a directory you add to the load path, and Propshaft then digests and serves what it finds there, which is a simple arrangement but one the documentation leaves to the reader to work out.

Subresource Integrity, and the dev server's file watching

Two features round out the pipeline, and they are the ones you would not design yourself but will be glad to have.

Subresource Integrity is supported, and it works by letting browsers verify that resources fetched from CDNs or other sources have not been tampered with, by checking cryptographic hashes. It is enabled by configuring the hash algorithm in the Rails application:

ruby
config.assets.integrity_hash_algorithm = "sha384"

The valid algorithms are sha256, described as the most common, sha384, described as recommended for enhanced security, and sha512, described as the strongest. Once configured, you enable it per helper call with the integrity: true option:

erb
<%= stylesheet_link_tag "application", integrity: true %>
<%= javascript_include_tag "application", integrity: true %>

And the generated HTML carries the hash:

html
<link rel="stylesheet" href="/assets/application-abc123.css"
      integrity="sha384-xyz789...">
<script src="/assets/application-def456.js"
        integrity="sha384-uvw012..."></script>

The important caveat is at the end of that section. SRI only works in secure contexts, meaning HTTPS, or during local development. The integrity hashes are automatically omitted when serving over HTTP in production, for security reasons. So enabling SRI costs nothing and helps, but it does nothing for a production deployment that is not on HTTPS, and that is worth checking before you assume your assets are being verified.

There is also a bulk inclusion extension. Propshaft extends stylesheet_link_tag with special symbols, so :all includes all stylesheets and :app includes only the app/assets ones, both with integrity: true working as expected. That is a small convenience for the case where you want every stylesheet with integrity attributes without listing them.

The development server has its own cost, and it is the one that shows up in day to day work. Before every request, Propshaft checks if any asset was updated to decide if a cache sweep is needed, and this verification is done using the application's configured file watcher, which by default is ActiveSupport::FileUpdateChecker.

If you have a lot of assets in your project, the documented fix is to add the listen gem to the development group in your Gemfile, and this line to the development.rb environment file:

ruby
config.file_watcher = ActiveSupport::EventedFileUpdateChecker

The default checker polls; the evented one is driven by filesystem notifications. On a large asset tree, polling every file before every request is the kind of thing that makes a dev server feel broken, and the fix is a two line change. It is the most useful piece of performance advice in the entire README, and it is buried at the bottom under a heading about development.

Editorial conclusion

Propshaft fits a Rails team that already compiles its JavaScript and CSS with a Node based bundler and wants the asset pipeline to stop being a second build system, or that is starting a new Rails 8 application and would rather not configure Sprockets at all. It is the wrong choice if you rely on Sprockets for CoffeeScript or Sass compilation, since the documentation says outright that migrating in that case is a fair bit of work, and the wrong choice if you depend on assets being included only when referenced. Before migrating, read UPGRADING.md, and check the load path against your gem dependencies, because the change from Sprockets is that every asset on the path is now served rather than only the ones you named.

Frequently asked questions

What is Propshaft and how is it different from Sprockets?

Propshaft is the asset pipeline library for Rails, the default in Rails 8. It provides a configurable load path, digest stamping, a development server and a simple compiler setup, and it does not bundle, minify or transpile. Unlike Sprockets, it copies every asset in the load path rather than only the ones explicitly included in a bundle.

How do I install Propshaft in a Rails application?

With Rails 8 it is the default asset pipeline for new applications. With Rails 7 you can start a new application with propshaft using rails new myapp -a propshaft, and for existing applications the upgrade guide in the UPGRADING.md file contains step by step instructions.

How do I reference a digested asset from JavaScript in Propshaft?

You have to trigger the transformation manually with the RAILS_ASSET_URL pseudo-method, so this.img = RAILS_ASSET_URL("/icons/trash.svg") becomes this.img = "/assets/icons/trash-54g9cbef.svg". In CSS the conversion happens automatically, so background: url("/bg/pattern.svg") becomes a digested path before the stylesheet is served.

Does Propshaft support Subresource Integrity?

Yes. Set config.assets.integrity_hash_algorithm to sha256, sha384 or sha512 and pass integrity: true to the asset helpers. The hashes are automatically omitted when serving over HTTP in production, because SRI only works in secure contexts or during local development.

Official sources

  1. Issues
  2. License: MIT
  3. rails/propshaft on GitHub
  4. README
  5. Releases
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/rails-propshaft.svg)](https://hysenlabs.com/projects/rails-propshaft)

Was this useful?