Skip to main content
Version: 2.11 (deprecated)

Plugin upgrade guide

How to adjust for changes made to the Plugin API.


See for the changelog.

Deprecated Subsystem.register_options()

Pants 2.11 added "concrete" option types which when used as class attributes of your subsystem. These are more declarative, simplify accessing options, and work with MyPy!


class MySubsystem(Subsystem):
options_scope = "example"
help = "..."

def register_options(cls, register):


class MySubsystem(Subsystem):
options_scope = "example"
help = "..."

my_opt = BoolOption(

To access an option in rules, simply use my_subsystem.my_opt rather than my_subsystem.options.my_opt.

See Options and subsystems for more information, including the available types.

Moved BinaryPathRequest to pants.core.util_rules.system_binaries

The new module pants.core.util_rules.system_binaries centralizes all discovery of existing binaries on a user's machines.

The functionality is the same, you only need to change your imports for types like BinaryPathRequest to pants.core.util_rules.system_binaries rather than pants.engine.process.

Deprecated not implementing TargetGenerator in GenerateTargetsRequest implementors

See for an explanation and some examples of how to fix.

Replaced GoalSubsystem.required_union_implementations with GoalSubsystem.activated()

See for an explanation and some examples of how to fix.


See for the changelog.

Rename LintRequest to LintTargetsRequest

Pants 2.10 added a new LintFilesRequest, which allows you to run linters on code without any owning targets!

To improve clarity, we renamed LintRequest to LintTargetsRequest.

FmtRequest, CheckRequest, and LintTargetsRequest must set name

You must set the class property name on these three types.


class MyPyRequest(CheckRequest):
field_set_type = MyPyFieldSet


class MyPyRequest(CheckRequest):
field_set_type = MyPyFieldSet
name = "mypy"

This change is what allowed us to add the lint --only=flake8 feature.

For DRY, it is a good idea to change the formatter_name, linter_name, and checker_name in FmtResult, LintResults, and CheckResults, respectively, to use rather than hardcoding the string again. See for examples.

Removed LanguageFmtTargets for fmt

When setting up a new language to be formatted, you used to have to copy and paste a lot of boilerplate like ShellFmtTargets. That's been fixed, thanks to

To fix your code:

  1. If you defined any new languages to be formatted, delete the copy-and-pasted LanguageFmtTargets code.
  2. For every formatter, change the UnionRule to be UnionRule(FmtRequest, BlackRequest), rather than UnionRule(PythonFmtRequest, BlackRequest), for example.

ReplImplementation now passes root targets, not transitive closure

We realized that it's useful to let REPL rules know what was specified vs. what is a transitive dependency:

To adapt to this, you will want to use transitive_targets = await Get(TransitiveTargets, TransitiveTargetsRequest(request.addresses), then operate on transitive_targets.closure.

Removed PexFromTargetsRequest.additional_requirements

Let us know if you were using this, and we can figure out how to add it back:

Removed PexFromTargetsRequest(direct_deps_only: bool)

Let us know if you were using this, and we can figure out how to add it back:

Renamed GenerateToolLockfileSentinel.options_scope to resolve_name

See for more info.

Renamed PythonModule to PythonModuleOwnersRequest

This type was used to determine the owners of a Python module. The new name makes that more clear. See


See for the changelog.

Deprecated RuleRunner.create_files(), .create_file() and .add_to_build_file()

Instead, for your RuleRunner tests, use .write_files(). See for some examples.


See for the changelog.

Target modeling changes

Pants 2.8 cleaned up the modeling of targets. Now, there are targets that describe the atom of each language, like python_test and python_source which correspond to a single file. There are also target generators which exist solely for less boilerplate, like python_tests and python_sources.

We recommend re-reading Targets and BUILD files.


The Sources class was replaced with SourcesField, SingleSourceField, and MultipleSourcesField.

When defining new target types with the Target API, you should choose between subclassing SingleSourceField and MultipleSourcesField, depending on if you want the field to be source: str or sources: list[str].

Wherever you were using Sources in your @rules, simply replace with SourcesField.

Renames of some Sources subclasses

You should update all references to these classes in your @rules.

  • FilesSources -> FileSourceField
  • ResourcesSources -> ResourceSourceField
  • PythonSources -> PythonSourceField


The method OutputPathField.value_or_default() no longer takes Address as an argument.


See for the changelog.

Type hints work properly

Pants was not using PEP 561 properly, which means that MyPy would not enforce type hints when using Pants APIs. Oops! This is now fixed.

Options scopes should not have _

For example, use my-subsystem instead of my_subsystem. This is to avoid ambiguity with target types.


See for the changelog.


ProcessCacheScope.NEVER was renamed to ProcessCacheScope.PER_SESSION to better reflect that a rule never runs more than once in a session (i.e. a single Pants run) given the same inputs.

ProcessCacheScope.PER_RESTART was replaced with ProcessCacheScope.PER_RESTART_ALWAYS and ProcessCacheScope.PER_RESTART_SUCCESSFUL.


Now called InterpreterConstraints and defined in pants.backend.python.util_rules.interpreter_constraints.


See for the changelog.


BoolField.value is no longer bool | None, but simply bool. This means that you must either set required = True or set the default.

Use TriBoolField if you still want to be able to represent a trinary state: False, True, and None.

Added RuleRunner.write_files()

This is a more declarative way to set up files than the older API of RuleRunner.create_file(), .create_files(), and .add_to_build_files(). See Testing plugins.


See for the changelog.

PexRequest changes how entry point is set

See Instead of setting entry_point="pytest" in the PexRequest constructor, now you set main=ConsoleScript("black") or main=EntryPoint("pytest").

Must use EnvironmentRequest for accessing environment variables

See Pants now eagerly purges environment variables from the run, so using os.environ in plugins won't work anymore.

Instead, use await Get(Environment, EnvironmentRequest(["MY_ENV_VAR"]).

For RuleRunner tests, you must now either set env or the new env_inherit arguments for environment variables to be set. Tests are now hermetic.


There were no substantial changes to the Plugin API in 2.3. See for the changelog.


See for the changelog.

PrimitiveField and AsyncField are removed (2.2.0.dev0)

Rather than subclassing PrimitiveField, subclass Field directly. Field now behaves like PrimitiveField used to, and PrimitiveField was removed for simplicity.

Rather than subclassing AsyncField or AsyncStringSequenceField, subclass Field or a template like StringField and also subclass AsyncFieldMixin:

from import AsyncFieldMixin, StringField)

class MyField(StringField, AsyncFieldMixin):
alias = "my_field"
help = "Description."

Async fields now access the raw value with the property .value, rather than .sanitized_raw_value. To override the eager validation, override compute_value(), rather than sanitize_raw_value(). Both these changes bring async fields into alignment with non-async fields.

Set the property help with Subsystems, Targets, and Fields (2.2.0.dev3)

Previously, you were supposed to set the class's docstring for the ./pants help message. Instead, now set a class property help, like this:

class MyField(StringField):
alias = "my_field"
help = "A summary.\n\nOptional extra information."

Pants will now properly wrap strings and preserve newlines. You may want to run ./pants help ${target/subsystem} to verify things render properly.


See for the changelog.

SourcesSnapshot is now SpecsSnapshot (2.1.0rc0)

The type was renamed for clarity. Still import it from pants.engine.fs.


See for the changelog.

Use TransitiveTargetsRequest as input for resolving TransitiveTargets (2.0.0rc0)

Rather than await Get(TransitiveTargets, Addresses([addr1])), use await Get(TransitiveTargets, TransitiveTargetsRequest([addr1])), from

It's no longer possible to include TransitiveTargets in your @rule signature in order to get the transitive closure of what the user specified on the command. Instead, put Addresses in your rule's signature, and use await Get(TransitiveTargets, TransitiveTargetsRequest(addresses)).

Codegen implementations: use DependenciesRequestLite and TransitiveTargetsLite (2.0.0rc0)

Due to a new cycle in the rule graph, for any codegen implementations, you must use DependenciesRequestLite instead of DependenciesRequest, and TransitiveTargetsLite instead of TransitiveTargetsRequest. Both imports are still from

These behave identically, except that they do not include dependency inference in the results. Unless you are generating for input = PythonSources, this should be fine, as dependency inference is currently only used with Python.

This is tracked by

Dependencies-like fields have more robust support (2.0.0rc0)

If you have any custom fields that act like the dependencies field, but do not subclass Dependencies, there are two new mechanisms for better support.

  1. Instead of subclassing StringSequenceField, subclass SpecialCasedDependencies from This will ensure that the dependencies show up with ./pants dependencies and ./pants dependees.
  2. You can use UnparsedAddressInputs from pants.engine.addresses to resolve the addresses:
from pants.engine.addresses import Address, Addresses, UnparsedAddressedInputs
from import Targets


addresses = await Get(Addresses, UnparsedAddressedInputs(["//:addr1", "project/addr2"], owning_address=None)

# Or, use this.
targets = await Get(
UnparsedAddressedInputs(["//:addr1", "project/addr2"], owning_address=Address("project", target_name="original")

If you defined a subclass of SpecialCasedDependencies, you can use await Get(Addresses | Targets, UnparsedAddressInputs, my_tgt[MyField].to_unparsed_address_inputs()).

(Why would you ever do this? If you have dependencies that you don't treat like normal—e.g. that you will call the equivalent of ./pants package on those deps—it's often helpful to call out this magic through a dedicated field. For example, Pants's archive target type has the fields files and packages, rather than dependencies.)

package implementations may want to add the field output_path (2.0.0rc0)

All of Pants's target types that can be built via ./pants package now have an output_path field, which allows the user to override the path used for the created asset.

You optionally may want to add this output_path field to your custom target type for consistency:

  1. Include OutputPathField from pants.core.goals.package in your target's core_fields class property.
  2. In your PackageFieldSet subclass, include output_path: OutputPathField.
  3. When computing the filename in your rule, use my_package_field_set.output_path.value_or_default(field_set.address, file_ending="my_ext").