Dart Tutorial

Dart Lesson 91 of 102 4 min read

Regular Expressions in Dart with RegExp

Learn RegExp in Dart: test, find, extract and replace text with patterns, use groups and named groups, and validate input.

On this page

A regular expression (regex) is a pattern that describes text. With one you can check whether a string has a certain shape, pull pieces out of it, or replace parts of it. In Dart, regular expressions are RegExp objects.

Always write the pattern as a raw string, r'...', so that backslashes are not treated as escapes by Dart.

Does it match? #

void main() {
  final digitsOnly = RegExp(r'^\d+$');

  print(digitsOnly.hasMatch('12345'));
  print(digitsOnly.hasMatch('123a5'));
  print(RegExp(r'cat').hasMatch('concatenate')); // found anywhere
}
true
false
true

Without ^ (start) and $ (end), a pattern matches if it is found anywhere in the text.

Pattern cheat sheet #

PatternMatches
abcThe exact text “abc”
.Any single character
\d \w \sA digit, a word character (letter, digit, _), whitespace
\D \W \SThe opposite of each
[abc] [a-z] [^0-9]One of a set, a range, anything not in the set
^ $Start and end of the text
\bA word boundary
* + ?Zero or more, one or more, zero or one
{3} {2,5} {2,}Exactly 3, between 2 and 5, 2 or more
a|bEither a or b
( )A group
\. \? \(A literal special character

Find the first match #

firstMatch returns a match object, or null.

void main() {
  final price = RegExp(r'\d+\.\d{2}');
  final match = price.firstMatch('Total due: Rs. 1499.50 by Friday');

  if (match != null) {
    print(match.group(0)); // the whole match
    print(match.start);
    print(match.end);
  }

  print('No digits here'.contains(price));
}
1499.50
15
22
false

Find every match #

void main() {
  const text = 'Call 9841000000 or 9801234567, not 12345.';
  final phone = RegExp(r'\b9\d{9}\b');

  final numbers = phone.allMatches(text).map((m) => m.group(0)!).toList();
  print(numbers);

  final hashtags = RegExp(r'#\w+')
      .allMatches('Learning #dart and #flutter today')
      .map((m) => m[0]!)
      .toList();
  print(hashtags);
}
[9841000000, 9801234567]
[#dart, #flutter]

m[0] is shorthand for m.group(0).

Groups: pull out the parts #

Brackets capture part of the match. Group 0 is everything, and groups 1, 2, 3 follow the opening brackets from left to right.

void main() {
  final date = RegExp(r'(\d{4})-(\d{2})-(\d{2})');
  final match = date.firstMatch('Published on 2026-10-10.')!;

  print(match.group(0));
  print(match.group(1));
  print(match.group(2));
  print(match.group(3));
}
2026-10-10
2026
10
10

Named groups #

Numbers become hard to follow. Give groups names with (?<name>...).

void main() {
  final pattern = RegExp(r'(?<user>[\w.]+)@(?<domain>[\w.-]+\.\w+)');
  final match = pattern.firstMatch('Contact: asha.rai@example.com.np today');

  if (match != null) {
    print(match.namedGroup('user'));
    print(match.namedGroup('domain'));
  }
}
asha.rai
example.com.np

Replace #

String.replaceAll accepts a RegExp. Use replaceAllMapped when the replacement depends on what was matched.

void main() {
  // Collapse runs of whitespace.
  print('too    many     spaces'.replaceAll(RegExp(r'\s+'), ' '));

  // Hide all but the last four digits.
  print('4111111111111111'.replaceAll(RegExp(r'\d(?=\d{4})'), '*'));

  // Swap day and month.
  print('10/31/2026'.replaceAllMapped(
    RegExp(r'(\d+)/(\d+)/(\d+)'),
    (m) => '${m[2]}/${m[1]}/${m[3]}',
  ));

  // Upper-case every word that follows a hash.
  print('use #dart daily'.replaceAllMapped(
    RegExp(r'#(\w+)'),
    (m) => '#${m[1]!.toUpperCase()}',
  ));
}
too many spaces
************1111
31/10/2026
use #DART daily

Split #

void main() {
  print('apple, banana;cherry  date'.split(RegExp(r'[,;\s]+')));
}
[apple, banana, cherry, date]

Options #

void main() {
  final anyCase = RegExp(r'dart', caseSensitive: false);
  print(anyCase.hasMatch('I love DART'));

  final lineStarts = RegExp(r'^\w+', multiLine: true);
  print(lineStarts.allMatches('one two\nthree four').map((m) => m[0]).toList());

  final emoji = RegExp(r'\p{Emoji_Presentation}', unicode: true);
  print(emoji.hasMatch('Hello 👋'));
}
true
[one, three]
true
OptionEffect
caseSensitive: falseIgnore upper and lower case
multiLine: true^ and $ match at each line, not only at the ends of the text
dotAll: true. also matches new lines
unicode: trueEnables \p{...} and treats emoji as single characters

Validation helpers #

final _email = RegExp(r'^[\w.+-]+@[\w-]+(\.[\w-]+)+$');
final _username = RegExp(r'^[a-z][a-z0-9_]{2,15}$');
final _hexColour = RegExp(r'^#([0-9a-fA-F]{6})$');

bool isEmail(String s) => _email.hasMatch(s);
bool isUsername(String s) => _username.hasMatch(s);
bool isHexColour(String s) => _hexColour.hasMatch(s);

bool isStrongPassword(String s) =>
    s.length >= 8 &&
    s.contains(RegExp(r'[A-Z]')) &&
    s.contains(RegExp(r'[a-z]')) &&
    s.contains(RegExp(r'\d'));

void main() {
  print(isEmail('rita@example.com'));
  print(isEmail('rita@example'));
  print(isUsername('dart_fan99'));
  print(isHexColour('#0B5FD9'));
  print(isStrongPassword('Secret123'));
}
true
false
true
true
true

Advice #

  • Create a RegExp once and reuse it. Building one inside a loop wastes time.
  • Start simple and test with real examples, including ones that should not match.
  • A regex for email can only check the rough shape. The real test is sending a message.
  • If a pattern needs a paragraph to explain, plain string methods (startsWith, split, contains) may be clearer.
  • Do not parse HTML or JSON with regular expressions. Use a parser.

Try it yourself #

Write a function that takes a log line such as 2026-10-10 14:05:33 [ERROR] Disk full on /dev/sda1 and returns a record with the date, the time, the level and the message, using named groups. Return null when the line does not match.

Practise in the playground Updated by Santosh Adhikari