Dart Extension Methods
last modified October 22, 2026
In this article, we show how to use extension methods in Dart language. Extension methods allow you to add functionality to existing classes without modifying their source code or creating a subclass.
Extension methods are particularly useful when you want to add utility methods
to classes from external libraries or built-in types like String,
int, or List. An extension can contain methods,
getters, setters, operators, and static members; it cannot contain instance
fields or constructors.
Dart Extension Methods: Simple Example
The following example demonstrates how to create and use a simple extension method in Dart.
extension StringExtensions on String {
bool get isPalindrome {
final normalized = toLowerCase().replaceAll(RegExp(r'[^a-z0-9]'), '');
return normalized == normalized.split('').reversed.join();
}
}
void main() {
const word = 'racecar';
const phrase = 'A man, a plan, a canal: Panama';
print('Is "$word" a palindrome? ${word.isPalindrome}');
print('Is "$phrase" a palindrome? ${phrase.isPalindrome}');
print('Is "falcon" a palindrome? ${'falcon'.isPalindrome}');
}
In this program, we define an extension getter isPalindrome for the
String class. The getter normalizes the string first: it converts
the text to lower case and removes everything that is not an ASCII letter or
digit. It then checks whether the result reads the same backward as forward.
extension StringExtensions on String {
bool get isPalindrome {
final normalized = toLowerCase().replaceAll(RegExp(r'[^a-z0-9]'), '');
return normalized == normalized.split('').reversed.join();
}
}
We define an extension named StringExtensions that adds a
isPalindrome getter to the String class. Inside the
extension, this refers to the string on which the member is called,
and the members of the string can be used without the this
prefix.
print('Is "$word" a palindrome? ${word.isPalindrome}');
We use the isPalindrome extension to check a single word and a full
phrase. The call looks exactly like a call of a built-in member. This
normalization is deliberately ASCII-oriented; for text with accented letters or
emoji, apply Unicode normalization and compare grapheme clusters instead.
$ dart main.dart Is "racecar" a palindrome? true Is "A man, a plan, a canal: Panama" a palindrome? true Is "falcon" a palindrome? false
Dart Extension Methods with Parameters
Extension methods can also accept parameters, allowing you to add more flexible functionality.
import 'dart:math';
extension NumberExtensions on int {
int power(int exponent) => pow(this, exponent).toInt();
bool get isPrime {
if (this < 2) return false;
for (var i = 2; i * i <= this; i++) {
if (this % i == 0) return false;
}
return true;
}
}
void main() {
const number = 2;
print('$number raised to the power of 3 is ${number.power(3)}');
print(7.isPrime);
print(10.isPrime);
}
In this program, we define the extension methods power and
isPrime for the int class. The method calculates the
power of a number. (Note that the ^ operator in Dart is the bitwise
XOR, not exponentiation; we use pow from dart:math.)
int power(int exponent) => pow(this, exponent).toInt();
We define an extension named NumberExtensions that adds a
power method to the int class. The pow
function returns a num, which we convert to an integer.
print('$number raised to the power of 3 is ${number.power(3)}');
We use the power extension method to calculate 2 raised
to the power of 3. The literal 7.isPrime works too,
because extensions can be called on literals.
$ dart main.dart 2 raised to the power of 3 is 8 true false
Dart Extension Methods with Generic Types
Extension methods can also be defined for generic types, making them reusable across different data types.
extension ListExtensions<T> on List<T> {
List<T> duplicate() {
return [...this, ...this];
}
}
extension NumListExtensions on List<num> {
double get average => isEmpty ? 0 : fold<num>(0, (a, b) => a + b) / length;
}
void main() {
final numbers = [1, 2, 3];
print('Duplicated list: ${numbers.duplicate()}');
print('Average: ${numbers.average}');
print(['a', 'b'].duplicate());
}
In this program, we define an extension method duplicate for the
List class. The method duplicates the elements of the list. The
second extension is declared only on lists of numbers.
extension ListExtensions<T> on List<T> {
We define an extension named ListExtensions that adds a
duplicate method to the List class. The method works
for any type T.
extension NumListExtensions on List<num> {
This extension applies only when the static type of the list is
List<num> (or a subtype such as List<int>).
Calling average on a list of strings would be a compile-time
error.
$ dart main.dart Duplicated list: [1, 2, 3, 1, 2, 3] Average: 2.0 [a, b, a, b]
Dart Extension Methods with Static Helpers
Extension methods can also include static helper methods, which are useful for utility functions. Static members are called on the extension name, not on an instance.
extension StringHelpers on String {
static String capitalize(String input) {
if (input.isEmpty) return input;
return input[0].toUpperCase() + input.substring(1);
}
String get capitalized => isEmpty ? this : capitalize(this);
}
void main() {
const name = 'alice';
print('Capitalized name: ${StringHelpers.capitalize(name)}');
print('bob'.capitalized);
print('Empty: "${StringHelpers.capitalize('')}"');
}
In this program, we define a static helper method capitalize within
an extension for the String class. The method returns the input
unchanged when it is empty and otherwise capitalizes its first character. The
instance getter capitalized reuses it. This simple helper operates
on UTF-16 code units; for grapheme-aware capitalization, use the
characters package.
$ dart main.dart Capitalized name: Alice Bob Empty: ""
Dart Extension Operators
An extension can define operators. In the following example, we add the
* operator to Duration.
extension DurationScale on Duration {
Duration operator *(num factor) =>
Duration(microseconds: (inMicroseconds * factor).round());
}
extension Repeat on int {
Iterable<int> get times sync* {
for (var i = 0; i < this; i++) {
yield i;
}
}
}
void main() {
final d = const Duration(minutes: 2, seconds: 30) * 2;
print(d);
for (final i in 3.times) {
print('round $i');
}
}
The first extension allows us to multiply a duration by a number. The second
adds a times getter to integers, which yields the numbers from 0 to
this - 1.
$ dart main.dart 0:05:00.000000 round 0 round 1 round 2
Dart Extensions on Nullable Types
An extension can be declared on a nullable type. Inside the extension,
this can then be null, which lets us write helpers that are safe to
call on null values.
extension NullableString on String? {
bool get isNullOrEmpty => this == null || this!.isEmpty;
String orDefault(String value) => isNullOrEmpty ? value : this!;
}
void main() {
String? a;
const String b = '';
const String c = 'falcon';
print(a.isNullOrEmpty);
print(b.isNullOrEmpty);
print(c.isNullOrEmpty);
print(a.orDefault('n/a'));
}
The extension methods can be called on a String? without the
?. operator, because the extension itself handles null.
$ dart main.dart true true false n/a
Dart Extension Name Conflicts
If two extensions in scope define a member with the same name and both are applicable, the call is ambiguous and the compiler reports an error. We can resolve it by invoking one of the extensions explicitly. A member of the class itself always takes precedence over an extension member.
extension Shout on String {
String get label => '${toUpperCase()}!';
}
extension Whisper on String {
String get label => '(${toLowerCase()})';
}
extension on int {
String get label => 'int $this';
}
void main() {
const s = 'Falcon';
print(Shout(s).label);
print(Whisper(s).label);
print(5.label);
// print(s.label); // error: ambiguous extension member access
}
The wrapper syntax Shout(s).label calls the extension explicitly.
The third extension has no name; such an extension is private to the library and
cannot be invoked explicitly, but its members can be used implicitly. Extension
methods are resolved statically, from the static type of the receiver. For a
receiver of type dynamic they are not available.
$ dart main.dart FALCON! (falcon) int 5
Dart Extension Import and Show
Extensions are applied only when they are in scope. An extension declared in
another library must be imported, and we can import just the extensions we need
with show or exclude one with hide. In the following
example, the extensions live in a separate string_extensions.dart
file:
extension Shout on String {
String get shout => '${toUpperCase()}!';
}
extension Whisper on String {
String get whisper => '(${toLowerCase()})';
}
import 'string_extensions.dart' show Shout;
void main() {
print('falcon'.shout);
// print('falcon'.whisper); // error: Whisper is hidden by show
}
$ dart main.dart FALCON!
The show combinator makes only Shout visible, so
Whisper is not in scope. Use hide to import everything
except one extension:
import 'string_extensions.dart' hide Whisper;
Both forms work with relative library paths and with package: URIs.
Relative imports are convenient for examples; packages use
package:my_app/string_extensions.dart.
Source
Dart Extension Methods - Language Documentation
In this article, we have covered the basics of using extension methods in Dart. Extension methods are a powerful feature for adding functionality to existing classes without modifying their source code.
Author
List all Dart tutorials.