| |

Dart 67 🎯 Macros Framework (Dart 3.x+): Metaprogramming and Compile-Time Code Augmentation

⏱️ Estimated read: 11 min | |

The Dart macros framework was the most anticipated language feature in the Dart and Flutter ecosystem for years, and it was cancelled. In January 2025, the Dart team announced that work on macros had been suspended indefinitely . The feature that was supposed to eliminate build_runner, generate toJson/fromJson automatically, and enable community-authored compile-time metaprogramming is not shipping. This chapter covers what macros were designed to do, why the team stopped, and what replaced them.

Understanding this history matters because the ecosystem is still shaped by it. Packages like freezed and json_serializable remain the standard, and the augmentations feature—extracted from the macro work—is the path forward for cleaner code generation. If you are writing Dart in 2026 or later, the practical guidance is not “how to write macros” but “what to use instead.”

Key point: Dart macros were an experimental compile-time metaprogramming system that would have generated code by introspecting declarations at compile time; the Dart team cancelled the work in January 2025 due to unacceptable regressions in hot reload and incremental compilation, and the extracted augmentations feature is the surviving replacement for cleaner code generation.


Why macros were designed the way they were

The build_runner problem. Dart does not support runtime reflection because it prevents tree-shaking and fast startup. The alternative for code generation has been build_runner, an external tool that runs before compilation, reads annotations, and writes .g.dart files to disk. This works, but it is slow: a full build can take minutes, the generated files clutter the project, and the IDE needs a separate step to recognize them . Macros were designed to eliminate that external step by running inside the compiler itself.

The JsonCodable preview. Dart 3.4 shipped a preview of a single macro, @JsonCodable, to demonstrate the intended developer experience. Applying it to a class would generate toJson() and fromJson() in real time, with no .g.dart file and no build step. The macro would create an “augmentation class” that added the generated members to the existing class . IDE autocomplete worked immediately because the compiler resolved the macro as part of analysis.

The semantic introspection goal. The Dart team did not want a syntactic macro system that only manipulates tokens. They wanted deep semantic introspection—a macro that could see the types of a class’s fields, not just their names, to generate correct serialization code. This ambition is what made the feature expensive. Semantic introspection requires the compiler to resolve types during editing, which regresses the incremental compilation that keeps hot reload fast .

The phased application model. The macro specification defined multiple phases. Macros on inner declarations run before outer ones. Macros on the same declaration run right-to-left. Superclasses and mixins are processed before the classes that extend them. This ordering was necessary to make generated code deterministic and to allow macros to see the results of other macros in later phases . The complexity of this model contributed to the performance problems.


a. Why the Dart team cancelled macros

The official announcement was unusually direct. The Dart team stated that “each time we solved a major technical hurdle, we saw new ones pop up” and that they were “not seeing macros converging anytime soon toward a feature we are comfortable shipping, with the quality and developer-time performance we want” .

The specific regressions were in two areas. First, editing performance: static analysis and code completion slowed down because the compiler had to execute macros to know what code existed. Second, incremental compilation: the first step of hot reload was delayed because macro-generated code had to be regenerated before the application could update . The team concluded that the compile-time cost of semantic introspection was fundamentally incompatible with Dart’s fast development cycle.

The cancellation was not a rejection of metaprogramming in principle. The team explicitly stated they remain interested in “general metaprogramming in the long term” but will not ship macros “in the foreseeable future” . The work was not wasted: the augmentations feature was prototyped as part of the macro system and is being shipped independently.

Community reaction was largely supportive of the decision. Very Good Ventures described it as “a strategic shift, not a setback,” noting that the team chose not to degrade the development experience of millions of developers for a powerful but costly feature . The practical advice that emerged was to stop waiting for macros and continue using the existing code generation ecosystem.


b. What replaced macros: augmentations and the existing ecosystem

The surviving feature from the macro work is augmentations. An augmentation is a declaration that adds members to an existing class, function, or variable without replacing it. The augment keyword marks the declaration, and the compiler merges the parts into a single entity .

Augmentations are not macros. They do not introspect code or generate logic on their own. A tool—typically build_runner—still produces the augmented declarations. The difference is the output format: instead of a separate .g.dart file stitched together with part directives, the generated code is an augment class that integrates into the original class as if the members were written by hand . This is cleaner for debugging and avoids the namespace pollution of generated function names.

The practical guidance for Dart development remains: use freezed for immutable data classes and json_serializable for JSON serialization, driven by build_runner. These tools are mature and battle-tested. The @JsonCodable preview is no longer relevant because the macro system it depended on was removed from the SDK . The dart_macros package on pub.dev is an unrelated C-style preprocessor, not the official Dart macro system, and should not be confused with it .

If you encounter older tutorials that describe enabling macros with --enable-experiment=macros or adding json to dependencies, that guidance is obsolete. The experiment flag no longer has an effect, and the package dependencies were removed from the SDK.


