Convert to Primary Constructor: Dart 3.13's IDE Refactoring
Dart 3.13 ships IDE refactorings that convert in-body constructors to primary constructors and back. Here's how to use them safely.
Published on • October 20, 2026
AI Assistant

Primary constructors went stable in Dart 3.13, and the feature alone can shrink a class from ten lines to one. But features that rewrite how your declarations look only pay off if you don’t have to rewrite everything by hand. Dart 3.13 addresses that directly: alongside the language change, the release ships four new IDE refactorings for converting constructors back and forth, plus six new lints with automated fixes. Here’s how to use them.
Before: the boilerplate you’re converting from
A typical data class spends most of its lines shuttling values from a constructor into fields:
class Point {
final int x;
final int y;
Point(this.x, this.y);
Point.origin()
: x = 0,
y = 0;
double get magnitude => sqrt(x * x + y * y);
}
Three of those declarations exist only to connect the constructor parameters to the fields.
Convert to primary constructor
The Convert to primary constructor refactoring turns an eligible in-body constructor into a primary constructor in the class header. Applied to the class above, it produces:
class Point(final int x, final int y) {
Point.origin()
: x = 0,
y = 0;
double get magnitude => sqrt(x * x + y * y);
}
The final (or var) modifier on each header parameter is what makes it a declaring parameter — it implicitly induces the instance field, replacing the separate field declarations and the constructor body. Without the modifier, a header parameter behaves like a plain constructor parameter and induces no field.
To invoke it, put your cursor on the constructor declaration or the class name and trigger the code actions menu — in VS Code that’s Quick Fix (Ctrl+. / Cmd+.); in IntelliJ or Android Studio, Show Context Actions (Alt+Enter). The refactoring only appears when the analyzer judges the conversion eligible, so if you don’t see it, the constructor has something that blocks the transformation.
The reverse direction exists too: Convert to in-body constructor expands a primary constructor back into the traditional field-plus-constructor form. That’s useful when a class outgrows the header — for instance, when you need a late field or a constructor the primary constructor can’t express.
The companion refactorings
Two more refactorings handle the cases that show up mid-migration:
- Convert to declaring parameter converts a non-declaring primary constructor parameter into a declaring one (adding the induced field). Use it when a header parameter turns out to deserve its own field.
- Move initialization to the field declaration moves field initialization from the constructor’s initializer list to the field declaration itself, using the primary initializer scope where header parameters are directly visible.
That second one pairs with a scope rule that makes primary constructors more capable than they first look: header parameters are in scope in non-late field initializers. So this:
class DeltaPoint(final int x, int delta) {
final int y;
DeltaPoint(this.x, delta) : y = x + delta;
}
can become:
class DeltaPoint(final int x, int delta) {
final int y = x + delta;
}
The refactoring performs exactly this move.
Lints with automated fixes
The announcement lists six new lints that push your codebase toward the concise style, all with dart fix support:
empty_container_bodies— prefer;over{}in class-like declarations with empty bodies.initialize_in_field_declaration— move field initialization from a primary constructor to the field declaration.unnecessary_primary_constructor_body— drop primary constructor bodies that do nothing.unnecessary_type_name_in_constructor— replace the repeated type name withnew.use_declaring_parameters— use declaring parameters where possible.unnecessary_const_in_enum_constructor— remove redundantconstfrom enum constructors.
Enable the ones you want in analysis_options.yaml, then run:
dart fix --apply
When not to convert
The refactorings are conservative, but know the constraints so you understand why a conversion is unavailable or why the analyzer complains after a manual attempt:
- A class with a primary constructor can’t declare any other non-redirecting generative in-body constructor — the primary constructor must execute on every instantiation. Redirecting and factory constructors remain fine.
- Declaring parameters can’t be
lateorexternal; declare those fields in the class body as usual. - A declaring parameter that collides with a method or field name in the class body is a compile-time error.
- Primary constructor parameters are read-only within field initializers and the initializer list — no
x = 5orx++there. - You can’t initialize the same field twice, in both its declaration and the initializer list, even for mutable fields.
Because of these rules, the safest migration path is exactly the one the tooling provides: let the refactoring move the code, then let the lints clean up the leftovers, and let dart analyze confirm you didn’t trip a constraint.
Getting the refactorings
The refactorings are delivered by the Dart analysis server, so they arrive with your SDK update rather than a separate plugin release. Upgrade to Dart 3.13 (or run flutter upgrade), make sure your package’s language version is 3.13, and the code actions show up in VS Code, IntelliJ, and Android Studio. For a full tour of the feature’s semantics — named primary constructors, const headers, enum primary constructors, and super parameters — see the primary constructors documentation.