Skip to content
Blog

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

  1. Single source of truth — one model, one validation rule
  2. Type safety across the stack — compile-time errors for mismatches
  3. Easier refactoring — change once, reflected everywhere
  4. 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.