c. The current state and what to use instead

The Dart team’s stated focus after cancelling macros was on “better data” and “improving build times and the overall code generation experience” . This has manifested in specific improvements rather than a general metaprogramming system.

Primary constructors are an experimental feature that reduces class boilerplate by combining the class declaration and its constructor. Private named parameters are stable and simplify the common pattern of private fields with public constructor parameters . Augmentations improve how generated code integrates with source files. None of these replace macros, but they address specific ergonomic pain points that macros were intended to solve.

The decision to cancel macros is an example of a language team prioritizing the stability of its core developer experience over an ambitious feature. Dart’s value proposition has always been a combination of static-language performance with dynamic-language development speed—hot reload, fast analysis, quick iteration. Macros threatened that combination, and the team chose to protect it .


Complete Example Session

The following session demonstrates the current state: the @JsonCodable preview is gone, and the json_serializable pattern remains the standard.

// ============================================
// PART 1: THE MACRO PREVIEW (NO LONGER WORKS)
// ============================================
// This was the Dart 3.4 preview syntax.
// The macros experiment was cancelled, so this code
// cannot compile in current Dart SDKs.
//
// @JsonCodable()
// class Vehicle {
//   final String description;
//   final int wheels;
//   Vehicle(this.description, this.wheels);
// }
//
// void main() {
//   final json = Vehicle('bicycle', 2).toJson();
// }

// ============================================
// PART 2: CURRENT STANDARD - json_serializable
// ============================================
// pubspec.yaml dependencies:
// dependencies:
//   json_annotation: ^4.9.0
// dev_dependencies:
//   build_runner: ^2.4.0
//   json_serializable: ^6.8.0

import 'package:json_annotation/json_annotation.dart';

part 'vehicle.g.dart';

@JsonSerializable()
class Vehicle {
  final String description;
  final int wheels;

  Vehicle(this.description, this.wheels);

  factory Vehicle.fromJson(Map<String, dynamic> json) =>
      _$VehicleFromJson(json);

  Map<String, dynamic> toJson() => _$VehicleToJson(this);
}

// ============================================
// PART 3: RUNNING THE GENERATOR
// ============================================
// Command: dart run build_runner build --delete-conflicting-outputs
// Output: vehicle.g.dart (generated, not committed)

// ============================================
// PART 4: USING THE GENERATED CODE
// ============================================
void main() {
  final vehicle = Vehicle('bicycle', 2);
  final json = vehicle.toJson();
  print(json); // {description: bicycle, wheels: 2}

  final restored = Vehicle.fromJson(json);
  print(restored.description); // bicycle
}

// ============================================
// PART 5: AUGMENTATIONS (FUTURE PATH)
// ============================================
// Augmentations are being developed as a separate feature.
// The syntax will allow generated code to integrate
// directly into the original class:
//
// augment class Vehicle {
//   // generated members appear here
// }
//
// Tools like json_serializable will eventually generate
// augmentations instead of separate .g.dart files.

// ============================================
// PART 6: KEY TAKEAWAY
// ============================================
// Macros were cancelled. The @JsonCodable preview
// is removed from the SDK. Use json_serializable
// and build_runner for JSON serialization today.
// Watch for augmentations as the future improvement
// to how generated code is integrated.

The six parts show the macro preview that no longer compiles, the current json_serializable standard, the generator command, usage of the generated code, the future augmentations syntax, and the takeaway. What the session demonstrates is that the practical answer to “how do I generate JSON serialization in Dart” has not changed: json_serializable remains the supported path.


Quick Reference

Macro Status

ItemStatus
Official Dart macrosCancelled January 2025
@JsonCodable previewRemoved from SDK
--enable-experiment=macrosNo effect
dart_macros pub packageUnrelated C-style preprocessor
AugmentationsIn development as separate feature

Current Code Generation Stack

ToolPurpose
build_runnerRuns generators
json_serializableJSON serialization
freezedImmutable data classes
json_annotationAnnotations for serializable

Why Macros Were Cancelled

ReasonDetail
Hot reload regressionIncremental compilation slowed
IDE performanceAnalysis and completion regressed
Semantic introspection costToo expensive to keep development fast
No convergenceNew problems appeared with each fix

What Replaced Macros

FeaturePurpose
AugmentationsCleaner integration of generated code
Primary constructorsReduce class boilerplate
Private named parametersSimplify field initialization

Best Practices

✅ Do This:

@JsonSerializable()                                        // ✅ use json_serializable
class User { ... }
// Run: dart run build_runner build                         // ✅ generate code
// Commit .g.dart or gitignore, but keep it out of source   // ✅ standard practice

❌ Don’t Do This:

@JsonCodable()                                             // ❌ removed from SDK
// Add json package and enable macros experiment           // ❌ obsolete
// Use dart_macros package expecting official macros       // ❌ unrelated package

