Shared Package Pattern: Full-Stack Dart Done Right
Eliminate the double-doc tax with the Shared Package pattern — write Dart code once, use it in Flutter and server-side code without duplication.
Published on • September 17, 2026
AI Assistant

The Double-Doc Tax
When you build full-stack Dart — Flutter on the frontend, Dart Frog or Cloud Functions on the backend — you inevitably duplicate code. Data models, validation logic, enums, constants — they all get written twice, documented twice, and maintained twice.
This is the double-doc tax, and the Shared Package pattern eliminates it.
What Is a Shared Package?
A shared package is a standard Dart package containing code used by both your Flutter app and your Dart server:
my_app/
├── packages/
│ └── shared/
│ ├── lib/
│ │ ├── models/
│ │ │ ├── user.dart
│ │ │ └── transaction.dart
│ │ ├── validators/
│ │ │ └── email_validator.dart
│ │ └── constants/
│ │ └── api_routes.dart
│ └── pubspec.yaml
├── app/ # Flutter app
├── server/ # Dart Frog backend
└── pubspec.yaml
Implementation
// packages/shared/lib/models/user.dart
class User {
final String id;
final String email;
final String displayName;
const User({required this.id, required this.email, required this.displayName});
factory User.fromJson(Map<String, dynamic> json) {
return User(id: json['id'], email: json['email'], displayName: json['name']);
}
Map<String, dynamic> toJson() => {'id': id, 'email': email, 'name': displayName};
}
Both Flutter and server import the same package:
// Flutter app
import 'package:shared/models/user.dart';
// Server
import 'package:shared/models/user.dart';
What Goes in Shared?
- Data models (entities, DTOs)
- Validation logic (email, phone, password rules)
- Enums and constants (API routes, status codes)
- Extension methods (string formatting, date helpers)
- Serialization helpers (JSON codecs, protobuf definitions)
What Stays Separate?
- Flutter widgets — UI stays in the app
- Server middleware — backend logic stays in the server
- Platform-specific code — native plugins, FFI bindings
Benefits
- Single source of truth — one model, one validation rule
- Type safety across the stack — compile-time errors for mismatches
- Easier refactoring — change once, reflected everywhere
- Reduced bugs — no more forgetting to update the backend model
Conclusion
The Shared Package pattern is the missing link in full-stack Dart. Write your models and business logic once, and let both your Flutter app and server consume them. It’s clean, type-safe, and eliminates an entire class of maintenance headaches.