Dart standards (language and tooling)
Criteria verified as of August 2026. Re-verify on the web before committing to anything (§8).
1. Scope and triggers
Applies to the Dart language and its toolchain, whether used in Flutter, in a CLI, on a server or
on the web. Triggers: .dart, pubspec.yaml, pubspec.lock, analysis_options.yaml,
dart_test.yaml, build.yaml, dart analyze / format / fix / test / compile / doc,
dart pub, build_runner, freezed, json_serializable, ffigen, jnigen, publishing to pub.dev.
Covers: the type system and null safety, patterns and sealed, dependency resolution and the lockfile,
lint and formatting, asynchrony (Future/Stream), isolates, error handling, code generation,
tests, compilation (JIT/AOT/JS/Wasm), native interop and package publishing.
Not applicable: see mobile-standards (the design of the mobile app is theirs: app
architecture, navigation and platform lifecycle, permissions, secure on-device storage
—Keychain/Keystore—, signing, publishing to the App Store and Google Play, rollout and updates,
perceived performance and mobile accessibility; here only the Dart language, pub, the lints, the
formatting, the tests and the compilation), webassembly-standards (the Wasm compilation target,
the runtime, the sandbox limits and the artifact size are theirs; here only the Dart side
—dart:js_interop, package:web, --wasm— of the crossing), api-design-standards (design of the
HTTP/GraphQL/gRPC contract that Dart code consumes or exposes), appsec-standards (threat modelling
and agnostic vulnerability classes; here the concrete Dart sinks),
identity-access-management-standards (OAuth 2.1/OIDC flows and token issuance; here only their
consumption), secrets-management-standards (custody and rotation; here only the rule that a client
binary does not store secrets), cicd-standards (the pipeline that runs the gates of §4),
vulnerability-management-standards (triage and patching SLA for the CVEs the SCA finds),
observability-standards (telemetry pipeline; here only the instrumentation in code),
git-workflow-standards (branch, commits and SemVer tagging), typescript-standards (the web
frontend that is not Flutter).
Rule zero: first detect the real project (the sdk: constraint of the pubspec.yaml, the pinned
Flutter version, the lint set active in analysis_options.yaml) and respect its conventions; these
criteria govern what is new and what is marked as debt.
2. Default toolchain
Verify the latest version on the web before pinning it in a real project (§8).
| Piece |
Choice |
Minimum / status as of Aug 2026 |
Why |
| Dart SDK |
The one that comes with Flutter's stable channel if the project is Flutter |
Dart 3.12.2 (bundled in Flutter stable 3.44.8, 2026-07-23) |
In a Flutter project you do not choose the Dart version: the Flutter SDK imposes it. Pinning Dart separately is a guaranteed source of misalignment |
| Dart SDK (non-Flutter project) |
Latest stable of dart-lang/sdk |
3.12.x stable; 3.13.0 in beta (Flutter 3.47.0-0.3.pre bundles it) |
Dart is non-Flutter too (CLI, backend, tooling); there you do pin it directly |
Constraint in pubspec.yaml |
environment: sdk: ^3.12.0 (caret, not an open range) |
— |
The caret expresses "this major, from this minor"; a hand-written >=x <4.0.0 invites mistakes |
| SDK version management |
fvm or the CI's manager, pinned in the repo |
— |
"Whatever version each machine has" is a reproducibility failure, not a preference |
| Lint (non-Flutter package) |
package:lints, recommended set |
— |
The Dart team's own recommendation: "We recommend using at least the core rule set… Or, better yet, use the recommended rule set" |
| Lint (Flutter project) |
package:flutter_lints |
— |
Official doc: "If you're writing Flutter code, use the rule set in the flutter_lints package, which builds on lints". It is a superset of recommended, which in turn is one of core |
| Optional strict lint |
very_good_analysis (10.3.0, MIT, from Very Good Ventures) |
— |
Third-party, not official. Adopt it only by an explicit team decision: it adds aggressive style rules and you do not control its major cadence |
| Formatting |
dart format |
— |
The SDK's single formatter, with no style options. Zero debate in PRs |
| Analysis |
dart analyze --fatal-infos in CI |
— |
Without --fatal-infos the infos accumulate until they are permanent noise |
| Tests |
package:test (dart test / flutter test) |
— |
The ecosystem's only runner |
| Mocks |
mocktail (1.0.5, MIT) by default; mockito only if it is already there |
— |
mocktail needs no codegen: "no manual mocks or code generation". mockito forces build_runner for every mock |
| Codegen |
build_runner + json_serializable; freezed only if the model justifies it |
— |
Macros were cancelled (Jan 2025, repo archived): codegen via build_runner is the official path and it will be for a long time |
| Native interop |
dart:ffi + ffigen (C) / jnigen (Java-Kotlin) |
— |
Generated bindings, not hand-written |
| Web interop |
package:web + dart:js_interop |
— |
dart:html, package:js and dart:js are not compatible with compilation to Wasm (§ compilation) |
3. Structure and conventions
Types and language
- Sound null safety is not an option: "Dart has enforced sound null safety since Dart 3, released
in May 2023". There is no "legacy code without null safety" that you can run on a 3.x SDK — what is
left of the 2.12–2.19 era is abandoned packages that were never migrated: they are not debt, they
are an upgrade blocker; they get replaced, not patched.
late only with a real initialisation invariant (late injection, initState). late to shut the
analyzer up is a ! with more letters: the failure moves from compile time to runtime.
! (bang) forbidden outside tests and proven invariants; use ?., ??, promotion via
if (x != null) or patterns.
- Explicit
required on every named parameter with no sensible default; no nullable "just in case"
that later gets dereferenced.
- Patterns, records and
sealed have been available since Dart 3.0: model domain variants with
a sealed class + an exhaustive switch (the analyzer checks exhaustiveness and will break the
build when you add a case — that is exactly the effect you want). Chains of if (x is A) over closed
hierarchies: veto.
records for local multiple returns; not as a substitute for a named domain type.
- Class modifiers (
final, base, interface, sealed) explicit in a package's public API: the
absence of a modifier declares "anyone can extend this", and that is a contract.
- Generics with
extends when there is a real constraint; dynamic vetoed except at an annotated
boundary (use Object? + narrowing).
- Immutability by default:
final on fields and locals, const where the value allows it.
Package and dependencies
pubspec.yaml: constraints with a caret (^1.2.3), never any and no ranges open at the top.
dependency_overrides is an emergency tool with an expiry date and a TODO linked to an issue, never
a stable state.
- Lockfile, per Dart's official criteria, which depend on the type of package:
- Application (Flutter app, CLI, service): "we recommend that you commit the
pubspec.lock
file. Versioning the pubspec.lock file ensures changes to transitive dependencies are
explicit". It is always versioned.
- Publishable package / library: "don't commit the
pubspec.lock file. Regenerating the
pubspec.lock file lets you test your package against the latest compatible versions of its
dependencies". It is not versioned; in exchange, CI must have a job that resolves against the
latest permitted versions — that is the point of the recommendation, and without that job you
have merely lost reproducibility.
- Monorepos: pub workspaces (
workspace: in the root pubspec.yaml) rather than melos in new
projects; a shared resolution avoids skew between the repo's packages.
- Canonical layout:
lib/src/ for the implementation, lib/<package>.dart as the barrel that
exports the public API. What is not exported from lib/ is not API: importing
package:x/src/... from outside is a boundary violation, not a shortcut.
analysis_options.yaml at the root: include: of the chosen lint set + analyzer: language: strict-casts, strict-inference, strict-raw-types in new code. Exclude only generated files
(*.g.dart, *.freezed.dart), never your own code.
- Names:
lowerCamelCase for members, UpperCamelCase for types, snake_case for files and
packages. A _ prefix = private to the library, not to the class: keep that in mind when
splitting files.
- Document the public API of publishable packages with
///; dart doc in CI to detect broken
references.
Asynchrony
async/await by default; chained .then() only where await does not fit.
- No
Future left unawaited or unmanaged: enable unawaited_futures in the lint and use an
explicit unawaited(...) when fire-and-forget is deliberate. An orphan Future swallows its error
and shows up as an inexplicable failure months later.
Stream: single-subscription by default; broadcast only with a reason. Every subscription has
a cancel() in the corresponding dispose/close — uncancelled subscriptions are the number one
memory leak in Dart.
Completer only to adapt callback APIs; never as your own flow control mechanism.
- Explicit timeouts (
.timeout(...)) on every network or IPC operation. Without a timeout there is no
operability.
Isolates
- Dart does not share mutable memory between isolates: each has its own heap and event loop;
communication is by message passing (
SendPort/ReceivePort, Isolate.run). This eliminates
data races by construction — and it also eliminates the option of "just share the object".
- Usage rule: isolate = CPU-bound, not I/O-bound. I/O is already asynchronous in the main isolate;
putting it in an isolate only adds serialisation cost.
Isolate.run for one-off work; a long-lived isolate with ports only when the start-up cost and the
message volume justify it.
- The real cost is the serialisation of the message (with exceptions: transferable types such as
TransferableTypedData). Measure before assuming the isolate pays off.
Errors
- Criteria: exceptions for the exceptional (a bug, a broken invariant, an infrastructure
failure); a result type (
sealed class Result<T> with an exhaustive switch, or a third-party
Either) for what is expected and part of the contract: failed validation, 404, invalid
credential, no network. The criterion is "must the caller decide about this?" — if yes, it is a
return value, not an exception invisible in the signature. Dart has no checked exceptions: an
exception does not appear in the signature, and that is exactly the reason for the rule.
- Your own typed exceptions (
class PaymentDeclined implements Exception); never throw 'string'.
- A generic
catch (e) only at the boundary (request top level, runZonedGuarded, isolate handler)
and always rethrowing or logging with the StackTrace (on X catch (e, st)). An empty catch
or one that returns a silent default: veto.
- Distinguish
Error (a programmer bug: StateError, ArgumentError → they are not caught, they
are fixed) from Exception (a runtime condition → they are handled).
- Always release resources:
try/finally, close()/dispose() on every StreamSubscription,
HttpClient, file and ReceivePort.
Code generation
- Codegen is a permanent cost:
build_runner runs on every model change, on every clean checkout
and in every CI job, and in large projects it dominates build time. Every new generator is justified
in writing.
- Rule:
json_serializable yes (serialising by hand is error-prone and repetitive). freezed only
when you really need sealed unions + copyWith + structural equality across many types; if it is
four models, a hand-written sealed class is cheaper than the dependency and the generator.
- Generated files (
*.g.dart, *.freezed.dart): they are versioned if consumption requires it (a
published package, a reproducible build without the generator) and they are excluded from analysis
and coverage always. Decide a stance per repo and document it; the hybrid is what breaks.
- CI:
dart run build_runner build --delete-conflicting-outputs and git diff --exit-code —
out-of-date generated code = broken build, not a surprise locally.
Architecture and state management
Out of scope except for what is language: observable state is modelled with Stream/Listenable and
immutable types with sealed variants (loading/error/empty/data), and dependencies are injected
via the constructor. The choice of state framework and the app's architecture belong to
mobile-standards. What is a veto here: mutable global singletons and service locators as a
substitute for injection.
4. Quality: formatting, analysis, tests
CI gates, in increasing order of cost (they block the merge):
dart pub get (or flutter pub get) — in applications, against the versioned lockfile.
dart format --output=none --set-exit-if-changed . — non-negotiable, no exceptions. There are
no style options to argue about because the formatter does not offer any.
dart analyze --fatal-infos --fatal-warnings.
- Codegen verified (
build_runner + git diff --exit-code) if the repo uses generators.
dart test / flutter test with coverage (agreed threshold; a signal, not a target).
- Dependency SCA + informative
dart pub outdated.
- Golden tests and the release build (real compilation) at the end.
Testing
- Pyramid: mostly unit tests over the domain and pure logic (no platform, no network, no real
clock); just enough integration; few and stable widget/E2E.
package:test: group/test with names that describe behaviour; AAA; one failure reason per test.
setUp/tearDown for fixtures — no shared state between tests.
- Mandatory edges and errors: null/empty input, malformed JSON, timeout, network error, partial
response, cancellation, permission denied.
- Asynchrony in tests:
expectLater(stream, emitsInOrder([...])), fakeAsync/FakeAsync and injected
clocks. await Future.delayed(...) as a way of "waiting for something to happen": veto — it is
the number one source of flakiness.
- Mocks at the boundary (HTTP client, repository, platform), not of the domain types themselves.
mocktail by default; register registerFallbackValue for custom types.
- Golden tests only for the design system and high-value components. Operational warning: goldens
are sensitive to the rendering environment — pin them to one platform and toolchain version in
CI or you will spend more time regenerating
.pngs than testing. A golden that gets regenerated
"to see if it passes" proves nothing; delete it.
- Every fixed bug leaves a regression test. An unstable test: it gets fixed or deleted, never
retried in CI.
5. Stack security
- A client binary does not store secrets. Everything you compile into the app —API keys, private
endpoints, signing keys, third-party credentials— is public: it is extracted with
strings, with
a disassembler or by intercepting the traffic. Obfuscating (--obfuscate --split-debug-info) raises
the attacker's cost, it is not a confidentiality control. If a third party demands a secret key
from the client, the call goes through your backend: there is no correct alternative.
- Validation at the boundary: everything that comes in —an API response, a deeplink, a CLI
argument, a file, an environment variable, a message from another isolate— is parsed and validated
before touching the domain.
json_serializable validates shape, not semantics: ranges,
unknown enums and missing mandatory fields are yours. An as over a Map<String, dynamic> without a
check is a production crash waiting for the wrong payload.
- pub.dev dependencies: pub.dev does not audit the code. Its metrics (pub points, likes,
downloads, Dart 3 compatible badges, verified publisher) are a signal of maintenance and
ergonomics, not of security. Before adding a dependency: verified publisher, a public repository
with real activity, a recent last publication, the number of transitive deps and the licence read —
and check the name character by character (typosquatting). Bounded constraints + a lockfile in
applications; SCA and advisories (osv.dev, GitHub Advisories) in CI.
- Crypto:
package:cryptography or the platform APIs; never a home-made implementation.
Security randomness only with Random.secure() — Random() is a predictable PRNG and using it
for tokens, nonces or session IDs is a vulnerability, not a style slip.
- On-device storage and Keychain/Keystore: that belongs to
mobile-standards. Here only the rule:
nothing sensitive in cleartext files or in unencrypted preferences.
- Logs: no PII, no tokens, no full response bodies. In release, no
print.
- Injection:
Process.run/Process.start with an argument list and runInShell: false —
never composing a shell line with external input. Parameterised SQL always.
- TLS: never override
badCertificateCallback to return true, not even "temporarily for
testing". If it appears in a diff, it is a review blocker.
dart:mirrors is outside AOT and must not appear in new code.
6. Performance and operability
- JIT in development, AOT in production. The JIT gives hot reload and fast iteration start-up; AOT
gives stable cold start and performance. Never profile in debug/JIT mode: the figures bear no
resemblance to release ones.
dart compile formats (verified in the official doc):
exe: a self-contained executable (Windows/macOS/Linux); it supports cross-compilation to Linux
targets (ARM, ARM64, RISCV64, x64) from Dart 3.8+. No dart:mirrors and no dart:developer.
aot-snapshot: an architecture-specific AOT module, run with dartaotruntime.
jit-snapshot: IR with optimised code from a training run; peak performance potentially better
than AOT if the training run is representative — and worse if it is not.
kernel: a portable .dill.
js: deployable JavaScript (the doc recommends webdev for production builds).
wasm: "Currently under development" in the dart compile doc — see §8, there is an apparent
discrepancy with the state of Flutter web.
- WasmGC in Flutter web:
flutter build web --wasm and flutter run -d chrome --wasm exist and
are documented as a supported path, but it is not a universal target: Chromium/V8 support WasmGC
from version 119; Firefox announced stable support in 120 but "currently doesn't work" because
of a known limitation; Safari already supports WasmGC but has a compatibility bug; and on iOS
it does not work in any browser ("Flutter compiled to Wasm can't run on the iOS version of any
browser"), because they all use WebKit. Criteria: Wasm only with a JS fallback and with
measurement, not as the default. Hard migration consequence: dart:html, package:js and dart:js
are not compatible with compilation to Wasm — you have to migrate to package:web and
dart:js_interop, and review the differences in is/as and in Zone propagation in interop
callbacks. Diagnostics in production: --source-maps, and --no-strip-wasm only for staging/QA.
- Artifact size as a metric watched in CI (tree-shaking depends on there being no reflection and no
hidden dynamic entry points).
- Event loop: no long CPU-bound work in the main isolate — chunk it or move it to
Isolate.run. A
200 ms for is a dropped frame, not a detail.
- Profiling: DevTools (CPU profiler, memory, timeline) over a release/profile build.
Optimising without measuring is vetoed.
- Leaks: an uncancelled
StreamSubscription, an uncancelled Timer, an unclosed ReceivePort and
long-lived closures capturing large objects. It is the recurring pattern; look for it first.
- Observability: structured logs (
package:logging with your own handler, not print), uncaught
errors to runZonedGuarded/Isolate.current.addErrorListener, and crash reporting with symbols
uploaded when you obfuscate (without a saved --split-debug-info, release stack traces are
unreadable and obfuscation becomes a shot in the foot).
- Publishing to pub.dev:
dart pub publish --dry-run in CI, a strict CHANGELOG.md and SemVer,
example/, topics: in the pubspec, a verified publisher and publishing automated from CI with
OIDC (no pub tokens on laptops). The pub.dev metrics (pub points, likes, downloads) are a signal of
packaging quality, not of correctness.
7. Sustainability and prohibitions
Cadence
- Flutter project: you follow Flutter's stable channel and the Dart version comes bundled; the new
stable is adopted in weeks, not quarters — the lag is paid in package incompatibilities, not in
comfort. Never Flutter
master/main in production; beta only for testing.
- Dart publishes minors on an approximately quarterly cadence: read the changelog and the breaking
changes page before raising the SDK constraint.
- Dependencies: Renovate/Dependabot; security patches in <72 h;
dart pub outdated reviewed
periodically. A dependency not published in >18 months or without support for the current Dart major
gets reviewed or replaced.
- Toolchain pinned in the repo (fvm /
.tool-versions / CI image), not on each person's machine.
LIST OF PROHIBITIONS (they block review)
- ❌
dynamic as an escape from the type system; as without a prior check over external data.
- ❌
! (bang) for convenience; late to silence the analyzer.
- ❌ Chains of
is/if over sealed hierarchies instead of an exhaustive switch.
- ❌ A
Future without await or an explicit unawaited(); .then() without onError/catchError.
- ❌ An empty
catch, a catch that returns a silent default, or catching without the StackTrace.
- ❌
throw of a String or of a type that does not implement Exception/Error.
- ❌ A
StreamSubscription, Timer or ReceivePort left uncancelled/unclosed.
- ❌
await Future.delayed(...) as a wait in tests; tests dependent on order, on the real clock or on
public network services.
- ❌ FORBIDDEN to disable
dart format or to keep an alternative formatter.
- ❌ FORBIDDEN to ignore diagnostics with
// ignore: without a reason comment, and ignore_for_file
in your own code files.
- ❌ Excluding your own code from
analysis_options.yaml so that analysis passes.
- ❌
any as a version constraint; dependency_overrides without a linked issue and an exit date.
- ❌ An application (app/CLI/service) without a versioned
pubspec.lock; a publishable package
with a versioned lock and without a CI job that resolves against the latest permitted.
- ❌ Importing
package:other/src/... (another package's private API).
- ❌ Secrets, API keys or private endpoints compiled into the client binary; trusting obfuscation as a
confidentiality control.
- ❌
Random() for tokens, nonces, session IDs or any security use (use Random.secure()).
- ❌
badCertificateCallback => true, not even "temporarily".
- ❌
Process.run with a shell line composed from external input.
- ❌
print in release code; logs with PII or tokens.
- ❌
dart:mirrors in new code.
- ❌ Adding a code generator without justifying its build cost in writing; committing out-of-date
generated code.
- ❌ Mutable global singletons and service locators instead of constructor injection.
- ❌ Pinning Dart/Flutter versions from memory or from a blog instead of the official changelog.
8. Mandatory web verification
Before pinning any version, flag or capability in a real project, verify online:
- The latest Flutter stable and its bundled Dart:
releases_linux.json /
releases_macos.json from storage.googleapis.com/flutter_infra_release/releases/ (fields
current_release.stable, version, dart_sdk_version) — it is the source that does not lie. As of
Aug 2026: stable Flutter 3.44.8 / Dart 3.12.2 (2026-07-23); beta 3.47.0-0.3.pre / Dart
3.13.0-282.3.
- The latest Dart stable for non-Flutter projects:
dart.dev/get-dart/archive and the official
blog. Careful: the dart-lang/sdk Atom feed is dominated by -dev builds (as of 2026-08-03 the
most recent was 3.14.0-85.0.dev) — a -dev is not the stable. Declared discrepancy as of
Aug 2026: the Dart blog had no announcement post for 3.13 yet and the breaking changes page was
still marking it as unpublished, while Flutter beta already bundles 3.13.0-beta. Confirm the state
of 3.13/3.14 before assuming anything.
- The recommended lint set today:
dart.dev/tools/linter-rules (lints: core/recommended vs
flutter_lints) and the latest major of those packages on pub.dev. very_good_analysis is
third-party: check version, licence and activity before adopting it.
- The state of WasmGC as a Flutter/Dart target:
docs.flutter.dev/platform-integration/web/wasm
and dart.dev/tools/dart-compile. Declared discrepancy as of Aug 2026: the dart compile doc
describes the wasm output as "currently under development", while the Flutter web doc documents
flutter build web --wasm as a supported path with browser caveats. They are not the same thing
(standalone CLI vs. the Flutter web pipeline) but the exact state of each must be confirmed before
committing to a platform; and Firefox/Safari/iOS support changes — look at the linked bugs
(bugzilla 1788206, webkit 267291) before ruling it out or accepting it.
- The state of macros / static metaprogramming: cancelled in Jan 2025 (
dart-lang/macros
archived); check whether augmentations have already landed in the language, because that would
change the codegen criteria of §3.
- SDK breaking changes before raising the constraint:
dart.dev/resources/breaking-changes and
dart.dev/changelog.
- CVEs and advisories for every direct dependency (osv.dev, GitHub Advisories) and the maintenance
status on pub.dev before pinning it.
Declared gap as of Aug 2026: this drafting did not check the current state of mockito
(version, maintenance) or that of freezed 3.x beyond the context of the macros cancellation; nor the
concrete figures for build_runner cost in large projects, which are repo-dependent and must be
measured, not quoted.
If the web contradicts this document, the web wins — flag the discrepancy.
1---2name: dart-standards3description: Use when writing or reviewing Dart code and its tooling - .dart files, pubspec.yaml, pubspec.lock, analysis_options.yaml, dart analyze, dart format, dart fix, dart test, dart compile exe/js/wasm, dart run build_runner, package:lints, package:flutter_lints, very_good_analysis, freezed, json_serializable, package:test, mocktail, mockito, golden tests, isolates, Future/Stream/async, dart:ffi, ffigen, jnigen, package:web and dart:js_interop, pub get/upgrade/publish, pub workspaces, or publishing to pub.dev.4---56# Dart standards (language and tooling)78Criteria verified as of **August 2026**. Re-verify on the web before committing to anything (§8).910## 1. Scope and triggers1112Applies to the **Dart language and its toolchain**, whether used in Flutter, in a CLI, on a server or13on the web. Triggers: `.dart`, `pubspec.yaml`, `pubspec.lock`, `analysis_options.yaml`,14`dart_test.yaml`, `build.yaml`, `dart analyze` / `format` / `fix` / `test` / `compile` / `doc`,15`dart pub`, `build_runner`, `freezed`, `json_serializable`, `ffigen`, `jnigen`, publishing to pub.dev.1617Covers: the type system and null safety, patterns and `sealed`, dependency resolution and the lockfile,18lint and formatting, asynchrony (`Future`/`Stream`), isolates, error handling, code generation,19tests, compilation (JIT/AOT/JS/Wasm), native interop and package publishing.2021**Not applicable**: see `mobile-standards` (**the design of the mobile app is theirs**: app22architecture, navigation and platform lifecycle, permissions, secure on-device storage23—Keychain/Keystore—, signing, publishing to the App Store and Google Play, rollout and updates,24perceived performance and mobile accessibility; **here only the Dart language, `pub`, the lints, the25formatting, the tests and the compilation**), `webassembly-standards` (the Wasm compilation target,26the runtime, the sandbox limits and the artifact size are theirs; here only the Dart side27—`dart:js_interop`, `package:web`, `--wasm`— of the crossing), `api-design-standards` (design of the28HTTP/GraphQL/gRPC contract that Dart code consumes or exposes), `appsec-standards` (threat modelling29and agnostic vulnerability classes; here the concrete Dart sinks),30`identity-access-management-standards` (OAuth 2.1/OIDC flows and token issuance; here only their31consumption), `secrets-management-standards` (custody and rotation; here only the rule that a client32binary does not store secrets), `cicd-standards` (the pipeline that runs the gates of §4),33`vulnerability-management-standards` (triage and patching SLA for the CVEs the SCA finds),34`observability-standards` (telemetry pipeline; here only the instrumentation in code),35`git-workflow-standards` (branch, commits and SemVer tagging), `typescript-standards` (the web36frontend that is not Flutter).3738**Rule zero**: first detect the real project (the `sdk:` constraint of the `pubspec.yaml`, the pinned39Flutter version, the lint set active in `analysis_options.yaml`) and respect its conventions; these40criteria govern what is new and what is marked as debt.4142## 2. Default toolchain4344> Verify the latest version on the web before pinning it in a real project (§8).4546| Piece | Choice | Minimum / status as of Aug 2026 | Why |47|---|---|---|---|48| Dart SDK | **The one that comes with Flutter's `stable` channel** if the project is Flutter | Dart **3.12.2** (bundled in Flutter stable **3.44.8**, 2026-07-23) | In a Flutter project **you do not choose the Dart version**: the Flutter SDK imposes it. Pinning Dart separately is a guaranteed source of misalignment |49| Dart SDK (non-Flutter project) | **Latest stable of dart-lang/sdk** | 3.12.x stable; **3.13.0 in beta** (Flutter 3.47.0-0.3.pre bundles it) | Dart is *non-Flutter* too (CLI, backend, tooling); there you do pin it directly |50| Constraint in `pubspec.yaml` | `environment: sdk: ^3.12.0` (caret, not an open range) | — | The caret expresses "this major, from this minor"; a hand-written `>=x <4.0.0` invites mistakes |51| SDK version management | **fvm** or the CI's manager, pinned in the repo | — | "Whatever version each machine has" is a reproducibility failure, not a preference |52| Lint (non-Flutter package) | **`package:lints`**, **`recommended`** set | — | The Dart team's own recommendation: *"We recommend using at least the `core` rule set… Or, better yet, use the `recommended` rule set"* |53| Lint (Flutter project) | **`package:flutter_lints`** | — | Official doc: *"If you're writing Flutter code, use the rule set in the `flutter_lints` package, which builds on `lints`"*. It is a superset of `recommended`, which in turn is one of `core` |54| Optional strict lint | `very_good_analysis` (10.3.0, MIT, from Very Good Ventures) | — | **Third-party, not official.** Adopt it only by an explicit team decision: it adds aggressive style rules and you do not control its major cadence |55| Formatting | **`dart format`** | — | The SDK's single formatter, with no style options. Zero debate in PRs |56| Analysis | **`dart analyze --fatal-infos`** in CI | — | Without `--fatal-infos` the infos accumulate until they are permanent noise |57| Tests | **`package:test`** (`dart test` / `flutter test`) | — | The ecosystem's only runner |58| Mocks | **`mocktail`** (1.0.5, MIT) by default; `mockito` only if it is already there | — | `mocktail` needs no codegen: *"no manual mocks or code generation"*. `mockito` forces `build_runner` for every mock |59| Codegen | `build_runner` + `json_serializable`; `freezed` only if the model justifies it | — | **Macros were cancelled** (Jan 2025, repo archived): codegen via `build_runner` is the official path and it will be for a long time |60| Native interop | `dart:ffi` + **`ffigen`** (C) / **`jnigen`** (Java-Kotlin) | — | Generated bindings, not hand-written |61| Web interop | **`package:web`** + **`dart:js_interop`** | — | `dart:html`, `package:js` and `dart:js` **are not compatible with compilation to Wasm** (§ compilation) |6263## 3. Structure and conventions6465**Types and language**6667- **Sound null safety is not an option**: *"Dart has enforced sound null safety since Dart 3, released68 in May 2023"*. There is no "legacy code without null safety" that you can run on a 3.x SDK — what is69 left of the 2.12–2.19 era is abandoned packages that were never migrated: **they are not debt, they70 are an upgrade blocker**; they get replaced, not patched.71- `late` only with a real initialisation invariant (late injection, `initState`). `late` to shut the72 analyzer up is a `!` with more letters: **the failure moves from compile time to runtime**.73- `!` (bang) forbidden outside tests and proven invariants; use `?.`, `??`, promotion via74 `if (x != null)` or patterns.75- Explicit `required` on every named parameter with no sensible default; no nullable "just in case"76 that later gets dereferenced.77- **Patterns, records and `sealed`** have been available since **Dart 3.0**: model domain variants with78 a `sealed class` + an **exhaustive** `switch` (the analyzer checks exhaustiveness and will break the79 build when you add a case — that is exactly the effect you want). Chains of `if (x is A)` over closed80 hierarchies: veto.81- `records` for local multiple returns; **not** as a substitute for a named domain type.82- Class modifiers (`final`, `base`, `interface`, `sealed`) explicit in a package's public API: the83 absence of a modifier declares "anyone can extend this", and that is a contract.84- Generics with `extends` when there is a real constraint; `dynamic` vetoed except at an annotated85 boundary (use `Object?` + narrowing).86- Immutability by default: `final` on fields and locals, `const` where the value allows it.8788**Package and dependencies**8990- `pubspec.yaml`: constraints with a caret (`^1.2.3`), **never `any`** and no ranges open at the top.91 `dependency_overrides` is an emergency tool with an expiry date and a TODO linked to an issue, never92 a stable state.93- **Lockfile**, per Dart's official criteria, which depend on the type of package:94 - **Application** (Flutter app, CLI, service): *"we recommend that you commit the `pubspec.lock`95 file. Versioning the `pubspec.lock` file ensures changes to transitive dependencies are96 explicit"*. It is **always** versioned.97 - **Publishable package / library**: *"don't commit the `pubspec.lock` file. Regenerating the98 `pubspec.lock` file lets you test your package against the latest compatible versions of its99 dependencies"*. It is not versioned; in exchange, **CI must have a job that resolves against the100 latest permitted versions** — that is the point of the recommendation, and without that job you101 have merely lost reproducibility.102- Monorepos: **pub workspaces** (`workspace:` in the root `pubspec.yaml`) rather than `melos` in new103 projects; a shared resolution avoids skew between the repo's packages.104- Canonical layout: `lib/src/` for the implementation, `lib/<package>.dart` as the *barrel* that105 exports the public API. **What is not exported from `lib/` is not API**: importing106 `package:x/src/...` from outside is a boundary violation, not a shortcut.107- `analysis_options.yaml` at the root: `include:` of the chosen lint set + `analyzer: language:108 strict-casts, strict-inference, strict-raw-types` in new code. Exclude **only** generated files109 (`*.g.dart`, `*.freezed.dart`), never your own code.110- Names: `lowerCamelCase` for members, `UpperCamelCase` for types, `snake_case` for files and111 packages. A `_` prefix = private to the **library**, not to the class: keep that in mind when112 splitting files.113- Document the public API of publishable packages with `///`; `dart doc` in CI to detect broken114 references.115116**Asynchrony**117118- `async`/`await` by default; chained `.then()` only where `await` does not fit.119- **No `Future` left unawaited or unmanaged**: enable `unawaited_futures` in the lint and use an120 explicit `unawaited(...)` when fire-and-forget is deliberate. An orphan `Future` swallows its error121 and shows up as an inexplicable failure months later.122- `Stream`: **single-subscription by default**; `broadcast` only with a reason. Every subscription has123 a `cancel()` in the corresponding `dispose`/`close` — uncancelled subscriptions are the number one124 memory leak in Dart.125- `Completer` only to adapt callback APIs; never as your own flow control mechanism.126- Explicit timeouts (`.timeout(...)`) on every network or IPC operation. Without a timeout there is no127 operability.128129**Isolates**130131- **Dart does not share mutable memory between isolates**: each has its own heap and event loop;132 communication is by message passing (`SendPort`/`ReceivePort`, `Isolate.run`). This eliminates133 *data races* by construction — and it also eliminates the option of "just share the object".134- Usage rule: **isolate = CPU-bound**, not I/O-bound. I/O is already asynchronous in the main isolate;135 putting it in an isolate only adds serialisation cost.136- `Isolate.run` for one-off work; a long-lived isolate with ports only when the start-up cost and the137 message volume justify it.138- The real cost is the **serialisation of the message** (with exceptions: transferable types such as139 `TransferableTypedData`). Measure before assuming the isolate pays off.140141**Errors**142143- **Criteria**: exceptions for the *exceptional* (a bug, a broken invariant, an infrastructure144 failure); a **result type** (`sealed class Result<T>` with an exhaustive `switch`, or a third-party145 `Either`) for what is **expected and part of the contract**: failed validation, 404, invalid146 credential, no network. The criterion is "must the caller decide about this?" — if yes, it is a147 return value, not an exception invisible in the signature. Dart has no checked exceptions: an148 exception **does not appear in the signature**, and that is exactly the reason for the rule.149- Your own typed exceptions (`class PaymentDeclined implements Exception`); never `throw 'string'`.150- A generic `catch (e)` only at the boundary (request top level, `runZonedGuarded`, isolate handler)151 and **always rethrowing or logging with the `StackTrace`** (`on X catch (e, st)`). An empty `catch`152 or one that returns a silent default: veto.153- Distinguish `Error` (a programmer bug: `StateError`, `ArgumentError` → **they are not caught**, they154 are fixed) from `Exception` (a runtime condition → they are handled).155- Always release resources: `try/finally`, `close()`/`dispose()` on every `StreamSubscription`,156 `HttpClient`, file and `ReceivePort`.157158**Code generation**159160- Codegen is a **permanent cost**: `build_runner` runs on every model change, on every clean checkout161 and in every CI job, and in large projects it dominates build time. Every new generator is justified162 in writing.163- Rule: `json_serializable` yes (serialising by hand is error-prone and repetitive). `freezed` **only**164 when you really need sealed unions + `copyWith` + structural equality across many types; if it is165 four models, a hand-written `sealed class` is cheaper than the dependency and the generator.166- Generated files (`*.g.dart`, `*.freezed.dart`): they **are versioned** if consumption requires it (a167 published package, a reproducible build without the generator) and they **are excluded from analysis168 and coverage** always. Decide a stance per repo and document it; the hybrid is what breaks.169- CI: `dart run build_runner build --delete-conflicting-outputs` and **`git diff --exit-code`** —170 out-of-date generated code = broken build, not a surprise locally.171172**Architecture and state management**173174Out of scope except for what is language: observable state is modelled with `Stream`/`Listenable` and175**immutable** types with `sealed` variants (loading/error/empty/data), and dependencies are injected176via the constructor. The choice of state framework and the app's architecture belong to177`mobile-standards`. What is a veto here: **mutable global singletons** and service locators as a178substitute for injection.179180## 4. Quality: formatting, analysis, tests181182**CI gates, in increasing order of cost (they block the merge):**1831841. `dart pub get` (or `flutter pub get`) — in applications, against the versioned lockfile.1852. `dart format --output=none --set-exit-if-changed .` — **non-negotiable, no exceptions**. There are186 no style options to argue about because the formatter does not offer any.1873. `dart analyze --fatal-infos --fatal-warnings`.1884. Codegen verified (`build_runner` + `git diff --exit-code`) if the repo uses generators.1895. `dart test` / `flutter test` with coverage (agreed threshold; **a signal, not a target**).1906. Dependency SCA + informative `dart pub outdated`.1917. Golden tests and the release build (real compilation) at the end.192193**Testing**194195- **Pyramid**: mostly unit tests over the domain and pure logic (no platform, no network, no real196 clock); just enough integration; few and stable widget/E2E.197- `package:test`: `group`/`test` with names that describe behaviour; AAA; one failure reason per test.198 `setUp`/`tearDown` for fixtures — no shared state between tests.199- **Mandatory edges and errors**: null/empty input, malformed JSON, timeout, network error, partial200 response, cancellation, permission denied.201- Asynchrony in tests: `expectLater(stream, emitsInOrder([...]))`, `fakeAsync`/`FakeAsync` and injected202 clocks. **`await Future.delayed(...)` as a way of "waiting for something to happen": veto** — it is203 the number one source of flakiness.204- Mocks at the **boundary** (HTTP client, repository, platform), not of the domain types themselves.205 `mocktail` by default; register `registerFallbackValue` for custom types.206- **Golden tests** only for the design system and high-value components. Operational warning: goldens207 are **sensitive to the rendering environment** — pin them to one platform and toolchain version in208 CI or you will spend more time regenerating `.png`s than testing. A golden that gets regenerated209 "to see if it passes" proves nothing; delete it.210- Every fixed bug leaves a **regression test**. An unstable test: it gets fixed or deleted, never211 retried in CI.212213## 5. Stack security214215- **A client binary does not store secrets.** Everything you compile into the app —API keys, private216 endpoints, signing keys, third-party credentials— **is public**: it is extracted with `strings`, with217 a disassembler or by intercepting the traffic. Obfuscating (`--obfuscate --split-debug-info`) raises218 the attacker's cost, **it is not a confidentiality control**. If a third party demands a secret key219 from the client, the call goes through your backend: there is no correct alternative.220- **Validation at the boundary**: everything that comes in —an API response, a deeplink, a CLI221 argument, a file, an environment variable, a message from another isolate— is parsed and validated222 before touching the domain. `json_serializable` validates **shape**, not **semantics**: ranges,223 unknown enums and missing mandatory fields are yours. An `as` over a `Map<String, dynamic>` without a224 check is a production crash waiting for the wrong payload.225- **pub.dev dependencies**: pub.dev **does not audit the code**. Its metrics (pub points, likes,226 *downloads*, *Dart 3 compatible* badges, *verified publisher*) are a signal of maintenance and227 ergonomics, **not of security**. Before adding a dependency: verified publisher, a public repository228 with real activity, a recent last publication, the number of transitive deps and the licence read —229 and check the name character by character (**typosquatting**). Bounded constraints + a lockfile in230 applications; SCA and advisories (osv.dev, GitHub Advisories) in CI.231- **Crypto**: `package:cryptography` or the platform APIs; never a home-made implementation.232 Security randomness **only** with `Random.secure()` — `Random()` is a predictable PRNG and using it233 for tokens, nonces or session IDs is a vulnerability, not a style slip.234- **On-device storage and Keychain/Keystore**: that belongs to `mobile-standards`. Here only the rule:235 nothing sensitive in cleartext files or in unencrypted preferences.236- **Logs**: no PII, no tokens, no full response bodies. In release, no `print`.237- **Injection**: `Process.run`/`Process.start` **with an argument list and `runInShell: false`** —238 never composing a shell line with external input. Parameterised SQL always.239- **TLS**: never override `badCertificateCallback` to return `true`, not even "temporarily for240 testing". If it appears in a diff, it is a review blocker.241- `dart:mirrors` is outside AOT and must not appear in new code.242243## 6. Performance and operability244245- **JIT in development, AOT in production.** The JIT gives hot reload and fast iteration start-up; AOT246 gives stable cold start and performance. Never profile in debug/JIT mode: the figures bear no247 resemblance to release ones.248- **`dart compile` formats** (verified in the official doc):249 - `exe`: a self-contained executable (Windows/macOS/Linux); it supports cross-compilation to Linux250 targets (ARM, ARM64, RISCV64, x64) from Dart 3.8+. **No `dart:mirrors` and no `dart:developer`.**251 - `aot-snapshot`: an architecture-specific AOT module, run with `dartaotruntime`.252 - `jit-snapshot`: IR with optimised code from a *training run*; peak performance potentially better253 than AOT **if the training run is representative** — and worse if it is not.254 - `kernel`: a portable `.dill`.255 - `js`: deployable JavaScript (the doc recommends `webdev` for production builds).256 - `wasm`: **"Currently under development"** in the `dart compile` doc — see §8, there is an apparent257 discrepancy with the state of Flutter web.258- **WasmGC in Flutter web**: `flutter build web --wasm` and `flutter run -d chrome --wasm` exist and259 are documented as a supported path, but **it is not a universal target**: Chromium/V8 support WasmGC260 from version 119; Firefox announced stable support in 120 but **"currently doesn't work"** because261 of a known limitation; Safari already supports WasmGC but has a **compatibility bug**; and on **iOS262 it does not work in any browser** (*"Flutter compiled to Wasm can't run on the iOS version of any263 browser"*), because they all use WebKit. **Criteria**: Wasm only with a JS *fallback* and with264 measurement, not as the default. Hard migration consequence: `dart:html`, `package:js` and `dart:js`265 **are not compatible** with compilation to Wasm — you have to migrate to `package:web` and266 `dart:js_interop`, and review the differences in `is`/`as` and in `Zone` propagation in interop267 callbacks. Diagnostics in production: `--source-maps`, and `--no-strip-wasm` only for staging/QA.268- **Artifact size** as a metric watched in CI (tree-shaking depends on there being no reflection and no269 hidden dynamic entry points).270- **Event loop**: no long CPU-bound work in the main isolate — chunk it or move it to `Isolate.run`. A271 200 ms `for` is a dropped frame, not a detail.272- **Profiling**: DevTools (CPU profiler, memory, timeline) **over a release/profile build**.273 Optimising without measuring is vetoed.274- **Leaks**: an uncancelled `StreamSubscription`, an uncancelled `Timer`, an unclosed `ReceivePort` and275 long-lived closures capturing large objects. It is the recurring pattern; look for it first.276- **Observability**: structured logs (`package:logging` with your own handler, not `print`), uncaught277 errors to `runZonedGuarded`/`Isolate.current.addErrorListener`, and crash reporting with symbols278 uploaded when you obfuscate (without a saved `--split-debug-info`, release stack traces are279 unreadable and obfuscation becomes a shot in the foot).280- **Publishing to pub.dev**: `dart pub publish --dry-run` in CI, a strict `CHANGELOG.md` and SemVer,281 `example/`, `topics:` in the pubspec, a verified publisher and publishing **automated from CI with282 OIDC** (no pub tokens on laptops). The pub.dev metrics (pub points, likes, downloads) are a signal of283 packaging quality, not of correctness.284285## 7. Sustainability and prohibitions286287**Cadence**288289- Flutter project: you follow Flutter's **stable** channel and the Dart version comes bundled; the new290 stable is adopted in weeks, not quarters — the lag is paid in package incompatibilities, not in291 comfort. Never Flutter `master`/`main` in production; `beta` only for testing.292- Dart publishes minors on an approximately quarterly cadence: read the changelog and the *breaking293 changes* page before raising the SDK constraint.294- Dependencies: Renovate/Dependabot; security patches in <72 h; `dart pub outdated` reviewed295 periodically. A dependency not published in >18 months or without support for the current Dart major296 gets reviewed or replaced.297- Toolchain pinned in the repo (fvm / `.tool-versions` / CI image), not on each person's machine.298299**LIST OF PROHIBITIONS** (they block review)300301- ❌ `dynamic` as an escape from the type system; `as` without a prior check over external data.302- ❌ `!` (bang) for convenience; `late` to silence the analyzer.303- ❌ Chains of `is`/`if` over `sealed` hierarchies instead of an exhaustive `switch`.304- ❌ A `Future` without `await` or an explicit `unawaited()`; `.then()` without `onError`/`catchError`.305- ❌ An empty `catch`, a `catch` that returns a silent default, or catching without the `StackTrace`.306- ❌ `throw` of a `String` or of a type that does not implement `Exception`/`Error`.307- ❌ A `StreamSubscription`, `Timer` or `ReceivePort` left uncancelled/unclosed.308- ❌ `await Future.delayed(...)` as a wait in tests; tests dependent on order, on the real clock or on309 public network services.310- ❌ FORBIDDEN to disable `dart format` or to keep an alternative formatter.311- ❌ FORBIDDEN to ignore diagnostics with `// ignore:` without a reason comment, and `ignore_for_file`312 in your own code files.313- ❌ Excluding your own code from `analysis_options.yaml` so that analysis passes.314- ❌ `any` as a version constraint; `dependency_overrides` without a linked issue and an exit date.315- ❌ An application (app/CLI/service) **without a versioned `pubspec.lock`**; a publishable package316 **with** a versioned lock and without a CI job that resolves against the latest permitted.317- ❌ Importing `package:other/src/...` (another package's private API).318- ❌ Secrets, API keys or private endpoints compiled into the client binary; trusting obfuscation as a319 confidentiality control.320- ❌ `Random()` for tokens, nonces, session IDs or any security use (use `Random.secure()`).321- ❌ `badCertificateCallback => true`, not even "temporarily".322- ❌ `Process.run` with a shell line composed from external input.323- ❌ `print` in release code; logs with PII or tokens.324- ❌ `dart:mirrors` in new code.325- ❌ Adding a code generator without justifying its build cost in writing; committing out-of-date326 generated code.327- ❌ Mutable global singletons and service locators instead of constructor injection.328- ❌ Pinning Dart/Flutter versions from memory or from a blog instead of the official changelog.329330## 8. Mandatory web verification331332Before pinning any version, flag or capability in a real project, **verify online**:3333341. **The latest Flutter stable and its bundled Dart**: `releases_linux.json` /335 `releases_macos.json` from `storage.googleapis.com/flutter_infra_release/releases/` (fields336 `current_release.stable`, `version`, `dart_sdk_version`) — it is the source that does not lie. As of337 Aug 2026: stable **Flutter 3.44.8 / Dart 3.12.2** (2026-07-23); beta **3.47.0-0.3.pre / Dart338 3.13.0-282.3**.3392. **The latest Dart stable** for non-Flutter projects: `dart.dev/get-dart/archive` and the official340 blog. **Careful**: the `dart-lang/sdk` Atom feed is dominated by `-dev` builds (as of 2026-08-03 the341 most recent was `3.14.0-85.0.dev`) — a `-dev` **is not** the stable. **Declared discrepancy as of342 Aug 2026**: the Dart blog had no announcement post for 3.13 yet and the *breaking changes* page was343 still marking it as unpublished, while Flutter beta already bundles 3.13.0-beta. Confirm the state344 of 3.13/3.14 before assuming anything.3453. **The recommended lint set today**: `dart.dev/tools/linter-rules` (`lints`: `core`/`recommended` vs346 `flutter_lints`) and the latest major of those packages on pub.dev. `very_good_analysis` is347 third-party: check version, licence and activity before adopting it.3484. **The state of WasmGC as a Flutter/Dart target**: `docs.flutter.dev/platform-integration/web/wasm`349 and `dart.dev/tools/dart-compile`. **Declared discrepancy as of Aug 2026**: the `dart compile` doc350 describes the `wasm` output as *"currently under development"*, while the Flutter web doc documents351 `flutter build web --wasm` as a supported path with browser caveats. They are not the same thing352 (standalone CLI vs. the Flutter web pipeline) but the exact state of each must be confirmed before353 committing to a platform; and Firefox/Safari/iOS support changes — look at the linked bugs354 (bugzilla 1788206, webkit 267291) before ruling it out or accepting it.3555. **The state of macros / static metaprogramming**: cancelled in Jan 2025 (`dart-lang/macros`356 archived); check whether **augmentations** have already landed in the language, because that would357 change the codegen criteria of §3.3586. **SDK breaking changes** before raising the constraint: `dart.dev/resources/breaking-changes` and359 `dart.dev/changelog`.3607. **CVEs and advisories** for every direct dependency (osv.dev, GitHub Advisories) and the maintenance361 status on pub.dev before pinning it.362363**Declared gap as of Aug 2026**: this drafting did not check the current state of `mockito`364(version, maintenance) or that of `freezed` 3.x beyond the context of the macros cancellation; nor the365concrete figures for `build_runner` cost in large projects, which are repo-dependent and must be366measured, not quoted.367368If the web contradicts this document, **the web wins** — flag the discrepancy.