4.8 Aspects
A command like this looks as if it is building one target:
bazel build //pkg:app --aspects=//tools/lint:linters.bzl%ruff_aspect
But it is really asking Bazel for two things at once. Build //pkg:app as usual, and also run extra analysis-time logic over the dependency graph that //pkg:app reaches. The targets did not opt in through their BUILD files. Their rules did not grow linter-specific attributes. The aspect is layered over the graph for this request.
That is the first mental turn in this section. Aspects are not a prettier way to define targets, and they are not a post-build script that crawls the filesystem. Bazel applies them while it is analyzing the requested target graph: their implementations can read providers, register actions, and return providers or output groups as part of that analysis. They are not a layer that arrives after ordinary analysis has finished.
The Overlay Question
The core design question is: who owns this concern, and should that ownership be visible in every rule that participates?
If the rule author owns the concern, put it in the rule. A generated output, a provider, a runfiles contract, or a validation action should be part of the target's normal analysis result when every consumer must see it. If the infrastructure team owns the concern and it cuts across many existing rules, an aspect keeps that ownership separate. The rule still describes the target. The aspect describes the overlay applied to that target graph for a tool, CI job, IDE driver, or migration pass.
4.8.1 Aspects — Cross-Cutting Graph Traversal gives the shadow-graph model behind that split. A rule implementation defines what one configured target means: attributes in, providers and declared actions out. An aspect implementation asks what extra information or actions should exist if the concern is applied to target X and then to selected reachable targets. 4.8.2 Aspect Implementation Basics turns that model into the Starlark API: aspect(), (target, ctx), propagation attributes, provider filters, private tool attrs, public parameters, output groups, and invocation forms.
The Use Cases Are Ownership Tests
4.8.3 Validation Actions vs Aspects is the first design fork. If a check is intrinsic to a rule's correctness, a validation action belongs inside the rule. If it is project-wide and must layer onto existing rules, an aspect is the natural deployment shape. This is where the code-quality material from 3.4.4 Code Quality Integration connects to rule authoring.
4.8.4 Large-Scale Refactoring via Aspects and 4.8.5 Language Server Integration Architecture show why the overlay is worth learning. Refactoring aspects let a transformation tool run across a whole repository while Bazel and 6.3 Remote Execution Infrastructure handle decomposition and scheduling. LSP aspects project provider data into the shape editor tooling understands, instead of asking the IDE to guess from the filesystem. That connects back to 3.5.1 IDE Support and the Starlark tooling loop in 4.3.4 LSP / IDE Integration.
4.8.6 Dynamic Aspect Propagation and 4.8.7 shadowed_action — Action Composition are edge controls, not the default shape. Dynamic propagation is for cases where a static list like attr_aspects = ["deps"] cannot express the traversal. Shadowed actions are for narrow sidecar cases where an action should inherit the input envelope of an existing action. Most useful aspects remain simpler: propagate over known attrs, read providers, write optional outputs, and expose results through output groups.
Why It Feels Like A Rule But Is Not One
Aspects use implementation functions, providers, actions, attrs, and output groups, so they feel like rules at first. But the rule application and the aspect application share a target without sharing ownership of the target's contract. The aspect can add an overlay. It cannot pretend to be the underlying rule.
That contract split explains the API details that otherwise look odd. Aspect results should escape through providers and output groups because they are additional surfaces, not mutations of the underlying target. required_providers and propagation attributes scope the overlay to the graph that actually carries the concern. Public parameters and private tool attrs give the aspect a controlled API. Command-line invocation and rule-propagated invocation are different ways to request the same explicit overlay, not ways to make every visited rule absorb it.
Read 4.8.1 Aspects — Cross-Cutting Graph Traversal and 4.8.2 Aspect Implementation Basics together before jumping to use cases. If you cannot explain what A(X) means, or why ctx.rule.attr.deps contains deps with the aspect already applied, the later articles will feel like recipes rather than design tools.
After that, choose by the problem in front of you. If you are integrating linters, policy checks, or quality gates, go next to 4.8.3 Validation Actions vs Aspects. If you are building editor support or a language-tooling adapter, read 4.8.5 Language Server Integration Architecture. If the work is a recurring repository-wide transformation, 4.8.4 Large-Scale Refactoring via Aspects is the motivating pattern, but it is safe to skip on a first pass through Level 4.
Decide: A metadata tool must inspect many existing target kinds across dependency edges. Without a new mechanism, you would modify every participating rule, add wrapper targets, or reconstruct Bazel's graph externally. Should the concern live in an aspect or in the rules themselves?
Reveal
Use an aspect when the metadata is an orthogonal overlay: it should traverse selected attrs, read existing providers, and publish additional providers or output groups without changing each target's own meaning. Scope the traversal with propagation attrs and provider requirements.
Put the behavior in the rule when every instance of that target kind should own it as part of its normal contract. An aspect should avoid duplicating a rule's intrinsic outputs or semantics merely because it can visit the graph.
The mini-ruleset's glyph_metadata_aspect is a runnable overlay that follows deps and exports and publishes a transitive output group.
An aspect is Bazel's cross-cutting overlay over an existing target graph. It walks a shadow graph through selected attributes, reads the providers and rule attributes of each visited target, and returns its own providers or output groups without changing the rule-defined target underneath. Use it when the concern belongs beside many rules rather than inside one rule.
Sections in this chapter · 7
Cross-cutting logic traversing the dependency graph sideways for IDE, linting, and metadata.
Defining aspect() with an implementation function, propagation edges, attrs, and providers.
Decision framework for integrating linters and formatters — aspects vs validation actions.
Wrapping transformation tools like OpenRewrite as aspect-driven actions for RBE parallelism.
Layered LSP architecture with Bazel-specific drivers using query and aspects.
Per-target propagation decisions via function-form attr_aspects/toolchains_aspects and propagation_predicate.
shadowed_action — Action CompositionextraInherit inputs and environment from another action for aspect-driven augmentation.