Bazel Book

Load Statements

load() is the import boundary between a BUILD file and reusable Starlark definitions in .bzl files1,2. It brings selected names into the current file's scope, which is why it sits in the header we saw in 0.3.1 Anatomy of a BUILD File. The important beginner mental model is simple: load() imports symbols; it does not "run the build", and it does not make the rest of the file less declarative1,2.

load() selects public symbols into BUILD file scope
Only requested public names cross from .bzl to BUILD.bazel
.BZL FILE
defs.bzl
reusable Starlark names
my_rule rule
COMMON_TAGS const
_helper private
Names starting with _ are private.
They cannot be loaded.
LOAD STATEMENT
load(
":defs.bzl",
"my_rule",
TAGS = "COMMON_TAGS",
)
BUILD.bazel
file scope
after load()
my_rule rule
TAGS alias
Imported names become available
to later declarations in this file.
Only requested public names cross.

What load() Actually Imports

load("//foo/bar:file.bzl", "some_library")
load("//foo:defs.bzl", my_bin = "bin")

The first argument is a label pointing at a .bzl file. The later arguments name the specific symbols that should become available in the current file1. Those imported names can be rules, macros, functions, or constants1. Aliasing lets the current file use a different local name, as in my_bin = "bin" above1,2.

That is also why load() is more precise than "import this file". It does not dump everything from the .bzl file into scope. It exposes only the names you ask for, and symbols starting with _ are private and cannot be loaded from another file1.

The First Argument Is A Bazel Label

load(":my_rules.bzl", "some_rule", nice_alias = "some_other_rule")
load("@rules_shell//shell:defs.bzl", "sh_binary", "sh_library")

The path in load() uses Bazel label syntax, not Python module syntax1,2. In a BUILD file, :my_rules.bzl means "the .bzl file in this package". A form like //foo/bar:file.bzl points at another package in the main repository. A form like @rules_shell//shell:defs.bzl points into an external repository1,2. So load() is another place where the label model from 0.2.1 Label Anatomy matters before any target declarations begin.

Why It Lives In The Header

BUILD files are evaluated as a sequential list of statements, so a name has to exist before later code can use it1. load() statements must appear at top level, and typical BUILD-file structure puts them before the optional package() call from 0.2.6 package() Function and before any target declarations1,2. That keeps the file readable: first import the external vocabulary, then declare the package metadata and targets that use it.

This placement also helps preserve the declarative style from 0.3.1 Anatomy of a BUILD File. After the load() block, the rest of the file is still a flat list of declarations using the imported names. If a symbol such as sh_binary or my_bin appears later in the file, the header tells you where that name came from before you start reading attributes or dependencies.

Not every symbol in a BUILD file comes from load(). Some rules are native and already available, while others are defined in Starlark and loaded from built-in or external repositories4. Writing those abstractions belongs to later topics such as 4.1.3 Legacy Macros, 4.1.4 Symbolic Macros (Bazel 8+), and 4.3.1 Rule Function. In Level 0, the key skill is simply recognizing the import boundary correctly.

extra

A Later Performance Caveat

Aspect's training material notes that, as of Bazel 8, load statements are eagerly evaluated transitively, so deep load chains can slow the loading phase2. That belongs to later architecture and performance discussions, especially 2.2.1 Loading, Analysis & Execution and 3.1.5 Eager Fetch Anti-pattern. For now, the practical lesson is just to place load() mentally in the package-loading step, not in action execution.

key takeaway

load() makes a BUILD file's external vocabulary explicit1,2. The first argument says where the .bzl file lives, the later arguments say which public names enter scope, and the rest of the file can use those names as ordinary declarations. If a BUILD file feels mysterious, read the load() block first: it tells you which rules, macros, and shared constants the package depends on before any target definitions start.

Check your understanding

1.What does load() do in a BUILD file?

2.Can symbols starting with _ (underscore) be loaded from a .bzl file?

3.The first argument to load() uses what kind of syntax?

Answer all questions to check

Footnotes

  1. BUILD filesload() syntax, label-based .bzl lookup, aliasing, top-level restriction, and _-prefixed symbols not being exportable 1 2 3 4 5 6 7 8 9 10 11 12

  2. Bazel Training 101 (Part 12): Manually using rules in BUILD filesload() as the common BUILD-file opening pattern, file-scope imports, BUILD vs .bzl distinction, and the eager transitive evaluation note 1 2 3 4 5 6 7 8

  3. Sharing Variables — using .bzl files plus load() to share constants across multiple BUILD files 1 2

  4. Rules — distinction between native rules that are already available and Starlark-defined rules loaded from built-in or external repositories