skip to content

In a Dart package's build.yaml, how do targets, generate_for and builder options configure the generators build_runner runs?

level: middleimportance: should knowfreq 30%

answer

  1. $default target, keyed by builder
  2. globs narrow which files run
  3. options become BuilderOptions
  4. annotation beats build.yaml
  5. --define beats everything

basics

~20 s

build.yaml's targets: $default: builders: section configures each builder applied to the package: generate_for limits it to matching files, enabled switches it on or off, and options passes settings such as json_serializable's explicit_to_json to every generated class.

solid answer

~40 s

`build.yaml` sits at the package root. Under `targets:`, the `$default` target (or one named after the package) has a `builders:` map keyed by builder, such as `json_serializable` or `source_gen:combining_builder`. Each entry can set `enabled`, `generate_for` (globs, or an `include`/`exclude` map) to restrict inputs, and `options`, a free-form map passed to the builder as `BuilderOptions`; `dev_options` and `release_options` override per build mode. For json_serializable, `options` set package-wide defaults for every `@JsonSerializable` field such as `explicit_to_json` or `field_rename`, and a value set on the annotation wins over `build.yaml`. A top-level `global_options:` section overrides target options, and `--define=pkg:builder=key=value` on the command line overrides everything. `generate_for` also speeds builds, since the builder skips files it does not need.

code

bash · 5 lines
bash
# one-off override without editing build.yaml
dart run build_runner build --define=json_serializable:json_serializable=explicit_to_json=true

# read build.ci.yaml instead of build.yaml
dart run build_runner build --config=ci

go deeper

for a junior

Recall that build.yaml sits at the package root and that targets: $default: builders: is where per-builder settings go.

for a middle

Explain generate_for, options as BuilderOptions, the annotation-over-build.yaml rule for json_serializable, and the priority chain up to --define.

for a senior

Show you use generate_for to keep large builds fast and keep package-wide generator defaults in one reviewed file.

for a principal

Decide which generator settings a multi-package repo standardises centrally and which each package may override.

## Where build.yaml fits `build_runner` works without any configuration: each generator package declares, in **its own** `build.yaml`, which files it reads and writes and whether it applies automatically to packages that depend on it (`auto_apply: dependents`). Your package adds a `build.yaml` at its root only when you want to change that behaviour: limit a builder to some files, pass options, or reorder builders. ## Targets A **target** is a named set of source files. Almost every app uses one, `$default`, which stands for the target named after the package. Each target can have: - **`sources`**: globs for the files in the target (default: the whole package); - **`dependencies`**: other targets it depends on (default: everything in `pubspec.yaml`); - **`builders`**: a map from builder key to its configuration. Builder keys have the form `package:builder`; when the builder has the same name as its package, the package name alone works, which is why `json_serializable:` appears on its own. You will also see the older `package|builder` spelling, as in `source_gen|combining_builder`. ## Per-builder settings | Key | Type | Meaning | |---|---|---| | `enabled` | bool | apply or skip this builder for the target; needed for builders with `auto_apply: none` | | `generate_for` | globs, or `include`/`exclude` map | the subset of the target's sources this builder runs on | | `options` | free-form map | passed to the builder as `BuilderOptions` | | `dev_options` / `release_options` | free-form map | per-key overrides for dev or `--release` builds | ```yaml targets: $default: builders: json_serializable: generate_for: - lib/models/**.dart options: explicit_to_json: true field_rename: snake freezed: options: format: true source_gen:combining_builder: options: preamble: "// coverage:ignore-file" ``` The **meaning of `options` belongs to the builder**. json_serializable documents that every `@JsonSerializable` field can be set here with its snake_case name (`any_map`, `checked`, `create_to_json`, `explicit_to_json`, `field_rename`, `include_if_null` and more), and that a value written on an annotation takes precedence. freezed accepts keys such as `format`, `copy_with` and `equal`. Check each builder's README for its keys; there is no shared vocabulary. ## How an option value is resolved build_config applies layers from lowest to highest priority: 1. the builder author's defaults in the generator's own `build.yaml`; 2. those defaults by build mode; 3. your target-level `options`; 4. your target-level `dev_options` or `release_options`; 5. `global_options` without a mode; 6. `global_options` by mode; 7. `--define` on the command line, for example `dart run build_runner build --define=json_serializable:json_serializable=explicit_to_json=true`; values that parse as JSON become lists or maps. `global_options:` is a top-level `build.yaml` section keyed by builder, useful when a setting should apply across all targets. ## Other knobs worth knowing - **Ordering**: a generator package can declare `runs_before`, and a builder can declare `required_inputs`; freezed runs before json_serializable this way. You rarely need to change it. - **Alternate files**: `--config=name` (or `-c name`) reads `build.name.yaml` instead of `build.yaml`, which some teams use for CI-only settings. - **Scope for speed**: without `generate_for`, a builder considers every Dart file in the target. On large apps, restricting model generators to `lib/models/**` or mocks to `test/**` shortens builds noticeably. - **Workspaces**: with `--workspace`, package-specific options come from each package's `build.yaml`, and global options from the workspace root's. ## Common mistakes - Putting builder settings in `pubspec.yaml` or `analysis_options.yaml`; neither is read by build_runner. - Expecting `build.yaml` to override an explicit annotation argument; for json_serializable the annotation wins. - Restricting `generate_for` and then adding a model outside the glob, which silently gets no `.g.dart` generated. ## Example: one package, three generators A Flutter app using json_serializable for API models, freezed for state classes and mockito for tests might keep a single reviewed file: 1. `json_serializable` limited to `lib/data/**` with `field_rename: snake`, because the backend sends snake_case keys; 2. `freezed` limited to `lib/domain/**` and `lib/state/**` with `format: true`; 3. `mockito`'s builder limited to `test/**`, since mocks are never needed in `lib/`; 4. `source_gen:combining_builder` with a `preamble` of `// coverage:ignore-file`, so generated code does not distort coverage reports. With those globs, a full build touches a fraction of the package's files, and the settings that change generated output are visible in one place during review. When a teammate wonders why a JSON key is snake_case, the answer is in `build.yaml` rather than scattered across annotations. ## Why interviewers ask The question separates people who have only copied a `dart run build_runner build` line from people who have maintained a codegen-heavy app: knowing where options live, which layer wins, and how to keep builds fast.

  • A new model under lib/features/ gets no .g.dart although it is annotated; what do you check first?
    The builder's `generate_for` in `build.yaml`. If it was restricted to `lib/models/**`, files elsewhere are never passed to json_serializable, so nothing is generated. Widen the glob or move the model, then confirm the file declares the matching `part` directive.
  • When would you use global_options instead of target options?
    When a package defines several targets, or in a workspace, and one setting should apply to a builder everywhere. `global_options` is applied after target-level configuration and before `--define`, so it overrides each target's value for that builder in one place.

saying these in an interview costs you the question

  • Configures json_serializable options in pubspec.yaml
  • Believes build.yaml options override values set on the annotation
  • Thinks generate_for is about output paths rather than input files
  • Expects a builder with auto_apply: none to run without enabled: true
  • Assumes every builder understands the same option names