Dart Primary Constructors
last modified October 18, 2026
In this article, we show how to use primary constructors in Dart language. A primary constructor declares the main constructor of a class and its instance variables directly in the class header, which removes much of the boilerplate of the traditional syntax.
Primary constructors require language version 3.13 or later. The examples
were tested with Dart 3.13.5; make sure the SDK constraint in
pubspec.yaml is ^3.13.0 or newer.
Traditional constructors vs. primary constructors
In the traditional syntax, we declare the fields, and then the constructor that
initializes them. With a primary constructor, the parameter list moves to the
class header, and the final or var modifier on a
parameter declares the matching field.
class Point {
final int x;
final int y;
Point(this.x, this.y);
}
class PrimaryPoint(final int x, final int y);
void main() {
final p1 = Point(1, 2);
final p2 = PrimaryPoint(3, 4);
print('${p1.x}, ${p1.y}');
print('${p2.x}, ${p2.y}');
}
The Point class uses the traditional form: two fields and a
constructor with initializing formals. The PrimaryPoint class
declares the same thing in one line. The semicolon replaces an empty class
body.
$ dart main.dart 1, 2 3, 4
Declaring parameters
A header parameter marked with final or var is a
declaring parameter; it implicitly creates an instance variable of
the same name. A final field is immutable, a var
field can be reassigned.
class User(final String name, var int age);
void main() {
final user = User('Alice', 30);
user.age = 31;
print('${user.name} is ${user.age}');
// user.name = 'Bob'; // error: 'name' is final
}
The name field is final, while age can be modified.
Assigning to name would be a compile-time error.
$ dart main.dart Alice is 31
Non-declaring parameters
A parameter without final or var is an ordinary
constructor parameter. It creates no field, but it can be used in field
initializers.
class Counter(int start) {
int value = start;
void increment() => value++;
}
void main() {
final c = Counter(10);
c.increment();
print(c.value);
}
Here start is not a field. It is visible in the initializer of
the value field, which is declared in the class body.
$ dart main.dart 11
The constructor body
To run code when an object is created, add a this block to the
class body. It plays the role of the constructor body, and may contain an
initializer list after a colon.
class Rectangle(final double width, final double height) {
this : assert(width > 0 && height > 0) {
print('Creating ${width}x$height rectangle');
}
double get area => width * height;
}
void main() {
final r = Rectangle(3, 4);
print(r.area);
}
The assert after this : is the initializer list; the
braces hold the body. Inside the body, width and
height refer to the fields. The body cannot be
async or use =>.
$ dart main.dart Creating 3.0x4.0 rectangle 12.0
Initializer lists
If we only need to initialize other fields, the this block can
consist of an initializer list ended by a semicolon.
class Temperature(final double celsius) {
final double fahrenheit;
this : fahrenheit = celsius * 9 / 5 + 32;
}
void main() {
final t = Temperature(100);
print('${t.celsius} C = ${t.fahrenheit} F');
}
The fahrenheit field is computed from the header parameter
celsius.
$ dart main.dart 100.0 C = 212.0 F
Named and optional parameters
Declaring parameters can be named, required, or have default values, just like in any other constructor.
class Person({required final String name, final int age = 18});
void main() {
final p = Person(name: 'Alice');
final q = Person(name: 'Bob', age: 42);
print('${p.name} ${p.age}');
print('${q.name} ${q.age}');
}
The parameters are placed between curly braces to make them named. The
name parameter is required, age defaults to 18.
$ dart main.dart Alice 18 Bob 42
Private named parameters
A named declaring parameter whose name starts with an underscore declares a private field, while callers use the name without the underscore.
class Account({required final String _owner, var double _balance = 0}) {
void deposit(double amount) => _balance += amount;
String get summary => '$_owner: $_balance';
}
void main() {
final acc = Account(owner: 'Alice', balance: 100);
acc.deposit(50);
print(acc.summary);
}
The fields are _owner and _balance, but the
constructor is called with owner: and balance:. This
removes the need for an initializer list that maps public names to private
fields.
$ dart main.dart Alice: 150.0
Const primary constructors
Placing const before the class name makes the primary constructor
constant. All instance variables must then be final, and no
body block is allowed (only an initializer list).
class const Color(final int r, final int g, final int b);
void main() {
const red = Color(255, 0, 0);
const red2 = Color(255, 0, 0);
print(identical(red, red2));
print('${red.r}, ${red.g}, ${red.b}');
}
Two constant expressions with the same arguments yield the same canonical
instance. Note that the keyword goes after class:
const class is not valid.
$ dart main.dart true 255, 0, 0
Named primary constructors
A name can be appended to the class name in the header. A private name such
as ._ is commonly used to prevent direct instantiation from
outside the library.
class Circle.fromRadius(final double radius) {
double get area => 3.14159 * radius * radius;
}
class Singleton._(final String id) {
static final instance = Singleton._('only');
}
void main() {
print(Circle.fromRadius(2).area);
print(Singleton.instance.id);
}
The Circle class is created with Circle.fromRadius.
Singleton._ can be called only inside its library, so the static
instance field is the only way to get an object.
$ dart main.dart 12.56636 only
Inheritance and super parameters
Super parameters in the header forward values to the superclass constructor.
class Animal(final String name);
class Dog(super.name, final String breed) extends Animal {
String describe() => '$name is a $breed';
}
void main() {
print(Dog('Rex', 'Collie').describe());
}
The super.name parameter is passed to the Animal
constructor. It does not create a new field in Dog.
$ dart main.dart Rex is a Collie
Enums
Enums can declare their fields in the header as well. Enum constructors are
implicitly constant, so const is not needed.
enum Planet(final double mass, final double radius) {
mercury(3.303e+23, 2.4397e6),
earth(5.976e+24, 6.37814e6);
double get gravity => 6.67300E-11 * mass / (radius * radius);
}
void main() {
for (final p in Planet.values) {
print('${p.name}: ${p.gravity.toStringAsFixed(2)}');
}
}
Each enum value passes arguments for the declared fields.
$ dart main.dart mercury: 3.70 earth: 9.80
Extension types
An extension type must have exactly one primary constructor parameter, the
representation variable; it is always a declaring parameter, so no
final is needed.
extension type Meters(double value) {
Meters operator +(Meters other) => Meters(value + other.value);
}
void main() {
final a = Meters(1.5);
final b = Meters(2.5);
print((a + b).value);
}
The value parameter is the representation of Meters
and is accessible as a field.
$ dart main.dart 4.0
Rules and limitations
- A class can have only one primary constructor, and it cannot also declare other generative constructors in its body; redirecting constructors are allowed.
- Declaring parameters cannot be
lateorexternal. - A field cannot be initialized both in its declaration and by a declaring parameter or the initializer list.
- Parameter names cannot clash with methods or other fields in the body.
- The
finalandvarmodifiers are allowed on parameters only in primary constructors. Using them on a function or method parameter is a compile-time error.
void greet(final String name) {
print(name);
}
void main() => greet('x');
The dart fix command or the avoid_final_parameters
lint can find such modifiers in existing code before upgrading the language
version.
$ dart main.dart
main.dart:1:12: Error: Can't have modifier 'final' here.
Try removing 'final'.
void greet(final String name) {
^^^^^
Summary
Primary constructors make simple classes, enums, and extension types much
shorter by declaring fields and the constructor in a single place. For
classes that need validation or setup, the this block provides
an initializer list and a body.
Author
List all Dart tutorials.