Common Pitfalls

PitfallWhy It HappensFix
@JsonCodable not foundPreview removed from SDKUse json_serializable
Experiment flag ignoredMacros cancelledRemove macros from experiments
dart_macros package confusionName similarityIt is a separate C-style macro tool
Missing .g.dartGenerator not rundart run build_runner build
Waiting for macrosFeature cancelledMigrate to json_serializable

Real-World Examples

1. JSON serialization today

@JsonSerializable()
class User { final String name; ... }

2. Generate code

dart run build_runner build --delete-conflicting-outputs

3. Freezed for immutable models

@freezed
class User with _$User { ... }

4. Augmentations (future)

augment class User { // generated members }

5. Primary constructors (experimental)

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

6. Remove obsolete experiment

# analysis_options.yaml - remove:
# enable-experiment:
#   - macros

7. Check SDK version

dart --version

8. The cancelled preview

// @JsonCodable() - no longer supported

9. Use json_annotation

import 'package:json_annotation/json_annotation.dart';

10. Watch for augmentations

// Follow dart.dev for augmentations updates

Visual

The Macro Promise vs Reality

┌──────────────────────────────────────────────┐
│  PROMISE                                     │
│  @JsonCodable()                              │
│  class User { ... }                          │
│         │                                    │
│         ▼                                    │
│  Compiler generates toJson/fromJson          │
│  No build step, no .g.dart, instant IDE      │
│                                              │
│  REALITY                                     │
│  Macros cancelled January 2025               │
│         │                                    │
│         ▼                                    │
│  Hot reload regressed                        │
│  IDE performance regressed                   │
│  Semantic introspection too expensive        │
└──────────────────────────────────────────────┘

Current Workflow

┌──────────────────────────────────────────────┐
│  @JsonSerializable()                         │
│  class User { ... }                          │
│         │                                    │
│         ▼                                    │
│  dart run build_runner build                 │
│         │                                    │
│         ▼                                    │
│  user.g.dart generated                       │
│         │                                    │
│         ▼                                    │
│  part 'user.g.dart';                         │
│  Compiles with generated code                │
└──────────────────────────────────────────────┘

Augmentations (Future)

┌──────────────────────────────────────────────┐
│  Original: class User { ... }                │
│         │                                    │
│         ▼                                    │
│  Generated: augment class User {             │
│    // toJson, fromJson, etc.                 │
│  }                                           │
│         │                                    │
│         ▼                                    │
│  Compiler merges into one class              │
│  No .g.dart file, no part directive          │
│  Cleaner than current .g.dart approach       │
└──────────────────────────────────────────────┘

Why Macros Failed

┌──────────────────────────────────────────────┐
│  Semantic introspection                      │
│  (needed for correct code generation)        │
│         │                                    │
│         ▼                                    │
│  Compiler must resolve types during editing  │
│         │                                    │
│         ▼                                    │
│  Incremental compilation slows               │
│         │                                    │
│         ▼                                    │
│  Hot reload regresses                        │
│         │                                    │
│         ▼                                    │
│  Development experience degrades             │
│         │                                    │
│         ▼                                    │
│  Dart team cancels the feature               │
└──────────────────────────────────────────────┘

Summary

ItemValue
Official Dart macrosCancelled January 2025
@JsonCodable previewRemoved from SDK
Reason for cancellationHot reload and IDE performance regressions
Surviving featureAugmentations (separate from macros)
Current serializationjson_serializable + build_runner
Current immutable modelsfreezed + build_runner
dart_macros packageUnrelated C-style preprocessor
Future improvementAugmentations for cleaner code generation

Key takeaways:

  • Dart macros are not shipping. The Dart team cancelled the work in January 2025 after concluding that semantic introspection was too expensive to keep hot reload fast .
  • The @JsonCodable preview is gone. Any tutorial or code sample that uses it is obsolete. The json package and the macros experiment flag no longer apply.
  • json_serializable remains the standard. The build_runner workflow is the supported path for JSON serialization in Dart and Flutter .
  • freezed remains the standard for data classes. The immutable model and copyWith generation that macros promised is already handled by freezed.
  • Augmentations are the surviving improvement. They allow generated code to integrate into the original class instead of living in a separate .g.dart file, but they do not eliminate build_runner .
  • The dart_macros package is not the official macro system. It is an unrelated C-style preprocessor and should not be confused with the cancelled Dart language feature .

Remember: The Dart macros story is a case study in a language team choosing to protect its core developer experience over an ambitious feature. Macros would have been powerful, but they would have made editing and hot reload slower—the two things that make Dart pleasant to use. If you are building Dart or Flutter applications today, the practical answer is unchanged: use json_serializable for JSON, freezed for immutable models, and build_runner to generate the code. Watch for augmentations as a cleaner integration path, but do not wait for macros. They are not coming in the foreseeable future.


Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!