4.9.1 Repository Rule Fundamentals

Repository rules are Bazel's loading-phase mechanism for making another repository exist. A repo rule takes attributes such as a URL, checksum, local path, or generated BUILD file, then materializes a directory tree that Bazel can address with labels like @repo//pkg:target.1 Build rules create targets inside an already-loaded package. Repository rules prepare the external repository whose packages Bazel can then load and analyze.2

A repo rule materializes an external repository on demand
During loading, Bazel materializes the requested file tree before packages in that repository can load.
Declare
A module requests a repository
MODULE.bazel or extension
repo.bzl
hello_repo =
repository_rule(...)
MODULE.bazel
repo = use_repo_rule(
"//:repo.bzl",
"hello_repo",
)
repo(name = "hello")

Bzlmod decides that a repository should exist.

Run
The implementation runs during loading, when needed
repository_ctx API
def _impl(rctx):
rctx.file("BUILD.bazel")
rctx.file("hello.txt")
loading phase no actions no providers

This is repository_ctx, not analysis-phase rule ctx.

Materialize
A repository tree becomes loadable
packages and labels later
@hello/
BUILD.bazel
hello.txt
@hello//:hello.txt

ordinary label after the repository is fetched

The output is a namespace, not one declared build output.

Repository rules perform repository materialization. The resulting repository namespace must exist before its packages load.

A Repo Rule Materializes A Directory Tree

An external repository is just a repository-shaped directory tree: source files plus Bazel boundary files and packages. Bazel fetches that directory on demand when a label, dependency, or command needs something from it.3 The rule invocation is the definition of how to create that tree: download and extract an archive, symlink a local directory, clone a Git repository, resolve Maven artifacts, or generate BUILD files around host-specific content.4

That makes repository rules feel similar to build rules, but their output is not a target's declared file. Their output is an entire repository namespace. Once materialized, normal labels inside that repository work the same way as labels in the main repository: a package is still a package, a target is still a target, and @repo//tools:compiler means "the target compiler in package tools of the apparent repository repo."5

The smallest custom repository rule has the same skeleton as other Starlark extension points: define an implementation function, then assign the result of repository_rule() to a global symbol.6

def _hello_repo_impl(ctx):
    ctx.file("hello.txt", ctx.attr.message + "\n")
    ctx.file("BUILD.bazel", 'exports_files(["hello.txt"])')

hello_repo = repository_rule(
    implementation = _hello_repo_impl,
    attrs = {
        "message": attr.string(mandatory = True),
    },
)

In a Bzlmod project, a user can expose that rule in MODULE.bazel with use_repo_rule() and call it to create a repository.7

hello_repo = use_repo_rule("//:repo.bzl", "hello_repo")

hello_repo(
    name = "hello",
    message = "Hello, repository rules",
)

This example assumes repo.bzl sits in a Bazel package. An empty root BUILD.bazel file is enough to make the //:repo.bzl label resolvable.7

The implementation receives repository_ctx, not the analysis-phase ctx from rule(). That context can write files, create symlinks, expand templates, download files, extract archives, execute host commands, read environment variables, and watch files that should trigger re-fetching.8

It Runs When The Repository Is Needed

A repository rule implementation runs during loading, on demand when Bazel needs a target from that repository. Materialization must finish before Bazel can load packages and targets from it.9 It does not run in the action sandbox, does not create actions, and cannot depend on files produced by normal build actions.10 If the repo rule needs a helper tool, it must get that tool in loading-phase terms: download a prebuilt binary, use a host command deliberately, or structure the integration so the generated repository exposes targets for the later build graph.11

That lifecycle difference is the reason repository rules are powerful and easy to misuse. They can reach the network and host filesystem outside the ordinary action model. Bazel therefore cannot automatically notice every possible change that might affect the generated repository. It re-fetches a repository when tracked inputs change: the repo rule attributes, the Starlark implementation, environment variables read through repository_ctx.getenv() (preferred), watched paths, or an explicit forced fetch.12 The legacy repository_rule(environ = [...]) declaration is deprecated in favor of getenv(), which is covered later in 4.9.6 getenv() vs os.environ Pitfall alongside file watching in 4.9.7 watch() / watch_tree() File Dependencies.

Built-In Rules Are The Default Starting Point

Most projects should start with built-in repository rules instead of custom code. http_archive downloads a compressed archive, verifies it, extracts it, and makes the repository's targets available.13 http_file and http_jar handle single-file downloads.14 local_repository exposes a local directory that already contains Bazel files, while new_local_repository creates Bazel files around a local directory that does not already have them.15

