Getting Started
Guides
Dependency Management
- How to manage application dependencies with Bundler
- How to manage dependencies with Bundler
- How to update gems with Bundler
- How to manage groups of gems
- How to install gems from git repositories
- How to develop multiple gems in one repository
- How to use Bundler with Ruby
- How to use Bundler in a single-file Ruby script
- How to deploy bundled applications
Gem Development
Publishing & Security
- Trusted Publishing
- Setting up multi-factor authentication
- Using multi-factor authentication in command line
- Managing owners using UI
- Organizations
- Removing a Published gem
- Security Practices
- How to delay new gem versions with cooldown
Integrations
- How to use Bundler with Rails
- How to use Bundler with Sinatra
- How to use Bundler with Docker
- How to use Bundler in CI
Hosting & Sources
Extending
Troubleshooting
- Troubleshooting common issues
- How to troubleshoot RubyGems and Bundler TLS/SSL Issues
- How to use git bisect with Bundler
Concepts
- What is a gem?
- Gemfile and gemspec
- Where gems are installed and how they load
- How dependency resolution works
- How Gemfile.lock works
- Versioning and compatibility
- Platforms and native gems
- Caching and vendoring
- Default gems and bundled gems
- Common Vulnerabilities and Exposures
Reference
- gem Command Reference
- Bundler Command Reference
- Gemfile Reference
- Ruby Directive
- Specification Reference
- RubyGems.org API
- RubyGems.org Compact Index API
- RubyGems.org rate limits
- API key scopes
- Bundler compatibility with Ruby
- Configuration (.gemrc)
- Environment variables
- Known Bundler Plugins
Appendix
How to write a Bundler plugin
Extend Bundler with new commands, gem sources, and lifecycle hooks.
A Bundler plugin is a regular gem with one extra file, plugins.rb, at its root. Through that file the gem can register three kinds of extensions:
- Commands, so that
bundle my_commandruns your code - Gem sources, so that a Gemfile can install gems from somewhere other than a gem server, git, or a local path
- Lifecycle hooks, so that your code runs at events such as before or after
bundle install
Installing and using plugins
Plugins install from a gem server by default, or from a git repository or local path:
bundle plugin install my_plugin
bundle plugin install my_plugin --git https://github.com/example/my_plugin
bundle plugin install my_plugin --path /path/to/my_plugin
Once installed, the plugin’s commands are available and its hooks are registered. bundle plugin list shows installed plugins and their commands, and bundle plugin uninstall my_plugin removes one.
A Gemfile can also declare plugins, and bundle install will install them:
plugin "my_plugin"
plugin "my_plugin", git: "https://github.com/example/my_plugin.git"
plugin "my_plugin", path: "/path/to/my_plugin"
A plugin is a regular gem
Start by creating a gem as usual. That guide builds a command-line executable with Thor, but a plugin needs none of that. No executable, no CLI framework. Bundler talks to your plugin through plugins.rb instead.
plugins.rb lives at the top level of the gem, next to the gemspec, and is the entry point Bundler loads. Usually it just requires your gem’s main file:
require "my_plugin"
Make sure the gemspec ships this file. If spec.files is a hand-maintained list rather than git ls-files, add plugins.rb to it.
When the plugin is installed, Bundler runs plugins.rb once and records every command, source, and hook it registers into a plugin index. After that, Bundler loads the plugin again only when one of those registrations is used. Registration must therefore happen at load time, in code that runs when plugins.rb is required.
Adding a command
A command class needs two things: it registers itself for a command name, and it defines an instance method exec. The smallest working command plugin looks like this, with the class reached from plugins.rb:
require "bundler/plugin/api"
module MyPlugin
class Hello < Bundler::Plugin::API
command "hello"
def exec(command, args)
puts "Hello! You passed #{args.inspect}"
end
end
end
When a user runs bundle hello world --loud, Bundler instantiates the registered class with no arguments and calls exec("hello", ["world", "--loud"]). The second argument is the raw list of remaining command-line arguments. Parse it however you like, for example with OptionParser as bundler-graph does.
Bundler routes only on the first word after bundle. Subcommands such as bundle hello status are yours to implement by dispatching on args[0].
If you prefer not to inherit from Bundler::Plugin::API, register a plain class explicitly. It must still be a class with a public exec instance method, because Bundler calls .new on whatever you register:
require "bundler/plugin/api"
module MyPlugin
class Hello
Bundler::Plugin::API.command("hello", self)
def exec(command, args)
puts "Hello! You passed #{args.inspect}"
end
end
end
Raising errors
When something goes wrong, raise Bundler::BundlerError (or a subclass). Bundler rescues it and prints the message concisely. Any other exception makes Bundler print its bug report template asking users to file an issue against Bundler itself. The details are in friendly_errors.rb.
raise Bundler::BundlerError, "my_command requires an argument" if args.empty?
Commands and Thor
If your gem already has a Thor CLI, do not register the Thor class itself as the command. Thor classes define no exec instance method, so Bundler’s call lands on the private Kernel#exec and the command crashes with NoMethodError: private method 'exec' called. Keep the Bundler command in its own small class and delegate to Thor from there:
module MyPlugin
class BundlerCommand < Bundler::Plugin::API
command "my_command"
def exec(command, args)
MyPlugin::CLI.start(args)
end
end
end
MyPlugin::CLI.start(args) here is the same entry point the gem’s own executable would use. Delegating to Thor also gives you subcommands: bundler-sbom registers the single command sbom and delegates to a Thor class with dump and license tasks, which is what makes bundle sbom dump work. Thor receives ["dump", ...] and dispatches as usual.
Plugin commands vs. executables on PATH
There is a second, older way to add a bundle subcommand that has nothing to do with plugins. When bundle foo matches neither a built-in command nor an installed plugin command, Bundler searches PATH for an executable named bundler-foo and runs it. bundler-audit works this way: installing the gem puts a bundler-audit executable on PATH, which makes bundle audit work. It is not a Bundler plugin and does not use the plugin API.
The two mechanisms differ in how they are installed and where they run. A PATH executable comes from a gem installed with gem install or a Gemfile, runs in its own process, and does not appear in bundle plugin list. A plugin command is installed with bundle plugin install, runs inside the Bundler process with access to Bundler’s API, and is listed by bundle plugin list. If both exist for the same name, the plugin command wins. For a new project, prefer the plugin API. Adding command registration to a gem that already ships a bundler-prefixed executable changes nothing for its users except the installation method.
Running code at lifecycle events
Hooks run your code when Bundler reaches specific events. Register a hook with the event name and a block. The block arguments depend on the event:
require "bundler/plugin/api"
Bundler::Plugin::API.hook("before-install-all") do |dependencies|
puts "About to install #{dependencies.map(&:name).join(", ")}"
end
The full list of events, with their descriptions and block arguments, is in events.rb. Check the copy in the Bundler version you target, since events have been added over time. A hook registered for an event the running Bundler does not define raises an error at plugin install time.
For real-world examples, bundler-multilock uses an after-install-all hook, and bundler-timing-plugin times fetches and installs by registering several hooks in plugins.rb that share one tracker object.
Adding a gem source
A source plugin lets a Gemfile install gems from a place Bundler does not support natively, such as Amazon S3. Subclass Bundler::Plugin::API::Source and override at least fetch_gemspec_files and install. The required and overridable methods are documented in api/source.rb.
Bundler’s own sources implement the same interface, so their code is a useful reference: the rubygems source, the git source, and the path source.
Developing your plugin locally
Install your work-in-progress plugin straight from its source directory:
bundle plugin install my_plugin --path /path/to/my_plugin
Run this inside a project with a Gemfile and the plugin installs into the project’s .bundle/plugin directory, keeping the experiment local. Run it outside any project and the plugin installs globally for your user.
A path-installed plugin runs directly from the source directory, so edits to your code take effect on the next bundle invocation. The exception is plugins.rb registrations, which Bundler caches in its index at install time. After adding or renaming a command, source, or hook, reinstall:
bundle plugin uninstall my_plugin
bundle plugin install my_plugin --path /path/to/my_plugin
Releasing your plugin
A plugin is released like any other gem. Publish it to RubyGems.org so others can install it with bundle plugin install.
Example plugins
- bundler-graph adds a command. It is maintained by the rubygems organization and is a good reference for the command API.
- bundler-sbom delegates its command to a Thor CLI with subcommands.
- bundler-multilock uses a lifecycle hook.
- bundler-timing-plugin registers multiple hooks that share state.
- Bundler’s built-in rubygems, git, and path sources implement the source interface.
More are listed in the known plugins list.