# Packwerk for Rails: Enforcing Package Boundaries

> Packwerk is a Ruby gem that enforces package boundaries and constant visibility in Rails applications. It fits existing monoliths that need gradual modularization, but its static analysis intentionally misses some dependencies in dynamically loaded Ruby.

**Shopify/packwerk** — Good things come in small packages.

- Repository: https://github.com/Shopify/packwerk
- Stars: 1,927 · Forks: 123
- Language: Ruby
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/shopify-packwerk

## Packwerk's Role in a Rails Monolith

Packwerk addresses a specific architectural problem in Rails applications: code groups accumulate until dependencies between those groups are difficult to see and changes have broad consequences. The project describes package boundaries as a way to control this dependency cost. Its intended users are teams working on existing Rails codebases that want clearer package ownership and more controlled constant access without an immediate rewrite.

The unit of organization is the package. Packwerk can combine groups of files into packages and define package-level constant visibility. Public constants can serve as an allowed interface, while references that cross a package boundary without permission can be reported as violations. This is particularly relevant to large applications in which directory structure alone does not communicate which dependencies are intentional.

The project also positions itself as a migration aid. Packwerk is intended to help an existing codebase become more modular without obstructing development. That does not mean enforcement begins with a perfect architecture. The available documentation points to configuration generation through `init`, followed by the separate `USAGE.md`, but it does not promise that initialization defines every package or resolves every violation. Package design remains an architectural task in the application.

## How Packwerk Reads Constant Dependencies

Packwerk performs static analysis centered on constant references. It relies on Zeitwerk conventions and the application's autoloader to resolve constants that are defined in conventionally loaded code. Once it can associate a reference with a constant, Packwerk can compare that reference against package visibility rules and report an invalid dependency.

This approach deliberately does not model the complete runtime behavior of a Rails application. Method calls are ignored, and objects passed through the application are not tracked. A runtime-only relationship may therefore remain invisible even if it makes one package behaviorally dependent on another. Packwerk's boundary is constant visibility, not a complete dependency graph of method calls, return values, or object identities.

The design prioritizes avoiding false positives, where a valid reference would be reported incorrectly, over catching every possible violation. In documentation terms, Packwerk accepts some false negatives rather than claiming complete detection from dynamic Ruby. This makes reports safer for gradual adoption, though it also means a successful check is not proof that every architectural dependency has been discovered.

Sorbet type signatures form a documented bridge to the analysis. Packwerk understands constants used in Sorbet signatures, so a method parameter expressed as a constant type can contribute information that Packwerk can evaluate. The README does not claim that Packwerk performs Sorbet's full type checking, so Sorbet should not be described as replacing Ruby analysis.

## Packwerk Installation and Initial Configuration

Add the documented gem declaration to the application's `Gemfile`:

```ruby
gem 'packwerk'
```

Install the bundle with Bundler:

```bash
bundle install
```

The reader should see Bundler resolve the gem and update the application's dependency state. The README also lists direct installation:

```bash
gem install packwerk
```

After installation, generate the Packwerk binstub:

```bash
bundle binstub packwerk
```

Then initialize Packwerk in the application:

```bash
bin/packwerk init
```

The README states that `init` generates the configuration files. It does not document the exact file names or generated contents, so those details should not be inferred. The repository includes `USAGE.md` for subsequent configuration and usage. A first real use begins with installing the gem and generating the configuration, not with assuming that all existing packages have already been declared. The application owner must then follow `USAGE.md` to describe packages and their public constants.

## Autoloading Boundaries and Known Failure Modes

The most important constraint is Zeitwerk compatibility. Packwerk can resolve only constants defined in code loaded by the application's Zeitwerk autoloader. Code loaded through an explicit `require` often does not follow Zeitwerk's conventions, so Packwerk may not resolve references to those constants. This defines which part of the application Packwerk can inspect.

Custom configuration creates another boundary. The README says Packwerk understands custom ActiveSupport inflections. However, direct custom Zeitwerk inflections and collapsed directories can confuse Packwerk and produce false positives. A team should review its Zeitwerk setup before treating Packwerk reports as authoritative. The README does not identify a flag that changes this limitation.

Packwerk is therefore strongest when application loading follows conventional Zeitwerk paths. It is less suitable when code is loaded through mechanisms outside the autoloader. A non-Rails or non-Zeitwerk application is also outside the documented Ruby gem setup. The ecosystem section mentions an experimental Rust implementation for those cases, but that separate project should not be confused with the Ruby gem's supported configuration.