git_repository and new_git_repository also exist, but http_archive is the recommended default: Git rules depend on system git, support only one remote instead of mirror URLs, and do not work with the repository cache in the same way.16 4.9.2 Archive Downloads & Integrity explains the archive and integrity trade-offs in detail. Here, the decision rule is simple: choose the repository rule whose materialization model is already closest to the dependency you need.

Bzlmod Decides. Repo Rules Materialize

Bzlmod and repository rules solve different parts of external dependency handling. Module resolution starts from MODULE.bazel, reads the transitive module graph, selects versions, and determines which repositories should be available.17 Repository rules are the lower-level materialization mechanism: they say how a particular repo directory is fetched, symlinked, or generated.18

Module extensions connect the two. An extension reads tags across the module graph and usually creates repositories by calling repo rules.19 For example, an extension for Maven, Go modules, or Cargo can aggregate the user's dependency declarations, resolve them with ecosystem-specific logic, then call repository rules to expose generated repositories to Bazel.20 4.10.1 Module Extension Fundamentals develops the extension model. For now, keep the distinction that a repository rule is the unit it calls to make a repo exist.

Design With The Boundary In Mind

Treat a repository rule as a boundary between Bazel's dependency graph and something outside it: a release archive, a local SDK, a language package manager, a system installation, or a generated toolchain repository. The best repo rules make that boundary explicit. They take clear attributes, verify downloaded bytes, generate predictable BUILD files, and record the host inputs that affect their output.21

The worst repo rules turn loading into an opaque bootstrap script. If the implementation shells out to tools from ambient PATH, reads untracked environment variables, follows mutable branches, or embeds a full external evaluator, the generated repository can differ across machines without Bazel knowing why.22 Those failure modes create the design pressure behind the rest of this section: integrity checks, custom rule APIs, reproducibility, watched inputs, hermetic host tooling, and non-incremental external evaluation.

key takeaway

A repository rule does not build a target. It makes a repository available so Bazel can load packages and analyze targets inside it.

Use built-in repo rules first. Write a custom one only when the dependency boundary itself needs custom materialization logic.

Check your understanding · 4 questions

1.What does a repository rule produce?

Select one answer

2.Which operations belong to the repository rule boundary?

Select all that apply

3.Match each built-in repository rule to its usual materialization model.

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
http_archive
local_repository
new_local_repository
git_repository

4.How do Bzlmod module extensions and repository rules usually divide responsibility?

Select one answer

0 of 4 answered

Footnotes

  1. External dependencies overview — repository rule as the schema that materializes a repository directory.

  2. Repository Rules — repo rules are invoked like build rules but define external repositories, not ordinary build targets.

  3. External dependencies overview — fetched repositories are made available on local disk on demand.

  4. Repository Rules — examples include external libraries, generated host-specific BUILD files, C++ configuration, Go repositories, and Maven artifacts.

  5. External dependencies overview — repository, apparent name, canonical name, and external label concepts.

  6. Repository Rules — defining a repo rule with repository_rule(), attributes, and an implementation function.

  7. Writing Bazel rules: repository rules — Bzlmod use_repo_rule() example for a custom repository rule. 1 2

  8. repository_ctx — API surface for file generation, downloads, execution, environment access, templates, and watches.

  9. Repository Rules — implementation function executes strictly in the loading phase when Bazel needs a target from the repository

  10. Writing Bazel rules: repository rules — repo rules run during loading and cannot create actions or depend on regular action outputs

  11. Writing Bazel rules: repository rules — guidance on host tools, prebuilt binaries, and complexity in repository rules.

  12. Repository Rules — documented re-fetch triggers: attributes, Starlark implementation, tracked environment, watched paths, and forced fetch.

  13. http repository ruleshttp_archive downloads and extracts archives as repositories.

  14. http repository ruleshttp_file and http_jar single-file repository rules.

  15. local repository ruleslocal_repository versus new_local_repository.

  16. git repository rules — recommendation to prefer http_archive over Git repository rules and the listed reasons.

  17. External dependencies overview — Bzlmod starts from the root module, resolves transitive modules, then determines repository definitions.

  18. External dependencies overview — every repo is defined by calling a repo rule with arguments.

  19. Module extensions — extensions read tags and create repos by calling repo rules.

  20. External dependencies overview — module extensions integrate non-Bazel package managers while respecting the module graph.

  21. Writing Bazel rules: repository rules — best practices around checksum verification, generated BUILD files, host-system avoidance, and reproducibility.

  22. Writing Bazel rules: repository rules — caution on host system dependencies, unsandboxed repository_ctx, environment variables, timestamps, and platform differences.