Dartdoc Conventions — the public surface is a contract
Public code is read far more than it is written. A symbol with no leading _ is a contract callers depend on, so it carries a /// doc a reader understands without opening the body. Doc comments use /// and follow Effective Dart: Documentation. Applies whenever you add or change a public declaration, or write an in-body // comment.
Non-negotiable rules
- Every public declaration gets a
/// doc — public classes, constructors, methods, getters, top-level functions, typedefs, and fields. public_member_api_docs is an error with no "obvious member" exemption: it flags every undocumented public member, so a missing doc fails the build. Making a symbol public "just in case" is a review reject — make it _-private instead so it needs no doc and no contract.
///, never /** */. Dartdoc only recognizes ///. A JavaDoc block is silently ignored and the symbol reads as undocumented. slash_for_doc_comments flags it.
- First line is one standalone sentence ending in a period, in its own paragraph. Tools show only this sentence in API lists, so it must stand alone; a blank
/// line separates it from the body.
- Method/function docs start with a verb phrase (third person): "Returns…", "Schedules…", "Loads…", "Marks…". A boolean getter or
bool-returning method starts with "Whether…".
- Never restate the name.
/// The name. on String name, /// Returns the total. on total() — banned. If a public member has nothing to add beyond its name, that is the signal to make it _-private (a private member needs no doc, so the tension disappears). A member that must stay public still needs a real /// — never leave it public-and-undocumented; add meaning: units, ranges, nullability, throws, side effects, and the invariant the symbol enforces.
- Cross-link identifiers in
[brackets] so dartdoc resolves them: /// Throws [StateError] if [id] is unknown; see [copyWith]. comment_references warns on a broken link.
- In-body
// explains why, never what. The code already says what. Narrating comments (// loop over items) rot out of sync and become misinformation. Comment the reason, the gotcha, or the invariant.
- Restate an enforced invariant at its enforcement point. Where one line upholds a guarantee — an ordering, a clamp, a persist-before-publish, a canonical-unit conversion — a terse
// states it so a diff that weakens it gets an unmissable flag. Same for a magic constant: cite where the number comes from.
- Docs change in the same diff as the code. A wrong doc is worse than none. Every comment your change touches must still be true — put it on the PR checklist.
- One library doc per exported barrel. The public entry point (e.g.
my_package.dart) gets a /// library doc above the library; directive. dangling_library_doc_comments is an error — a leading /// with no attached declaration must be a real library doc, not an orphan above a blank line or an export.
Documenting a value type
/// A note the user has authored and can later edit or archive.
///
/// Immutable; derive changes with [copyWith]. [updatedAt] is UTC and never
/// precedes [createdAt].
class Note {
/// Creates a note; [updatedAt] must not precede [createdAt].
const Note({
required this.id,
required this.title,
required this.createdAt,
required this.updatedAt,
});
/// Stable unique identifier, assigned once at creation.
final String id;
/// Human-readable heading shown in lists; may be empty, never null.
final String title;
/// Creation instant, in **UTC**.
final DateTime createdAt;
/// Last-edit instant, in **UTC**; equals [createdAt] until first edited.
final DateTime updatedAt;
/// Returns a copy with the given fields replaced.
Note copyWith({String? title, DateTime? updatedAt}) => Note(
id: id,
title: title ?? this.title,
createdAt: createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
Every public field carries a ///, including title — under the enforced public_member_api_docs a field that "restates the name" is not a licence to drop the doc but a prompt to say something real (or make the field _-private). Document units and constraints explicitly where the type does not carry them: minor currency units, a 0–23 hour, an inclusive/exclusive range, what null means, "must be > 0".
Documenting behavior — throws, async, side effects
/// Schedules a reminder for [task] at [when].
///
/// [when] must be in the future; a past instant is clamped to now. Persists the
/// scheduled row before arming the OS notification, so a crash mid-call cannot
/// leave a notification the store never recorded.
///
/// Throws [PermissionDeniedException] if the OS denied notification permission.
Future<void> scheduleReminder(Task task, DateTime when) async {
// persist-before-arm: the store is the source of truth, the OS mirrors it
await _store.saveScheduled(task.id, when);
await _plugin.schedule(task.id, when);
}
State async behavior, what a method writes or mutates, and every exception it can throw. If a function is total (returns for every input, never throws), say so — it is a real guarantee callers rely on.
Library docs and the exported barrel
/// Pure-Dart domain core: value types, typed failures, and total functions.
///
/// No Flutter, Riverpod, or `dart:io` import — this purity is what lets the
/// core be unit-tested without a widget harness and reused across platforms.
library;
export 'src/note.dart';
export 'src/task.dart';
The /// attaches to the library; directive. Never leave a /// dangling above an export or a blank line.
Restating the invariant at the enforcement point
Where a symbol enforces a rule, document the rule as part of its contract and pin it in-body at the one line that upholds it:
/// Failure modes of a checkout. Switched exhaustively by the caller, so a new
/// case is a compile error at every call site.
sealed class CheckoutFailure {
/// Const base constructor, so subclasses can be `const`.
const CheckoutFailure();
}
/// The account balance was below the order total; no charge was made.
final class InsufficientFunds extends CheckoutFailure {
/// Creates an [InsufficientFunds] failure.
const InsufficientFunds();
}
/// Read-only view of the current order; mutated only through this notifier.
class OrderNotifier extends Notifier<Order> {
@override
Order build() => Order.empty();
/// Adds [item] and recomputes the total.
///
/// Persists via the repository before emitting, so a mid-write crash cannot
/// surface a line the store never saw.
Future<void> addItem(Item item) async {
// persist-before-emit: emitted state is always a state the store holds
await _repository.append(item);
state = state.withItem(item);
}
}
Anti-patterns
/** JavaDoc-style */ — dartdoc ignores it; the symbol reads as undocumented and fails public_member_api_docs.
- Restating the name —
/// Gets the id. on String get id. Delete it or add units/ranges/invariants.
- A paragraph before the one-sentence summary — the first sentence must stand alone in list views.
- Narrating mechanics in-body —
// loop over items, i++ // increment. Explain why, or delete.
- A magic constant with no source — a bare
3 for a cap with no comment saying where the number is defined.
- Stale docs describing pre-refactor parameters, or an invariant comment left on code that no longer honors it.
- Commented-out code "just in case" — git remembers.
// TODO with no owner or issue link — write // TODO(name): reason or nothing.
- Documenting private trivia while a public method sits undocumented.
- A dangling
/// above a blank line or an export — fails dangling_library_doc_comments.
- Banner / ASCII-art comment dividers that bloat files; rely on structure and naming.
Definition of done
Related skills
- See
lint-and-style-config for wiring public_member_api_docs, dangling_library_doc_comments, and comment_references as analyzer errors.
- See
naming-conventions for the role-suffix names (Notifier/Repository/Service/Failure) these docs describe.
- See
dart3-idioms-and-coding-standards for the sealed-type and total-function guarantees the docs promise.
- See
error-handling-typed-results for the Result/Failure contract a method doc must state.
References
1---2name: dartdoc-conventions3description: Enforces Effective-Dart documentation on the public surface — a `///` doc on every public class/method/getter/field/typedef, a one-sentence standalone summary that says WHY plus units/ranges/nullability/throws/side-effects (never a restatement of the name), verb-phrase method docs, "Whether…" boolean getters, `[bracket]` cross-links, one `library;` doc per exported barrel, in-body `//` that explains why not what, and the enforced invariant restated at its enforcement point — backed by `public_member_api_docs` and `dangling_library_doc_comments` as analyzer errors. Use when adding or reviewing a public API, a Notifier/provider, a Service interface, a sealed Failure, a value type, or preparing a package's dartdoc.4---56# Dartdoc Conventions — the public surface is a contract78Public code is read far more than it is written. A symbol with no leading `_` is a **contract** callers depend on, so it carries a `///` doc a reader understands without opening the body. Doc comments use `///` and follow *Effective Dart: Documentation*. Applies whenever you add or change a public declaration, or write an in-body `//` comment.910## Non-negotiable rules11121. **Every public declaration gets a `///` doc** — public classes, constructors, methods, getters, top-level functions, typedefs, and fields. `public_member_api_docs` is an **error** with no "obvious member" exemption: it flags *every* undocumented public member, so a missing doc fails the build. Making a symbol public "just in case" is a review reject — make it `_`-private instead so it needs no doc and no contract.132. **`///`, never `/** */`.** Dartdoc only recognizes `///`. A JavaDoc block is silently ignored and the symbol reads as undocumented. `slash_for_doc_comments` flags it.143. **First line is one standalone sentence ending in a period, in its own paragraph.** Tools show only this sentence in API lists, so it must stand alone; a blank `///` line separates it from the body.154. **Method/function docs start with a verb phrase** (third person): "Returns…", "Schedules…", "Loads…", "Marks…". A boolean getter or `bool`-returning method starts with "Whether…".165. **Never restate the name.** `/// The name.` on `String name`, `/// Returns the total.` on `total()` — banned. If a public member has nothing to add beyond its name, that is the signal to make it `_`-private (a private member needs no doc, so the tension disappears). A member that must stay public still needs a real `///` — never leave it public-and-undocumented; add meaning: **units, ranges, nullability, throws, side effects**, and the invariant the symbol enforces.176. **Cross-link identifiers in `[brackets]`** so dartdoc resolves them: `/// Throws [StateError] if [id] is unknown; see [copyWith].` `comment_references` warns on a broken link.187. **In-body `//` explains *why*, never *what*.** The code already says what. Narrating comments (`// loop over items`) rot out of sync and become misinformation. Comment the reason, the gotcha, or the invariant.198. **Restate an enforced invariant at its enforcement point.** Where one line upholds a guarantee — an ordering, a clamp, a persist-before-publish, a canonical-unit conversion — a terse `//` states it so a diff that weakens it gets an unmissable flag. Same for a magic constant: cite where the number comes from.209. **Docs change in the same diff as the code.** A wrong doc is worse than none. Every comment your change touches must still be true — put it on the PR checklist.2110. **One library doc per exported barrel.** The public entry point (e.g. `my_package.dart`) gets a `///` library doc above the `library;` directive. `dangling_library_doc_comments` is an **error** — a leading `///` with no attached declaration must be a real library doc, not an orphan above a blank line or an `export`.2223## Documenting a value type2425```dart26/// A note the user has authored and can later edit or archive.27///28/// Immutable; derive changes with [copyWith]. [updatedAt] is UTC and never29/// precedes [createdAt].30class Note {31 /// Creates a note; [updatedAt] must not precede [createdAt].32 const Note({33 required this.id,34 required this.title,35 required this.createdAt,36 required this.updatedAt,37 });3839 /// Stable unique identifier, assigned once at creation.40 final String id;4142 /// Human-readable heading shown in lists; may be empty, never null.43 final String title;4445 /// Creation instant, in **UTC**.46 final DateTime createdAt;4748 /// Last-edit instant, in **UTC**; equals [createdAt] until first edited.49 final DateTime updatedAt;5051 /// Returns a copy with the given fields replaced.52 Note copyWith({String? title, DateTime? updatedAt}) => Note(53 id: id,54 title: title ?? this.title,55 createdAt: createdAt,56 updatedAt: updatedAt ?? this.updatedAt,57 );58}59```6061Every public field carries a `///`, including `title` — under the enforced `public_member_api_docs` a field that "restates the name" is not a licence to drop the doc but a prompt to say something real (or make the field `_`-private). Document units and constraints explicitly where the type does not carry them: minor currency units, a `0`–`23` hour, an inclusive/exclusive range, what `null` means, "must be `> 0`".6263## Documenting behavior — throws, async, side effects6465```dart66/// Schedules a reminder for [task] at [when].67///68/// [when] must be in the future; a past instant is clamped to now. Persists the69/// scheduled row before arming the OS notification, so a crash mid-call cannot70/// leave a notification the store never recorded.71///72/// Throws [PermissionDeniedException] if the OS denied notification permission.73Future<void> scheduleReminder(Task task, DateTime when) async {74 // persist-before-arm: the store is the source of truth, the OS mirrors it75 await _store.saveScheduled(task.id, when);76 await _plugin.schedule(task.id, when);77}78```7980State async behavior, what a method writes or mutates, and every exception it can throw. If a function is **total** (returns for every input, never throws), say so — it is a real guarantee callers rely on.8182## Library docs and the exported barrel8384```dart85/// Pure-Dart domain core: value types, typed failures, and total functions.86///87/// No Flutter, Riverpod, or `dart:io` import — this purity is what lets the88/// core be unit-tested without a widget harness and reused across platforms.89library;9091export 'src/note.dart';92export 'src/task.dart';93```9495The `///` attaches to the `library;` directive. Never leave a `///` dangling above an `export` or a blank line.9697## Restating the invariant at the enforcement point9899Where a symbol *enforces* a rule, document the rule as part of its contract and pin it in-body at the one line that upholds it:100101```dart102/// Failure modes of a checkout. Switched exhaustively by the caller, so a new103/// case is a compile error at every call site.104sealed class CheckoutFailure {105 /// Const base constructor, so subclasses can be `const`.106 const CheckoutFailure();107}108109/// The account balance was below the order total; no charge was made.110final class InsufficientFunds extends CheckoutFailure {111 /// Creates an [InsufficientFunds] failure.112 const InsufficientFunds();113}114115/// Read-only view of the current order; mutated only through this notifier.116class OrderNotifier extends Notifier<Order> {117 @override118 Order build() => Order.empty();119120 /// Adds [item] and recomputes the total.121 ///122 /// Persists via the repository before emitting, so a mid-write crash cannot123 /// surface a line the store never saw.124 Future<void> addItem(Item item) async {125 // persist-before-emit: emitted state is always a state the store holds126 await _repository.append(item);127 state = state.withItem(item);128 }129}130```131132## Anti-patterns133134- **`/** JavaDoc-style */`** — dartdoc ignores it; the symbol reads as undocumented and fails `public_member_api_docs`.135- **Restating the name** — `/// Gets the id.` on `String get id`. Delete it or add units/ranges/invariants.136- **A paragraph before the one-sentence summary** — the first sentence must stand alone in list views.137- **Narrating mechanics in-body** — `// loop over items`, `i++ // increment`. Explain *why*, or delete.138- **A magic constant with no source** — a bare `3` for a cap with no comment saying where the number is defined.139- **Stale docs** describing pre-refactor parameters, or an invariant comment left on code that no longer honors it.140- **Commented-out code** "just in case" — git remembers. **`// TODO` with no owner or issue link** — write `// TODO(name): reason` or nothing.141- **Documenting private trivia** while a public method sits undocumented.142- **A dangling `///`** above a blank line or an `export` — fails `dangling_library_doc_comments`.143- **Banner / ASCII-art comment dividers** that bloat files; rely on structure and naming.144145## Definition of done146147- [ ] Every public declaration in the touched code has a `///` doc; first line is a standalone sentence ending in a period.148- [ ] Method/function docs start with a verb; boolean getters start with "Whether".149- [ ] Units, ranges, nullability, throws/`Result`, and side effects documented where they exist.150- [ ] Identifiers cross-linked with `[brackets]`; no broken `comment_references`.151- [ ] Enforced invariants restated at their enforcement points; magic constants cite their source.152- [ ] No restated-name docs, no narrating `//`, no commented-out code, no ownerless TODO, no JavaDoc blocks.153- [ ] Each exported barrel has a `library;` doc; no dangling doc comments.154- [ ] Every comment the diff touched is still true.155- [ ] `dart analyze --fatal-infos --fatal-warnings` clean; `dart doc` generates without warnings for packages.156157## Related skills158159- See `lint-and-style-config` for wiring `public_member_api_docs`, `dangling_library_doc_comments`, and `comment_references` as analyzer errors.160- See `naming-conventions` for the role-suffix names (`Notifier`/`Repository`/`Service`/`Failure`) these docs describe.161- See `dart3-idioms-and-coding-standards` for the sealed-type and total-function guarantees the docs promise.162- See `error-handling-typed-results` for the `Result`/`Failure` contract a method doc must state.163164## References165166- [Effective Dart: Documentation](https://dart.dev/effective-dart/documentation)167- [`dart doc` tool](https://dart.dev/tools/dart-doc)168- [Linter rules](https://dart.dev/tools/linter-rules) — `public_member_api_docs`, `dangling_library_doc_comments`, `comment_references`, `slash_for_doc_comments`169- [Developing packages & plugins](https://docs.flutter.dev/packages-and-plugins/developing-packages)