The third limitation is analytical scope. A clean Packwerk result means that the checks Packwerk can perform found no reported violation. It does not prove that method calls and objects passed between packages are independent. Teams using service objects, callbacks, or runtime dispatch need to inspect those paths separately.

## Packwerk Compared with Alternative Package Arrangements

The README describes several alternatives and integrations. `packs-rails` sets up Rails autoloading, RSpec, and FactoryBot integration for packages arranged in a flat list. It is convenient for that package arrangement, but the Packwerk README recommends `Rails::Engine`s for autoloading. The difference is architectural. `packs-rails` bundles Rails integration around a flat package list, while `Rails::Engine`s provide a more direct way to define Rails components and their loading boundaries.

`packs` is a Rust implementation of Packwerk with experimental support for non-Rails, non-Zeitwerk applications. That changes both implementation language and target environment. It may be relevant when the Ruby gem's Zeitwerk dependency is the problem, but the README only describes the alternative as experimental, so it should not be presented as a drop-in replacement with equivalent guarantees.

Other ecosystem projects address reporting or editor feedback rather than replacing the core Ruby gem. `packwerk-vscode` and `packwerk-intellij` integrate violations into editors. `danger-packwerk` sends Packwerk feedback to GitHub inline pull request comments. `graphwerk` draws package dependency graphs. These tools can improve visibility after Packwerk has established boundary rules, but they do not change the fundamental limitation of Packwerk's static constant analysis.

For type-sensitive method behavior, Sorbet is complementary. The README specifically mentions Sorbet for method parameters represented through constants, while Packwerk remains focused on package visibility. Using both tools addresses different parts of the analysis problem.

## Packwerk Maintenance and License Considerations

The repository is not archived, and the last push was on 2026-08-26. Recent releases include v3.3.1 on 2026-08-26, v3.3.0 on 2026-05-06, and v3.2.3 on 2025-05-29. That release history indicates recent project activity, but it does not establish compatibility with every Rails or Zeitwerk combination an application may use.

Upgrading is not documented in the README beyond the existence of `UPGRADING.md` in the repository layout. The existence of that file gives maintainers a specific place to check for migration instructions, but the README does not describe its contents. Likewise, `CHANGELOG.md` is present, but no release-specific upgrade behavior is stated there. The actual upgrade path must be read from those repository files before changing a production dependency.

Packwerk is distributed under the MIT License. The README identifies the gem as open source under those terms. The license permits use and modification under its conditions, but this review does not provide legal advice or replace the repository's `LICENSE.md`. A team should review that file, its dependencies, and its own compliance requirements separately.

## Conclusion

Packwerk suits maintainers of Zeitwerk-enabled Rails applications who want enforceable package boundaries without redesigning the entire codebase at once. It is a poor fit for applications that rely heavily on explicit requires, direct Zeitwerk configuration, or runtime object flows. Before adoption, verify that all relevant constants follow Zeitwerk conventions, then define which dependencies are public and which violations the team is willing to resolve.

## FAQ

### What problem does Packwerk solve in a Rails application?

Packwerk enforces boundaries and package-level constant visibility. It is intended for teams modularizing existing Rails applications by grouping files into packages and restricting which constants may cross package boundaries.

### How do I install and initialize Packwerk?

Add `gem 'packwerk'` to the application's `Gemfile`, run `bundle install`, generate a binstub with `bundle binstub packwerk`, and run `bin/packwerk init`. The README says that `init` generates configuration files, while `USAGE.md` covers subsequent usage.

### What kinds of dependencies does Packwerk detect?

Packwerk analyzes static constant references. It ignores method calls and objects passed through the application, and it may understand constants used in Sorbet type signatures.

### What limitations should I check before adopting Packwerk?

Packwerk requires constants to be loaded through Zeitwerk conventions. Direct custom Zeitwerk inflections and collapsed directories can confuse it and produce false positives, while dynamic method and object relationships remain outside its analysis.

### Is Packwerk available for non-Rails or non-Zeitwerk applications?

The Ruby Packwerk gem is documented for Rails applications using Zeitwerk. The README separately mentions `packs`, a Rust implementation with experimental support for non-Rails and non-Zeitwerk applications, but it does not describe that project as a standard replacement.

## Sources

- [Issues](https://github.com/Shopify/packwerk/issues)
- [License: MIT](https://github.com/Shopify/packwerk/blob/main/LICENSE)
- [README](https://github.com/Shopify/packwerk/blob/main/README.md)
- [Releases](https://github.com/Shopify/packwerk/releases)
- [Shopify/packwerk on GitHub](https://github.com/Shopify/packwerk)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/shopify-packwerk
