Bazel Book

package_group

recommended

A raw visibility = [...] list works well when one target has one simple access policy. It gets noisy when the same allowlist appears on several targets. package_group solves that problem: it defines a named set of packages in a BUILD file, and that name can then be reused from visibility declarations and package-wide defaults in 0.2.6 package() Function1,2.

One Label for a Shared Allowlist

package_group is Bazel's way to say "these packages are allowed clients" once, then refer to that set by label2,3.

package_group(
    name = "clients",
    packages = [
        "//app",
        "//tools/...",
    ],
)

cc_library(
    name = "internal_api",
    srcs = ["api.cc"],
    visibility = [":clients"],
)

Here :clients is an ordinary label pointing at the package_group target3. But Bazel only permits package_group labels in visibility lists, not arbitrary target labels3. The group does not describe a build artifact. It describes which packages may depend on one2.

Package Specs, Not Target Labels

Inside packages = [...], Bazel switches to a package-spec language3:

  • "//foo/bar" means exactly the package //foo/bar.
  • "//foo/bar/..." means that package and all its subpackages.
  • "public" and "private" are the package-group forms of //visibility:public and //visibility:private3.

That syntax is easy to misread because some strings look like labels. In normal label context, //my/app means the eponymous target //my/app:app. In a package_group, the same string means the package itself4. This context switch is the main conceptual difference between package_group and the visibility placeholders introduced in 0.2.4 Visibility.

extra

Two Similar-Looking Languages

visibility = ["//friends:__subpackages__"] and package_group(packages = ["//friends/..."]) can describe the same client set, but they use different spellings because they belong to different syntactic contexts3. visibility accepts target-side placeholders like __pkg__ and __subpackages__. package_group accepts package specifications like //pkg and //pkg/....

Reuse and Composition

The rule itself is intentionally small: package_group(name, packages, includes)5. packages lists the allowed clients directly. includes lets one group incorporate other groups instead of copying the same allowlist around3,5.

That gives you one place to maintain policy. If three internal libraries all need to be visible to the same slice of the repository, you update one package group instead of three visibility lists3. This becomes part of monorepo boundary design in 7.3 Code Sharing & Reuse and, later, part of access-control strategy in 7.4 Security & Access Control.

One subtle rule: package_group targets are always publicly visible3. That is what makes them reusable across packages. If the group itself had private visibility, it could not serve as a shared allowlist.

When to Reach for It

Use direct visibility placeholders when the policy is truly local and one-off. Reach for package_group when the same set of clients appears repeatedly, or when the allowed consumers are easier to describe as a named subtree like //backend/...3. The same group can also feed package(default_visibility = ...), which is the next step after target-level visibility rules in 0.2.6 package() Function2.

key takeaway

package_group does not replace visibility. It makes visibility reusable. Define the client set once with package specifications such as //foo or //foo/..., give it a name, then point multiple targets at it with visibility = [":clients"].

Check your understanding

1.What problem does package_group solve?

2.Inside package_group(packages = [...]), what does //foo/... mean?

Answer all questions to check

Footnotes

  1. Repositories, workspaces, packages, and targets — package groups as named sets of packages used from visibility and default_visibility

  2. BUILD filespackage_group(name, packages, includes) as a BUILD-level declaration with a reusable label 1 2 3 4

  3. Visibility — package_group as shared visibility allowlist, alternate package-spec syntax, includes, and always-public package_group targets 1 2 3 4 5 6 7 8 9 10

  4. Labels//pkg means a package inside package_group specs, not the eponymous target label

  5. BUILD filespackage_group(name, packages, includes) API: includes for composing groups 1 2