Starlark
Configuration language for Bazel and related build tools
Starlark is a configuration language, originally developed at Google as the BUILD-file language for the Bazel build system, that reads like a restricted dialect of Python and is used to describe build targets, dependencies, and rules in a…
Definition
Starlark is a configuration language, originally developed at Google as the BUILD-file language for the Bazel build system, that reads like a restricted dialect of Python and is used to describe build targets, dependencies, and rules in a deterministic, hermetic way. It omits mutable global state, unbounded recursion, and most of Python's dynamic features so that the same input always produces the same evaluation result, which makes it safe to run inside a sandboxed build graph rather than as a general scripting language.
Overview
Build systems need a way to describe what to compile and how the pieces depend on one another, and for a long time that meant either a declarative format like Make's syntax or a full scripting language bolted onto the build tool with all the risk that entails. Starlark was created to sit between those two extremes: it looks and feels like Python, borrowing its syntax for functions, lists, dictionaries, and control flow, but it strips out classes, exceptions, dynamic imports, and threading so that a BUILD file cannot do anything unpredictable or slow to evaluate. Mechanically, a Starlark file is evaluated by an interpreter embedded in the host tool, most commonly Bazel, and the evaluation is required to be deterministic and hermetic: given the same source files, the interpreter must produce the same set of build rules every time, with no network access, no file system reads outside declared inputs, and no reliance on wall-clock time or random values. This lets Bazel cache and parallelize build graph construction aggressively, because it can trust that re-evaluating a file with unchanged inputs will yield an unchanged result. Extensions to the language, called rules and macros, are themselves written in Starlark and loaded via a `load()` statement, which lets teams define reusable build logic without touching the host tool's own codebase. Among configuration languages, Starlark occupies a distinct niche from data-only formats like YAML or JSON, which can describe structure but not logic, and from general-purpose embedded languages like Lua, which allow arbitrary computation with fewer safety guarantees. It is closer in spirit to languages like CUE or Dhall in that it trades expressive power for predictability, but it differs from both by deliberately keeping Python-familiar syntax so that engineers already comfortable with Python can write build logic with little ramp-up. This familiarity is a deliberate design trade-off: Starlark looks approachable, but its restrictions surface in ways that surprise Python developers, such as the absence of a `while` loop with unbounded iteration or the ban on reassigning function-scoped variables inside certain contexts. In practice, Starlark is the language developers touch every day when writing or maintaining BUILD and .bzl files under Bazel, and it has been adopted beyond Bazel by tools including Buck2 and some continuous-integration configuration systems that wanted a safer alternative to full scripting. Teams write custom rules in Starlark to teach the build system how to compile a new language or packaging format, and macros to reduce repetition across many BUILD files in a large monorepo. Tooling such as Buildifier formats and lints Starlark files to keep large codebases consistent. The limitations are largely the intentional consequence of its safety goals: Starlark cannot perform arbitrary I/O, cannot depend on external mutable state, and has a smaller standard library than Python, so tasks that need genuine general-purpose scripting, like invoking a shell command with complex conditional logic, have to be delegated to a genuine script invoked as a build action rather than expressed directly in Starlark. Engineers coming from Python must also unlearn certain patterns, since global variables become read-only after a module finishes loading. When a project needs a fully dynamic scripting environment rather than a deterministic build description, a general-purpose language is the better fit.
Key Features
- Python-like syntax with functions, lists, dicts, and comprehensions
- Deterministic evaluation guarantees the same output for the same input
- No classes, exceptions, or unbounded recursion by design
- Hermetic execution with no network or unrestricted file access
- Custom rules and macros loaded via the load() statement
- Used as the native BUILD-file language for Bazel and Buck2
- Formatted and linted consistently across large monorepos with Buildifier
- Global variables become immutable once a module finishes loading