Dart 67 🎯 Macros Framework (Dart 3.x+): Metaprogramming and Compile-Time Code Augmentation
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
| Item | Status |
|---|---|
| Official Dart macros | Cancelled January 2025 |
@JsonCodable preview | Removed from SDK |
--enable-experiment=macros | No effect |
dart_macros pub package | Unrelated C-style preprocessor |
| Augmentations | In development as separate feature |
Current Code Generation Stack
| Tool | Purpose |
|---|---|
build_runner | Runs generators |
json_serializable | JSON serialization |
freezed | Immutable data classes |
json_annotation | Annotations for serializable |
Why Macros Were Cancelled
| Reason | Detail |
|---|---|
| Hot reload regression | Incremental compilation slowed |
| IDE performance | Analysis and completion regressed |
| Semantic introspection cost | Too expensive to keep development fast |
| No convergence | New problems appeared with each fix |
What Replaced Macros
| Feature | Purpose |
|---|---|
| Augmentations | Cleaner integration of generated code |
| Primary constructors | Reduce class boilerplate |
| Private named parameters | Simplify 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
| Pitfall | Why It Happens | Fix |
|---|---|---|
@JsonCodable not found | Preview removed from SDK | Use json_serializable |
| Experiment flag ignored | Macros cancelled | Remove macros from experiments |
dart_macros package confusion | Name similarity | It is a separate C-style macro tool |
Missing .g.dart | Generator not run | dart run build_runner build |
| Waiting for macros | Feature cancelled | Migrate 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
| Item | Value |
|---|---|
| Official Dart macros | Cancelled January 2025 |
@JsonCodable preview | Removed from SDK |
| Reason for cancellation | Hot reload and IDE performance regressions |
| Surviving feature | Augmentations (separate from macros) |
| Current serialization | json_serializable + build_runner |
| Current immutable models | freezed + build_runner |
dart_macros package | Unrelated C-style preprocessor |
| Future improvement | Augmentations 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
@JsonCodablepreview is gone. Any tutorial or code sample that uses it is obsolete. Thejsonpackage and themacrosexperiment flag no longer apply. json_serializableremains the standard. Thebuild_runnerworkflow is the supported path for JSON serialization in Dart and Flutter .freezedremains the standard for data classes. The immutable model andcopyWithgeneration that macros promised is already handled byfreezed.- Augmentations are the surviving improvement. They allow generated code to integrate into the original class instead of living in a separate
.g.dartfile, but they do not eliminatebuild_runner. - The
dart_macrospackage 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.
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!