4.5.4 Stardoc — API Documentation
recommendedStardoc turns the public Starlark API of a ruleset into a build artifact. That is the important shift: rule documentation is not a wiki page someone remembers to update after changing defs.bzl. It is generated from the rule, provider, macro, and function declarations that users actually load.1 For a production ruleset, Stardoc belongs next to analysis tests, integration fixtures, and executable examples because all of them protect the same thing: the public contract.
.bzl module and its transitive load() dependencies, extracts documented symbols, then emits a chosen output format.└─ load(":rules.bzl", …)
└─ load(":providers.bzl", …)
format = "proto"Structured output for another renderer or documentation pipeline.Document the API Where It Lives
Stardoc can only generate useful docs if the public API carries structured documentation. For rules, put the rule-level explanation in the doc parameter of rule() and document user-facing attributes with the doc parameter on attr.*() declarations.2 The same principle applies to providers: provider(doc = ...) explains the provider, and a fields dictionary documents the data consumers are allowed to read.3
LibraryInfo = provider(
doc = "Compilation data consumed by downstream library rules.",
fields = {
"headers": "depset of public header Files.",
"archives": "depset of compiled archive Files.",
},
)
library = rule(
implementation = _library_impl,
doc = "Builds a library and exposes compilation metadata.",
attrs = {
"srcs": attr.label_list(
allow_files = [".src"],
doc = "Source files compiled into the library.",
),
"deps": attr.label_list(
providers = [LibraryInfo],
doc = "Libraries this target depends on.",
),
},
)
This is the documentation side of 4.4.1 Rule Public API Design. Attribute names, provider fields, default outputs, and public .bzl entry points are API surface. Their docs should sit in the same declarations so reviewers see API behavior and API explanation together. The official .bzl style guide is explicit about this: document rules, aspects, attributes, providers, and provider fields using doc, and use file/function docstrings for public Starlark code.4
Functions and legacy macros use Python-style docstrings with an Args: section. Stardoc reads those docstrings, while rule attributes and provider fields come from the structured Starlark declarations.5 Private rule attributes whose names begin with _ do not appear in generated Stardoc output, which is another reason to keep implementation tools and helper labels behind private attrs instead of making users wire them directly.6
Generate Docs as a Bazel Target
The basic target is intentionally small: load stardoc, point it at one .bzl file, and declare an output Markdown file.7
load("@stardoc//stardoc:stardoc.bzl", "stardoc")
stardoc(
name = "defs-docs",
input = "defs.bzl",
out = "defs.md",
)
With Bzlmod, the default repository name in Stardoc examples is @stardoc. With legacy WORKSPACE setup, the repository name is @io_bazel_stardoc.8 Do not hard-code a Stardoc version from an old blog post into project documentation. Point maintainers to the current Stardoc release snippet for MODULE.bazel or WORKSPACE setup, then keep the local BUILD targets boring.
Running bazel build //docs:defs-docs produces the declared Markdown output. A single stardoc target documents one target .bzl file. symbol_names can limit the output to selected public symbols when a file exports more than the page should show.9 The default output format is Markdown, but Stardoc also supports format = "proto" for workflows that want the structured documentation data instead of rendered Markdown.10
Model Loaded .bzl Dependencies
Real rulesets rarely keep every public symbol in a standalone file. When the documented .bzl file loads other .bzl files, Stardoc needs those dependencies modeled as bzl_library targets and listed in the deps attribute.11
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("@stardoc//stardoc:stardoc.bzl", "stardoc")
bzl_library(
name = "internal-libs",
srcs = [
"providers.bzl",
"helpers.bzl",
],
)
stardoc(
name = "defs-docs",
input = "defs.bzl",
out = "defs.md",
deps = [":internal-libs"],
)
The reason is not just file discovery. Stardoc evaluates Starlark to determine rule attribute types, and the underlying documentation extraction must understand Bazel-specific .bzl loads rather than treating Starlark as plain Python-like text.12 If a docs target succeeds only because the file happens to be self-contained today, it will break as soon as the public API starts loading shared providers, constants, or helper macros.
For multiple public .bzl files, use a small documentation hub file that loads the public symbols and binds them as globals, then pass symbol_names to the stardoc target.13 That keeps the generated page organized without forcing the ruleset implementation to collapse into one giant defs.bzl.
Choose the Artifact Deliberately
For an internal ruleset or a simple documentation site, generated Markdown is often enough. Build it in CI, publish it with the rest of the docs site, or check it in only if the repository has a clear generated-file policy. The official deploying guide points new ruleset authors at the rules template because it already includes API documentation generation and a CI/CD path for distribution.14
Checked-in generated docs should be treated like other generated artifacts: useful when they serve readers directly, but noisy when every minor Starlark edit creates a failing golden diff. A common pattern guards checked-in Stardoc output with stardoc_with_diff_test, at a contributor cost: a small code correction can require regenerating Markdown before the PR turns green.15 If you use that pattern, make the refresh command obvious and keep the target fast enough that documentation does not become the slowest part of a ruleset contribution.
There is now another path for published Bzlmod modules. The Bazel Central Registry can render Starlark API docs from starlark_doc_extract outputs published as a release artifact, and the module metadata points to that archive with docs_url.16 The Stardoc repository map and upstream design alternatives make the decision boundary concrete: use Stardoc when you need its Markdown rendering, symbol filtering, or custom templates, but do not insert it when the registry can publish the native extraction artifact directly. That publishing workflow belongs with release gates in 4.11.5 Ruleset CI/CD Patterns. The 4.5.4 contract is narrower: the ruleset must have documented public symbols and buildable documentation targets before release automation tries to ship them.
The mini-ruleset wires generation in docs/BUILD.bazel and checks in the resulting glyph_api.md, making both sides of the maintenance choice inspectable.
Stardoc is most valuable when it is part of the ruleset contract, not a one-off docs command. Put doc strings on public rules, attrs, providers, fields, functions, and macros. Model .bzl dependencies with bzl_library. Then make the generated documentation target run in the same quality path as tests and examples.
The release pipeline can decide where the docs go. The rule author's job is to make documentation generation reproducible, reviewable, and close enough to the API declaration that drift becomes hard to miss.
Check your understanding · 3 questions
1.Which Starlark declarations should carry documentation that Stardoc can extract?
Select all that apply
2.Why should loaded .bzl files be modeled with bzl_library dependencies for Stardoc?
Select one answer
3.Match each Stardoc-related artifact or step to its role.
Drag each answer onto the matching prompt, or click an answer and then click a prompt
stardoc() targetsymbol_namesFootnotes
-
Stardoc Documentation - Stardoc as a documentation generator for Bazel/Starlark APIs and the
stardocrule that generates Markdown documentation. ↩ -
Stardoc Documentation - rule documentation through
rule(doc = ...)and attribute documentation throughattr.*(doc = ...). ↩ -
Stardoc Documentation - provider documentation through
provider(doc = ...)and field documentation through thefieldsmap. ↩ -
.bzl style guide - official guidance to document files, functions, rules, aspects, attributes, providers, and provider fields. ↩
-
Stardoc Documentation - function and macro documentation using Python-style docstrings and an
Args:section. ↩ -
Stardoc Documentation - private rule attributes beginning with
_are omitted from generated documentation. ↩ -
Stardoc Documentation - minimal
stardoc()target withinputandout. ↩ -
Stardoc Documentation - Bzlmod uses
@stardoc. LegacyWORKSPACEsetup uses@io_bazel_stardoc. ↩ -
Stardoc Documentation - one documentation page per
.bzlfile andsymbol_namesfiltering. ↩ -
Stardoc Documentation -
formatvaluesmarkdownandproto, with Markdown as the default. ↩ -
Stardoc Documentation - using
bzl_librarydependencies through thedepsattribute for loaded.bzlfiles. ↩ -
Bazel Starlark Docs on the Registry - documentation extraction runs Bazel's Starlark interpreter and needs transitively loaded files from
bzl_librarydeps. ↩ -
Stardoc Documentation - documentation hub pattern for multiple
.bzlfiles and explicitsymbol_names. ↩ -
Deploying Rules - rules template includes API documentation generation and CI/CD setup for distributing rulesets. ↩
-
Bazel Starlark Docs on the Registry - checked-in Stardoc output guarded by
stardoc_with_diff_testand the maintenance cost for contributors. ↩ -
Bazel Starlark Docs on the Registry - BCR docs flow using
starlark_doc_extractoutputs anddocs_urlmetadata. ↩