---
title: "A guide to Bazel query"
date: "2025-03-28"
author: "Christian Nunciato"
description: "Learn how Bazel's query tools can help you navigate complex projects, optimize builds, and make changes confidently as your codebase grows."
tags: "CI/CD, Monorepo, Bazel"
readingTime: "11 minute read"
---

# A guide to Bazel query

Learn how Bazel's query tools can help you navigate complex projects, optimize builds, and make changes confidently as your codebase grows.

<p>First, congratulations: If you&apos;re reading this, you&apos;ve likely succeeded in getting a <a href="https://bazel.build/">Bazel</a> project set up, configured, and actually built, which, as anyone who&apos;s used Bazel before will tell you, is no easy feat. If you haven&apos;t already, take a moment to kick back with the beverage of your choice, because that alone is something to celebrate.</p><p>With your project up and running, though, you&apos;ll soon be faced with another challenge: getting your head around the Bazel dependency graph. Why does this tiny library seem to be pulling in half the internet? Why is this test taking a half-hour to run, when it should take only a few seconds? How are all of these Bazel packages related? And if I change something here, what else might break?</p><p>All of these are questions that <a href="https://bazel.build/query/language"><code>bazel query</code></a>, one of several subcommands of the Bazel CLI, can help you answer—but of course, you have to learn how to use it first. So in this post, we&apos;ll help you do that with a practical example that shows how to use <code>bazel query</code> to:</p><ul><li><p>Explore and visualize your Bazel dependency graph</p></li><li><p>Analyze and trace the relationships between packages in a Bazel workspace</p></li><li><p>Identify and troubleshoot direct, transitive, and third-party dependencies</p></li><li><p>Make informed refactoring decisions safely</p></li><li><p>Optimize your build performance by pinpointing dependency bottlenecks</p></li></ul><p>With a solid understanding of <code>bazel query</code> in your toolbox, you&apos;ll not only be able to maintain control over your Bazel-managed codebase as it grows, you&apos;ll be able to build more advanced workflows on top of it that help you get the most out of this incredibly useful tool.</p><p>Let&apos;s dive in.</p><h2>Bazel query by example: a hypothetical Go monorepo </h2><p>Throughout this guide, we&apos;ll use the shell of a hypothetical Go microservices project as an example to work from. The project&apos;s monorepo (which is <a href="https://github.com/cnunciato/bazel-query-guide-example">available on GitHub</a> if you&apos;d like to follow along and run these commands yourself—and you should!) contains a single Bazel workspace with a handful of library and service packages:</p><pre><code>//services/auth:auth         # Authentication microservice
//services/user:user         # User management microservice
//services/payment:payment   # Payment processing microservice
//lib/logging:logging        # Shared logging utilities
//lib/config:config          # Configuration management
//lib/metrics:metrics        # Metrics collection</code></pre><p>Each service is a <code>go_binary</code> target, and the libraries are all <code>go_library</code> targets shared across those services. Here, for example, is what the <code>BUILD.bazel</code> file looks like for the payment service:</p><pre data-language="python"><code># ./services/payment/BUILD.bazel
load(&quot;@rules_go//go:def.bzl&quot;, &quot;go_binary&quot;, &quot;go_library&quot;, &quot;go_test&quot;)

go_library(
    name = &quot;payment_lib&quot;,
    srcs = [
        &quot;handlers.go&quot;,
        &quot;postgres.go&quot;,
        &quot;service.go&quot;,
    ],
    importpath = &quot;bazel_query_example/services/payment&quot;,
    visibility = [&quot;//visibility:private&quot;],
    deps = [
        &quot;//lib/config&quot;,
        &quot;//lib/logging&quot;,
        &quot;//lib/metrics&quot;,
        &quot;//services/payment/db&quot;,
        &quot;@com_github_lib_pq//:pq&quot;,
    ],
)

go_binary(
    name = &quot;payment&quot;,
    embed = [&quot;:payment_lib&quot;],
    visibility = [&quot;//visibility:public&quot;],
)</code></pre><p>Now let&apos;s start exploring this codebase with <code>bazel query</code>.</p><h2>The basic query: Understanding your workspace</h2><p>The most fundamental query lists all of the targets in your Bazel workspace:</p><pre data-language="bash"><code>bazel query //...</code></pre><p>With our example, this might show a dozen or so targets, but in a larger project, it could surface hundreds or even thousands, which might not be useful. You can get more specific by narrowing for the <em>kinds</em> of targets you&apos;re looking for—for example, for a list of the Go binaries defined anywhere in the <code>services</code> package of your workspace:</p><pre data-language="bash"><code>bazel query &quot;kind(&apos;go_binary&apos;, //services/...)&quot;</code></pre><p>In our case, that&apos;d return:</p><pre><code>//services/auth:auth
//services/payment:payment
//services/user:user</code></pre><p>The <code>kind()</code> function filters targets by their rule type. In Bazel terminology, a <em>rule</em> is essentially a function that defines how to build a particular output (like a binary or a library), given some set of inputs. The <code>go_binary</code> rule compiles Go code into an executable binary.</p><h2>Exploring dependencies with <code>deps()</code></h2><p>To understand what a specific service depends on:</p><pre data-language="bash"><code>bazel query &quot;deps(//services/payment:payment)&quot;</code></pre><p>This reveals all dependencies of the <code>payment</code> service, including both direct and transitive dependencies. The output might include targets like:</p><pre><code>//lib/logging:logging
//lib/config:config
//lib/metrics:metrics
...
@bazel_tools//tools/allowlists/function_transition_allowlist:function_transition_allowlist
@bazel_tools//tools/cpp:optional_current_cc_toolchain
@bazel_tools//tools/cpp:toolchain_type
...</code></pre><p>Notice how this includes not only our internal libraries, but also external dependencies (those prefixed with <code>@</code>). The <code>deps()</code> function traverses the entire dependency graph, showing every target required to build a given service.</p><p>To focus on direct dependencies only, you can add a second <em>depth</em> parameter:</p><pre data-language="bash"><code>bazel query &quot;deps(//services/payment:payment, 1)&quot;</code></pre><p>Which might instead return only these:</p><pre><code>//services/payment:payment
//services/payment:payment_lib</code></pre><p>Notice that <code>payment_lib</code> is listed because the <code>go_binary</code> rule (from above) uses the <code>embed</code> attribute rather than <code>deps</code>. The binary embeds the library, and the library depends on everything else. To see one more level:</p><pre data-language="bash"><code>bazel query &quot;deps(//services/payment:payment, 2)&quot;</code></pre><p>Now you&apos;d see something like:</p><pre><code>//lib/config:config
...
//services/payment:payment
//services/payment:payment_lib
...
@bazel_tools//tools/allowlists/function_transition_allowlist:function_transition_allowlist
@com_github_lib_pq//:pq
@rules_go//:cgo_context_data
@rules_go//:go_config
...</code></pre><p>This layered approach to querying helps you understand the structure of your build targets without being overwhelmed by information.</p><h2>Identifying dependents with <code>rdeps()</code></h2><p>Perhaps even more powerful than understanding what a given package depends on is understanding what other packages depends on <em>it</em>. Imagine, for example, you wanted to make a change to the logging library. Which other packages in the Bazel workspace would will be affected? </p><pre data-language="bash"><code>bazel query &quot;rdeps(//..., //lib/logging:logging)&quot;</code></pre><p>This would show:</p><pre><code>//lib/logging:logging
//services/auth:auth
//services/auth:auth_lib
//services/payment:payment
//services/payment:payment_lib
//services/user:user
//services/user:user_lib</code></pre><p>The <code>rdeps()</code> function (short for <em>reverse dependencies</em>) works in the opposite direction of <code>deps()</code>. Rather than asking, &quot;What does this package depend on?&quot;, it asks instead, &quot;What else depends on this package?&quot; — an important question to be able to answer in assessing the impact of changes to shared components in a large monorepo. </p><p>If you wanted to narrow it down to just the binaries (excluding the libraries):</p><pre data-language="bash"><code>bazel query &quot;kind(&apos;go_binary&apos;, rdeps(//..., //lib/logging:logging))&quot;</code></pre><p>You&apos;d get:</p><pre><code>//services/auth:auth
//services/user:user
//services/payment:payment</code></pre><p>This combination of functions creates some powerful filtering capabilities.</p><h2>Visualizing dependency paths with <code>somepath()</code> and <code>allpaths()</code></h2><p>Determining exactly <em>how</em> one target depends on another can be helpful for debugging unexpected dependencies as well. Say you&apos;re puzzled by the <code>payment</code> service&apos;s dependency on some third-party database driver. You could quickly get to the bottom of that mystery with the <code>somepath()</code> function:</p><pre data-language="bash"><code>bazel query &quot;somepath(//services/payment:payment, @com_github_lib_pq//:pq)&quot;                </code></pre><p>Which would reveal that the path to the dependency (<code>pq</code>) is through <code>payment_lib</code>:</p><pre><code>//services/payment:payment
//services/payment:payment_lib
@com_github_lib_pq//:pq</code></pre><p>The <code>somepath()</code> function lets you see the exact path of dependencies, making it clear exactly what&apos;s responsible for the database driver getting pulled in. </p><p>But what if the path is more complex? Imagine you refactored the <code>payment</code> service to use a database interface instead:</p><pre data-language="python"><code># In services/payment/BUILD.bazel...
#...

# Add a go_library definition for the interface.
go_library(
    name = &quot;postgres_adapter&quot;,
    srcs = [&quot;postgres.go&quot;],
    deps = [
        &quot;//services/payment/db:db&quot;,
        &quot;@com_github_lib_pq//:pq&quot;,
    ],
)

go_library(
    name = &quot;payment_lib&quot;,
    srcs = [
        &quot;handlers.go&quot;,
        &quot;postgres.go&quot;,
        &quot;service.go&quot;,
    ],
    importpath = &quot;bazel_query_example/services/payment&quot;,
    visibility = [&quot;//visibility:private&quot;],
    deps = [
        &quot;//lib/config&quot;,
        &quot;//lib/logging&quot;,
        &quot;//lib/metrics&quot;,
        &quot;//services/payment/db&quot;,
        
        # Add this line to the `payment_lib` to use this new interface.
        &quot;:postgres_adapter&quot;, 
    ],
)

# ...</code></pre><p>Now that same query would show both the addition of the new <code>postgres_adapter</code> interface <em>and</em>  that the new interface is what&apos;s responsible for the transitive dependency of <code>payment</code> on <code>pq</code>:</p><pre><code>//services/payment:payment
//services/payment:payment_lib
//services/payment:postgres_adapter
@com_github_lib_pq//:pq</code></pre><p>The <code>somepath()</code> function identifies a single (randomly selected) dependency path. For a more comprehensive view of <em>all</em> possible dependency paths, you can use the aptly named <code>allpaths()</code> function:</p><pre data-language="bash"><code>bazel query &quot;allpaths(//services/payment:payment, @com_github_lib_pq//:pq)&quot;</code></pre><p>This would identify all of the paths to <code>pq</code>, from anywhere in the <code>//services/payment:payment</code> package.</p><p>To generate a visual representation of these dependencies, you can use <code>--output graph</code>:</p><pre data-language="bash"><code>bazel query &apos;deps(//services/payment/..., 2) &apos; --output graph --noimplicit_deps  | dot -Tpng -o graph.png</code></pre><p>Assuming you have <a href="https://graphviz.org/">Graphviz</a> installed, this would generate a Graphviz diagram in PNG format that visualizes the entire dependency tree—a great resource for coming to a quick understanding of complex projects when text-based representations become unwieldy. Here, we&apos;re showing the dependencies of the <code>payment</code> service to a depth of <code>2</code>, as described above:</p><div>Image of A GraphViz diagram showing the dependencies of the //services/payment package.</div><h2>Filtering by target type with <code>kind()</code></h2><p>In a large monorepo, you&apos;ll often have a mix of libraries, binaries, and tests. To focus specifically on only the tests, you can combine <code>kind</code> with the <code>go_test</code> type:</p><pre data-language="bash"><code>bazel query &quot;kind(&apos;go_test&apos;, //...)&quot;</code></pre><p>This can be especially helpful when you need to run specific commands using certain types of targets—for example, running all of the tests for a given package:</p><pre data-language="bash"><code>bazel test $(bazel query &quot;kind(&apos;go_test&apos;, //services/payment/...)&quot;)</code></pre><p>Which for our project would yield:</p><pre><code>INFO: Analyzed target //services/payment:payment_test (138 packages loaded, 5585 targets configured).
INFO: Found 1 test target...
Target //services/payment:payment_test up-to-date:
  bazel-bin/services/payment/payment_test_/payment_test
INFO: Elapsed time: 31.644s, Critical Path: 15.89s
INFO: 64 processes: 6 internal, 58 darwin-sandbox.
INFO: Build completed successfully, 64 total actions
//services/payment:payment_test                              PASSED in 0.3s

Executed 1 out of 1 test: 1 test passes.</code></pre><p>This pattern—of using query results as input to <code>bazel</code> or other shell commands—is a powerful way to compose dynamic workflows in your software-delivery pipelines.</p><h2>Combining queries for powerful insights</h2><p>The real power comes from combining these functions to form more complex queries. For instance, to find all tests that depend on the metrics library (perhaps because you&apos;re considering updating the metrics interface):</p><pre data-language="bash"><code>bazel query &quot;kind(&apos;go_test&apos;, rdeps(//..., //lib/metrics:metrics))&quot;</code></pre><p>This might reveal:</p><pre><code>//lib/metrics:metrics_test
//services/auth:auth_test
//services/payment:payment_test
//services/user:user_test</code></pre><p>Now you know exactly which tests might be affected by your changes.</p><p>Or to identify any services that depend on both the <code>logging</code> and <code>config</code> libraries:</p><pre data-language="bash"><code>bazel query &quot;kind(&apos;go_binary&apos;, rdeps(//..., //lib/logging:logging)) intersect kind(&apos;go_binary&apos;, rdeps(//..., //lib/config:config))&quot;</code></pre><p>The result:</p><pre><code>//services/auth:auth
//services/payment:payment
//services/user:user</code></pre><p>In our case, all three of our services use both libraries, but in a larger codebase, queries for intersections like these could identify unexpected dependency relationships or architectural choices that may call for a closer look and/or some refactoring.</p><h2>Beyond the basics with <code>cquery</code> and <code>aquery</code></h2><p>As your Bazel workspace grows, you may find yourself wanting to understand how specific build flags or <code>select()</code> statements impact your build graph. This is where Bazel&apos;s <code>cquery</code> —or &quot;configurable query&quot;—command comes in.</p><p>As an example, if our hypothetical payment service were using <code>select()</code> with the <code>deps</code> attribute to  toggle between different dependencies by deployment environment—e.g., with the following modified version of the <code>payment</code> service:</p><pre data-language="python"><code># Again, in services/payment/BUILD.bazel...
# ...

go_library(
    name = &quot;payment_lib&quot;,
    srcs = [&quot;service.go&quot;, &quot;handlers.go&quot;],
    deps = [
        &quot;//lib/config:config&quot;,
        &quot;//lib/logging:logging&quot;,
        &quot;//lib/metrics:metrics&quot;,
        &quot;//services/payment/db:db&quot;,
    
    # To make dependencies selectable/configurable: 
    ] + select({
        &quot;//config:production&quot;: [&quot;//lib/tracing:datadog&quot;],
        &quot;//config:development&quot;: [&quot;//lib/tracing:jaeger&quot;],
        &quot;//conditions:default&quot;: [],
    }),
)

# ...</code></pre><p>... then you could combine <code>bazel cquery</code> with the <code>somepath()</code> function to confirm that in <code>production</code>, the service uses the <code>datadog</code> library for tracing:</p><pre data-language="bash"><code>bazel cquery &quot;somepath(//services/payment:payment, //lib/tracing:datadog)&quot; --define environment=production</code></pre><pre><code>INFO: Found 2 targets...
//services/payment:payment (91a5247)
//services/payment:payment_lib (91a5247)
//lib/tracing:datadog (91a5247)</code></pre><p>The standard <code>bazel query</code> command wouldn&apos;t be able to show this, as <code>query</code> operates on Bazel&apos;s <em>loading</em> phase (more on this below). But <code>cquery</code>, which operates on the <em>analysis</em> phase, would, as it&apos;s there that <code>select()</code> statements are evaluated, based on the build flags you provide to the Bazel CLI.</p><p>For even deeper insights into how Bazel actually builds your targets, the <code>aquery</code> (or &quot;action query&quot;) command reveals the precise commands (i.e., <em>actions</em>) that are executed:</p><pre data-language="bash"><code>bazel aquery &quot;//services/payment:payment&quot;</code></pre><p>Which would render, for example:</p><pre><code>action &apos;GoCompilePkg services/payment/payment.a&apos;
  Mnemonic: GoCompilePkg
  Target: //services/payment:payment
  Configuration: darwin_arm64-fastbuild
  Execution platform: @@platforms//host:host
  ActionKey: cdc5684315545c6d69266f14ad9568cb87c401b0ef775ce97a9fb57a1a5dd979
  Inputs: [bazel-out/darwin_arm64-fastbuild/bin/external/gazelle++go_deps+com_github_lib_pq/pq.x, bazel-out/darwin_arm64-fastbuild/bin/external/rules_go+/stdlib_/pkg, bazel-out/darwin_arm64-fastbuild/bin/lib/config/config.x, bazel-out/darwin_arm64-fastbuild/bin/lib/logging/logging.x, bazel-out/darwin_arm64-fastbuild/bin/lib/metrics/metrics.x, bazel-out/darwin_arm64-fastbuild/bin/services/payment/db/db.x, bazel-out/darwin_arm64-opt-exec-ST-d57f47055a04/bin/external/rules_go++go_sdk+go_default_sdk/builder_reset/builder, bazel-out/darwin_arm64-opt-exec-ST-d57f47055a04/bin/external/rules_go++go_sdk+go_default_sdk/packages.txt, external/rules_go++go_sdk+go_default_sdk/bin/gofmt, external/rules_go++go_sdk+go_default_sdk/go.env, external/rules_go++go_sdk+go_default_sdk/pkg/include/asm_amd64.h, external/rules_go++go_sdk+go_default_sdk/pkg/include/asm_ppc64x.h, external/rules_go++go_sdk+go_default_sdk/pkg/include/funcdata.h, external/rules_go++go_sdk+go_default_sdk/pkg/include/textflag.h, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/addr2line, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/asm, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/buildid, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/cgo, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/compile, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/covdata, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/cover, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/doc, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/fix, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/link, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/nm, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/objdump, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/pack, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/pprof, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/test2json, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/trace, external/rules_go++go_sdk+go_default_sdk/pkg/tool/darwin_arm64/vet, services/payment/handlers.go, services/payment/postgres.go, services/payment/service.go]
  Outputs: [bazel-out/darwin_arm64-fastbuild/bin/services/payment/payment.a, bazel-out/darwin_arm64-fastbuild/bin/services/payment/payment.x]
  Environment: [CGO_ENABLED=1, GOARCH=arm64, GODEBUG=winsymlink=0, GOEXPERIMENT=nocoverageredesign, GOOS=darwin, GOPATH=, GOROOT_FINAL=GOROOT, GOTOOLCHAIN=local, PATH=external/rules_cc++cc_configure_extension+local_config_cc:/usr/bin:/bin, ZERO_AR_DATE=1]
  Command Line: (exec bazel-out/darwin_arm64-opt-exec-ST-d57f47055a04/bin/external/rules_go++go_sdk+go_default_sdk/builder_reset/builder \
  ...</code></pre><p>The output here can be a bit overwhelming, as it includes every compilation action, linking step, and so on—showing the actual compiler flags, environment variables, and command lines used during the build. But it&apos;s definitely valuable data to be able to reach for to debug complex build issues or understanding the optimizations Bazel is applying.</p><h2>Practical scenarios in development and delivery</h2><p>Let&apos;s wrap up with a few more practical situations in which Bazel query can be useful.</p><h3>Optimizing build performance</h3><p>If your CI builds are slow, you can identify the most distant dependencies with <code>minrank</code>:</p><pre data-language="bash"><code>bazel query &quot;deps(//services/...)&quot; --output minrank | sort -rn | head -10</code></pre><p>The <code>minrank</code> operator sorts targets by their depth in the graph, or how far they are from their original targets, which can help you focus your optimization efforts on the most impactful targets. </p><h3>Safely removing dead code</h3><p>Before deleting what appears to be unused code, you can query for any dependencies on it:</p><pre data-language="bash"><code>bazel query &quot;rdeps(//..., //lib/legacy:some_old_util)&quot;</code></pre><p>If <code>bazel query</code> reports nothing, it&apos;s likely safe to remove.</p><h3>Auditing third-party dependencies</h3><p>To list all external dependencies your Bazel packages have pulled in, you can use the <code>filter()</code> function with the <code>@</code> symbol (which Bazel uses to prefix external/third-party dependencies):</p><pre data-language="bash"><code>bazel query &quot;filter(&apos;@&apos;, deps(//services/...))&quot;</code></pre><p>This command is particularly valuable for security audits, but is useful in many ways, including helping to identify:</p><ul><li><p>Outdated dependencies that need updating</p></li><li><p>Duplicate dependencies with differing versions</p></li><li><p>Unexpected dependencies that might introduce vulnerabilities</p></li></ul><h2>Understanding Bazel&apos;s build phases</h2><p>To get the most out of the Bazel <code>query</code>, <code>cquery</code>, and <code>aquery</code>, it helps to understand how each one relates to Bazel&apos;s build phases:</p><ol><li><p><strong>Loading phase</strong>: Here, Bazel loads <code>BUILD</code> files and constructs the target graph. The <code>query</code> command operates at this level, working with the <em>theoretical</em> structure of your build, without considering any build flags.</p></li><li><p><strong>Analysis phase</strong>: Here, Bazel applies the build the options that you provide at the command line and uses them to evaluate the <code>select()</code> statements in your <code>BUILD</code> files. The <code>cquery</code> command operates at this level, showing you what the build graph actually looks like <em>after</em> configuration.</p></li><li><p><strong>Execution phase</strong>: Here, Bazel determines the precise actions needed to build each target. The <code>aquery</code> command operates at this level, showing the actual commands that are executed during the build.</p></li></ol><p>Each query type offers a different perspective on your build, allowing you to examine exactly what you need at different stages of the build process.</p><h2>Wrapping up, and next steps</h2><p>We&apos;ve covered a lot! But by now it should be clear that <code>bazel query</code> is more than just a handy tool—it&apos;s an interactive lens that can bring clarity and maintainability to even the most complex Bazel workspaces. Combined with other tools, it can unlock even more advanced workflows that allow you to tailor your delivery processes and get the most out of what Bazel has to offer.</p><p>Some suggestions to keep the learning going:</p><ul><li><p><a href="https://github.com/cnunciato/bazel-query-guide-example">Experiment with the examples in this post</a> by checking out the repository on GitHub</p></li><li><p><a href="https://bazel.build/query/language">Dive into the Bazel query reference docs</a> to learn more (there&apos;s a lot!)</p></li><li><p><a href="https://buildkite.com/resources/webinars/how-bazel-built-its-ci-system-on-buildkite/">See how the Bazel team itself combined Bazel with Buildkite</a> to enable faster, more reliable builds by using <code>bazel query</code> outputs to generate their delivery pipelines dynamically </p></li></ul><p></p>