Visibility
Visibility is Bazel's access-control layer for the build graph. It determines which targets can depend on a given target — that is, who can reference its label in attributes like deps1. When target A depends on target B, Bazel checks whether B's visibility grants access to A's package. If not, the build fails during the analysis phase, before any compilation happens1.
This is how you enforce architectural boundaries mechanically. A library can expose its public API to consumers across the repository while keeping internal helpers invisible — and Bazel enforces those boundaries at build time, not by convention.
Visibility Specifications
Every rule target has a visibility attribute that accepts a list of package specifications2. The first four forms use special placeholder syntax — they don't correspond to real targets in the build graph:
"//visibility:public"— accessible from all packages."//visibility:private"— accessible only within the declaring package."//foo/bar:__pkg__"— accessible to targets in//foo/bar, but not its subpackages."//foo/bar:__subpackages__"— accessible to//foo/barand all packages nested under it, at any depth.
The fifth form is different — it's an ordinary label pointing at a real package_group target:
"//some_pkg:my_package_group"— accessible to all packages in the namedpackage_group(see 0.2.5 package_group).
Note the asymmetry between __pkg__ and __subpackages__. Granting visibility to //tests:__pkg__ allows targets in //tests/BUILD.bazel to depend on you, but targets in //tests/integration/BUILD.bazel are blocked2. The __subpackages__ form covers the entire subtree.
Multiple specifications combine into a union. A target visible to //src/app:__subpackages__ and //tests:__pkg__ can be used anywhere under //src/app/... and in //tests, but not in //tests/integration:
cc_library(
name = "internal_utils",
srcs = ["utils.cc"],
visibility = [
"//src/app:__subpackages__",
"//tests:__pkg__",
],
)
When granting access to another team's project, prefer __subpackages__ over __pkg__3. Projects evolve and add subdirectories — __pkg__ forces a visibility update every time that happens.
Defaults
When a target omits the visibility attribute, Bazel applies the package's default_visibility if one is set via package(). Otherwise, the target is private — visible only within its own package4. The package() function is covered in 0.2.6 package() Function.
Generated file targets — outputs produced by rules — inherit the visibility of the rule that creates them4. A java_binary visible to //friend:__pkg__ makes its implicit outputs (like _deploy.jar) visible to //friend as well.
What a Visibility Violation Looks Like
Visibility errors surface during analysis — before compilation, linking, or any real work1. Bazel reports which target is inaccessible and from where:
ERROR: .../app/BUILD.bazel:3:12: in java_binary rule //app:server:
Visibility error:
target '//lib:internal' is not visible from
target '//app:server'
Recommendation: modify the visibility declaration if you think
the dependency is legitimate. For more info see
https://bazel.build/concepts/visibility
The fix is either to widen the target's visibility or to restructure the dependency. These errors are protection, not obstacles — they stop developers from reaching into implementation details that were never intended as stable API. Visibility becomes a key tool for enforcing API boundaries as the codebase scales (7.3.2 API Boundaries via Visibility) and for restricting access to sensitive code (7.4.1 Visibility as Security Boundary).
Visibility controls who can depend on your target. Five forms — public, private, __pkg__, __subpackages__, and package_group references — scope access from "everyone" down to "just this package." Targets default to private when no visibility or default_visibility is set. Violations are caught during analysis, before any compilation.
Disabling Visibility Checks
The flag --check_visibility=false turns off visibility enforcement entirely2. This can unblock prototyping when you're iterating on package structure, but it should never appear in committed .bazelrc files or CI configurations — it defeats the architectural guardrails that visibility provides.
1.When does Bazel check visibility violations?
2.What is the difference between //foo:__pkg__ and //foo:__subpackages__ in a visibility list?
3.What is the default visibility when a target omits the visibility attribute and no package default is set?
Footnotes
-
Visibility — Target visibility concept and analysis-phase enforcement ↩1 ↩2 ↩3
-
Visibility — Visibility specifications and
--check_visibilityflag ↩1 ↩2 ↩3 -
Visibility — Best practice: prefer
__subpackages__over__pkg__to avoid churn ↩ -
Visibility — Rule target default visibility and generated file target inheritance ↩1 ↩2