← All writing

Dart 3.13 primary constructors: what Flutter developers need to know

Learn why Dart added primary constructors, how they reduce Flutter model boilerplate, what changes at runtime, and how to adopt them safely.

Dart 3.13 arrived on August 12, 2026 with a language feature Flutter developers have requested for years: primary constructors.

The idea is simple. A class can now declare its main constructor and fields in the class header. This removes repeated names and types from small model classes. It makes the code shorter, but it does not change how the class works at runtime.

A developer writing Dart code on a laptop beside a mobile app preview
Primary constructors make common Dart model classes shorter while keeping their familiar behavior.

The problem Dart wanted to solve

Flutter apps often contain many small classes: API models, screen state, configuration, coordinates, and value objects. Before Dart 3.13, even a tiny immutable class needed fields plus a constructor:

class UserProfile {
  final String name;
  final String avatarUrl;

  const UserProfile({
    required this.name,
    required this.avatarUrl,
  });
}

This code is clear, but name and avatarUrl appear more than once. That repetition becomes tiring when an app has dozens of models.

Dart already reduced some boilerplate with this.name. Primary constructors take the next step:

class UserProfile({
  required final String name,
  required final String avatarUrl,
});

Putting final before the type makes each parameter a declaring parameter. Dart creates a field with the same name and initializes it for you.

The official Dart 3.13 announcement says the feature became stable after an experimental preview in Dart 3.12. It requires language version 3.13 or newer.

What actually changed

A primary constructor is written directly after the class name. It can use positional or named parameters.

class Point(final double x, final double y);

class Product({
  required final String id,
  required final String title,
  final double price = 0,
});

Use final when the field should not change and var when it can change:

class Counter(var int value);

A parameter without final or var is only a constructor parameter. It does not create a field. This is useful when the parameter helps calculate another value:

class Initials(String firstName, String lastName) {
  final String value = '${firstName[0]}${lastName[0]}';
}

Primary constructor parameters can be used in field initializers. If validation or setup is needed, add a this block:

class Progress(final int completed, final int total) {
  this : assert(completed >= 0), assert(total > 0);

  double get fraction => completed / total;
}

This syntax may look unusual at first. The benefit is that fields, input, and validation stay close together.

Why this matters in Flutter

The biggest impact is readability. Flutter code often passes small data objects through widgets, repositories, and state classes. With less constructor boilerplate, the important parts of a model are easier to see.

Consider a simple screen state:

sealed class ProfileState;

class ProfileLoading() extends ProfileState;

class ProfileLoaded(
  final UserProfile profile,
  final bool isRefreshing,
) extends ProfileState;

class ProfileFailed(final String message) extends ProfileState;

The three states are visible at a glance. There is less code to maintain when a field is renamed, and less chance of assigning a constructor parameter to the wrong field.

There is no automatic JSON conversion, equality, copyWith, or toString. Primary constructors solve class declaration repetition. They are not full data classes, and they do not replace packages that generate those other features.

Runtime behavior and performance

Primary constructors are syntax sugar. The language documentation is explicit: the shorter declaration changes how you write the class, not how it behaves at runtime.

That means you should not expect smaller Flutter binaries, faster object creation, or different memory use just because a class uses the new syntax. The impact is on developer experience:

  • fewer repeated field and parameter names;
  • easier scanning of small classes;
  • safer class renaming with fewer constructor references;
  • simpler model and state declarations.

Those small improvements can matter across a large codebase, but they are maintainability gains rather than performance gains.

The upgrade detail that can break code

Dart 3.13 reserves final and var parameter modifiers for declaring parameters in primary constructors. Code like this is no longer valid at language version 3.13:

void logMessage(final String message) {
  print(message);
}

Remove the modifier:

void logMessage(String message) {
  print(message);
}

If your team used final to discourage parameter reassignment, enable the parameter_assignments lint instead. The Dart tools also include automated fixes and refactorings for converting eligible constructors.

Run these commands before changing many files by hand:

flutter upgrade
dart analyze
dart fix --dry-run

Review the suggested edits, then run your tests. Language versioning means dependencies can continue using older Dart syntax while your app moves forward.

Should you migrate every class?

No. A primary constructor is a good fit when the constructor mainly receives values and stores them. It works especially well for small immutable models, state objects, and enhanced enums.

Keep a traditional in-body constructor when it tells the story more clearly, especially when the class has several named constructors, complex initialization, or a long setup body. Shorter code is useful only when it stays easy to understand.

For a sensible migration:

  1. Upgrade the SDK and run the analyzer.
  2. Start with small leaf models that have simple field assignments.
  3. Use the IDE’s Convert to primary constructor refactoring where available.
  4. Review public package APIs carefully; a cleaner declaration should not accidentally change parameter names or behavior.
  5. Let old and new styles coexist while the team learns the syntax.

Primary constructors do not transform what a Flutter app can do. They improve the code developers read every day. That modest goal is exactly why the feature can have a broad impact: small model classes are everywhere, and now Dart gives them a form that matches their simplicity.

For the complete rules and edge cases, read the official primary constructors guide and the Dart team’s explanation of why the feature was designed this way.

Something to add? I’d love to hear it.

Let’s talk about it ↗