Extensions keep helper code readable, and extension types give type safety for IDs and interop without allocation.
An extension adds members to a type you don't control:
extension StringCase on String {
String get capitalized => isEmpty ? this : this[0].toUpperCase() + substring(1);
}
'dart'.capitalized; // DartExtensions can add methods, getters, setters and operators, and can be generic (extension on List<T>). They are resolved statically, based on the variable's static type: they don't work on dynamic values and can't override existing members. If two imported extensions conflict, hide one or call it explicitly: StringCase('x').capitalized. Flutter code often uses extensions such as context.theme helpers.
An extension type (Dart 3.3+) wraps an existing type with a new static interface and no run-time cost:
extension type UserId(int value) {
bool get isValid => value > 0;
}At compile time UserId is distinct from int, so you can't pass a raw int or an OrderId where a UserId is expected. At run time it is the int — no wrapper object is allocated. Add implements int to expose the representation type's members.
Because the wrapper is erased, is checks and casts see the underlying type, so extension types are not a security boundary. They shine for strongly typed IDs and units, and for JS interop (dart:js_interop is built on them).
Example
extension Stats on Iterable<num> {
double get average => isEmpty ? 0 : fold<num>(0, (a, b) => a + b) / length;
}
extension DurationFormat on Duration {
String get mmss =>
'${inMinutes.toString().padLeft(2, '0')}:${(inSeconds % 60).toString().padLeft(2, '0')}';
}
extension type UserId(int value) {
bool get isValid => value > 0;
}
extension type OrderId(int value) {}
String loadUser(UserId id) => id.isValid ? 'user #${id.value}' : 'invalid';
void main() {
print([2, 4, 9].average); // 5.0
print(const Duration(seconds: 125).mmss); // 02:05
final id = UserId(42);
print(loadUser(id)); // user #42
// loadUser(OrderId(42)); // compile error
// loadUser(42); // compile error
print((id as Object) is int); // true: erased at run time
}Remember
- Extensions add members, resolved by static type
- Extension types are compile-time-only wrappers with zero run-time cost
- Use extension types for typed IDs and interop, not for security
Watch out for
- Calling an extension method on a
dynamicvalue and getting a NoSuchMethodError.