ZetCode

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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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).

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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

main.dart
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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.