ZetCode

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.

main.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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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:

string_extensions.dart
extension Shout on String {
  String get shout => '${toUpperCase()}!';
}

extension Whisper on String {
  String get whisper => '(${toLowerCase()})';
}
main.dart
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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.