The most comprehensive string validation package for Dart & Flutter.
Inspired by validator.js · 40+ validators & sanitizers · Works with Flutter Forms out of the box
Flutter Validators is a pure Dart package with 40+ string validators and 13 sanitizers — from emails and URLs to credit cards, UUIDs, JWTs and strong-password checks. Every validator works three ways:
- as a top-level function —
isEmail('foo@bar.com') - as a
Stringextension —'foo@bar.com'.isEmail - as a Flutter form validator —
Validator.email()plugs straight intoTextFormField
Zero runtime dependencies. Fully tested. Works with both Dart and Flutter.
- ✨ Features
- 📦 Installation
- 🚀 Quick Start
- 🧩 Validators
- 🧹 Sanitizers
- 📝 Flutter Form Integration
- 💡 Behavior Notes and FAQ
- 🤝 Contributing
- 📄 License
- 40+ validators covering email, URL, numbers, encodings, identifiers, crypto hashes and more.
- 13 sanitizers for trimming, HTML escaping, character filtering and type conversion.
- Three usage styles — top-level functions,
Stringextensions, and Flutter form validators — pick whatever reads best. - First-class Flutter form support via the
Validatorclass, which returnsString? Function(String?)closures with customizable error messages. - Pure Dart, zero runtime dependencies — lightweight and safe to add to any project.
- Fully tested — every validator and sanitizer has dedicated test coverage.
Add the package to your pubspec.yaml:
dependencies:
flutter_validators: ^1.2.0Then run:
dart pub getImport the package:
import'package:flutter_validators/flutter_validators.dart';As String extensions — the most concise style:
'foo@bar.com'.isEmail; // true'https://google.com'.isURL; // true'4111111111111111'.isCreditCard; // true'abc123'.isAlphanumeric; // trueAs top-level functions — handy when the value isn't a literal:
isEmail('foo@bar.com'); // trueisURL('https://google.com'); // trueisIP('192.168.1.1'); // trueAs Flutter form validators — drop straight into TextFormField:
TextFormField(
validator:Validator.email(errorMessage:'Enter a valid email'),
)Every validator is available both as a top-level function and as a String extension. Parameterized validators accept their options as named/positional arguments.
| Validator | Extension | Description |
|---|---|---|
isEmail(str) | str.isEmail | Valid email address |
isURL(str) | str.isURL | Valid HTTP/HTTPS URL |
isFQDN(str) | str.isFQDN | Fully qualified domain name |
isPhone(str) | str.isPhone | Phone number (international & US formats) |
isLatLong(str) | str.isLatLong | latitude,longitude coordinate pair |
'user@example.com'.isEmail; // true'https://dart.dev'.isURL; // true'sub.example.co.uk'.isFQDN; // true'localhost'.isFQDN; // false (no TLD)'(123) 456-7890'.isPhone; // true'40.7128,-74.0060'.isLatLong; // true| Validator | Extension | Description |
|---|---|---|
isInt(str) | str.isInt | Integer (positive or negative) |
isNumeric(str) | str.isNumeric | Number (integer or float) |
isFloat(str, {min, max}) | str.isFloat({min, max}) | Finite float, optionally within a range |
isDecimal(str) | str.isDecimal | Decimal number |
isHexadecimal(str) | str.isHexadecimal | Hexadecimal number |
isOctal(str) | str.isOctal | Octal number |
isPort(str) | str.isPort | Port number (0–65535) |
'42'.isInt; // true'3.14'.isNumeric; // true'1.5'.isFloat(); // true'5'.isFloat(min:0, max:2); // false (out of range)'.5'.isDecimal; // true'deadBEEF'.isHexadecimal; // true'0o17'.isOctal; // true'8080'.isPort; // true'65536'.isPort; // false (out of range)| Validator | Extension | Description |
|---|---|---|
isAlpha(str) | str.isAlpha | Letters only (a–z, A–Z) |
isAlphanumeric(str) | str.isAlphanumeric | Letters and numbers only |
isAscii(str) | str.isAscii | ASCII characters only |
isLowercase(str) | str.isLowercase | Entirely lowercase |
isUppercase(str) | str.isUppercase | Entirely uppercase |
isLength(str, min, [max]) | str.isLength(min, [max]) | Length within a range |
isByteLength(str, min, [max]) | str.isByteLength(min, [max]) | UTF-8 byte length within a range |
isSlug(str) | str.isSlug | URL slug (my-blog-post) |
isIn(str, values) | str.isIn(values) | One of an allowed set of values |
matches(str, pattern) | str.matches(pattern) | Matches a Pattern / RegExp |
contains(str, seed, {ignoreCase, minOccurrences}) | — | Contains a substring |
equals(str, comparison) | str.equals(comparison) | Exact (case-sensitive) string match |
'Hello'.isAlpha; // true'abc123'.isAlphanumeric; // true'héllo'.isAscii; // false'hello'.isLowercase; // true'abc'.isLength(2, 5); // true'é'.isByteLength(2, 2); // true ('é' is 2 bytes in UTF-8)'my-blog-post'.isSlug; // true'red'.isIn(['red', 'green', 'blue']); // true'abc123'.matches(RegExp(r'\d+')); // true'foo'.equals('foo'); // true// `contains` is a top-level function only (see Behavior Notes)contains('hello world', 'world'); // truecontains('Hello World', 'world', ignoreCase:true); // truecontains('a-a-a', 'a', minOccurrences:3); // true| Validator | Extension | Description |
|---|---|---|
isBase32(str) | str.isBase32 | Base32 encoded |
isBase58(str) | str.isBase58 | Base58 encoded |
isBase64(str, {urlSafe}) | str.isBase64({urlSafe}) | Base64 encoded (standard or URL-safe) |
isJson(str) | str.isJson | Valid JSON |
isHexColor(str) | str.isHexColor | Hex color code (#fff, ff0000) |
isBoolean(str) | str.isBoolean | Boolean string (true/false/1/0) |
isDate(str) | str.isDate | Parseable date string |
'JBSWY3DP'.isBase32; // true'aGVsbG8='.isBase64(); // true'a-b_cdef'.isBase64(urlSafe:true); // true'{"name":"Dart"}'.isJson; // true'#ff0000'.isHexColor; // true'true'.isBoolean; // true'2024-01-15'.isDate; // true| Validator | Extension | Description |
|---|---|---|
isUUID(str) | str.isUUID | UUID (v1, v3, v4, v5) |
isMongoId(str) | str.isMongoId | MongoDB ObjectId (24-char hex) |
isMD5(str) | str.isMD5 | MD5 hash |
isJWT(str) | str.isJWT | JSON Web Token |
isCreditCard(str) | str.isCreditCard | Credit card number (Luhn algorithm) |
isMACAddress(str) | str.isMACAddress | MAC address (EUI-48 / EUI-64) |
isSemVer(str) | str.isSemVer | Semantic version |
'550e8400-e29b-41d4-a716-446655440000'.isUUID; // true'507f1f77bcf86cd799439011'.isMongoId; // true'd41d8cd98f00b204e9800998ecf8427e'.isMD5; // true'eyJhbGci.eyJzdWIi.SflKxwRJ'.isJWT; // true'4111111111111111'.isCreditCard; // true'00:1B:44:11:3A:B7'.isMACAddress; // true'2.1.0-alpha.1'.isSemVer; // true| Validator | Extension | Description |
|---|---|---|
isStrongPassword(str, {...}) | str.isStrongPassword({...}) | Password meets configurable strength rules |
isStrongPassword accepts five options, all with sensible defaults:
| Option | Default | Meaning |
|---|---|---|
minLength | 8 | Minimum total length |
minLowercase | 1 | Minimum lowercase letters |
minUppercase | 1 | Minimum uppercase letters |
minNumbers | 1 | Minimum digits |
minSymbols | 1 | Minimum non-alphanumeric symbols |
'Abcd1234!'.isStrongPassword(); // true'weak'.isStrongPassword(); // false// Relax the rules — e.g. allow passphrases with no symbols or digits'abcdefghij'.isStrongPassword(
minUppercase:0,
minNumbers:0,
minSymbols:0,
); // trueSanitizers transform or coerce strings. Like validators, they're available as both top-level functions and String extensions. Import them via the main library or directly:
import'package:flutter_validators/flutter_validators.dart';
// or, sanitizers only:import'package:flutter_validators/sanitizers.dart';| Sanitizer | Extension | Description |
|---|---|---|
trim(str, [chars]) | str.trimChars(chars) | Trim whitespace/chars from both ends |
ltrim(str, [chars]) | str.ltrimChars(chars) | Trim from the start |
rtrim(str, [chars]) | str.rtrimChars(chars) | Trim from the end |
trim(' hello '); // 'hello'trim('xxhelloxx', 'x'); // 'hello'ltrim('00042', '0'); // '42'rtrim('hello!!!', '!'); // 'hello'| Sanitizer | Extension | Description |
|---|---|---|
escape(str) | str.escape() | Escape HTML-unsafe characters |
unescape(str) | str.unescape() | Reverse of escape |
escape('<script>alert(1)</script>');
// '<script>alert(1)</script>'unescape('<b>hi</b>'); // '<b>hi</b>'| Sanitizer | Extension | Description |
|---|---|---|
blacklist(str, chars) | str.blacklist(chars) | Remove the listed characters |
whitelist(str, chars) | str.whitelist(chars) | Keep only the listed characters |
stripLow(str, {keepNewLines}) | str.stripLow({keepNewLines}) | Remove ASCII control characters |
blacklist('hello world', 'lo'); // 'he wrd'whitelist('a1b2c3', '0123456789'); // '123'stripLow('line1\nline2'); // 'line1line2'stripLow('line1\nline2', keepNewLines:true); // 'line1\nline2'| Sanitizer | Extension | Returns | Description |
|---|---|---|---|
toBoolean(str, {strict}) | str.toBoolean({strict}) | bool | Convert to a boolean |
toInt(str, {radix}) | str.toInt({radix}) | int? | Parse to an integer |
toFloat(str) | str.toFloat() | double? | Parse to a double |
toDate(str) | str.toDate() | DateTime? | Parse to a DateTime |
toBoolean('true'); // truetoBoolean('0'); // falsetoBoolean('yes', strict:true); // false (strict: only '1'/'true' are true)toInt('42'); // 42toInt('ff', radix:16); // 255toInt('abc'); // nulltoFloat('3.14'); // 3.14toDate('2024-01-15'); // DateTime(2024, 1, 15)| Sanitizer | Extension | Returns | Description |
|---|---|---|---|
normalizeEmail(str) | str.normalizeEmail() | String? | Canonicalize an email address |
normalizeEmail('Test.User+promo@GMAIL.com'); // 'testuser@gmail.com'normalizeEmail('User@Example.COM'); // 'User@example.com'normalizeEmail('not-an-email'); // nullThe Validator class returns String? Function(String?) closures — exactly the type TextFormField.validator expects. A closure returns null when the value is valid, or the error message when it isn't. Every method accepts a custom errorMessage.
import'package:flutter/material.dart';
import'package:flutter_validators/flutter_validators.dart';
classSignUpFormextendsStatefulWidget {
constSignUpForm({super.key});
@overrideState<SignUpForm> createState() =>_SignUpFormState();
}
class_SignUpFormStateextendsState<SignUpForm> {
final _formKey =GlobalKey<FormState>();
@overrideWidgetbuild(BuildContext context) {
returnForm(
key: _formKey,
child:Column(
children: [
// Combine `required` with `email` to enforce a non-empty, valid email.TextFormField(
decoration:constInputDecoration(labelText:'Email'),
autovalidateMode:AutovalidateMode.onUserInteraction,
validator: (value) {
returnValidator.required(errorMessage:'Email is required')(value) ??Validator.email(errorMessage:'Enter a valid email')(value);
},
),
TextFormField(
decoration:constInputDecoration(labelText:'Website'),
autovalidateMode:AutovalidateMode.onUserInteraction,
validator:Validator.url(),
),
TextFormField(
decoration:constInputDecoration(labelText:'Password'),
obscureText:true,
autovalidateMode:AutovalidateMode.onUserInteraction,
validator:Validator.strongPassword(
errorMessage:'Use 8+ chars with upper, lower, number & symbol',
),
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
constSnackBar(content:Text('Form is valid!')),
);
}
},
child:constText('Sign Up'),
),
],
),
);
}
}A complete, runnable app is in the example/ directory.
Every method on the Validator class returns String? Function(String?):
Validator.required({String errorMessage})
Validator.email({String errorMessage})
Validator.url({String errorMessage})
Validator.ip({int? version, String errorMessage})
Validator.fqdn({String errorMessage})
Validator.phone({String errorMessage})
Validator.latLong({String errorMessage})
Validator.date({String errorMessage})
Validator.numeric({String errorMessage})
Validator.integer({String errorMessage})
Validator.float({double? min, double? max, String errorMessage})
Validator.decimal({String errorMessage})
Validator.hexadecimal({String errorMessage})
Validator.octal({String errorMessage})
Validator.port({String errorMessage})
Validator.alpha({String errorMessage})
Validator.alphanumeric({String errorMessage})
Validator.ascii({String errorMessage})
Validator.lowercase({String errorMessage})
Validator.uppercase({String errorMessage})
Validator.slug({String errorMessage})
Validator.length(int min, {int? max, String errorMessage})
Validator.byteLength(int min, {int? max, String errorMessage})
Validator.contains(String seed, {bool ignoreCase, int minOccurrences, String errorMessage})
Validator.matches(Pattern pattern, {String errorMessage})
Validator.inList(Iterable<String> allowed, {String errorMessage})
Validator.equals(String comparison, {String errorMessage})
Validator.base32({String errorMessage})
Validator.base58({String errorMessage})
Validator.base64({bool urlSafe, String errorMessage})
Validator.json({String errorMessage})
Validator.hexColor({String errorMessage})
Validator.boolean({String errorMessage})
Validator.uuid({String errorMessage})
Validator.mongoId({String errorMessage})
Validator.md5({String errorMessage})
Validator.jwt({String errorMessage})
Validator.creditCard({String errorMessage})
Validator.macAddress({String errorMessage})
Validator.semVer({String errorMessage})
Validator.strongPassword({int minLength, int minLowercase, int minUppercase, int minNumbers, int minSymbols, String errorMessage})Validator methods treat null and empty strings as valid. This is intentional — it lets you compose validators freely. To make a field mandatory, pair it with Validator.required():
validator: (value) {
returnValidator.required()(value) ??Validator.email()(value);
}contains is a top-level function only. Dart's String already has a built-in .contains() method, so the package does not add a conflicting extension. Use contains(str, seed) instead of str.contains(...) when you need the case-insensitivity or minOccurrences options.
Trimming extensions are named trimChars / ltrimChars / rtrimChars. Dart's String already provides .trim(), .trimLeft() and .trimRight() for whitespace, so the custom-character variants use distinct names to avoid collisions. The top-level functions keep the plain trim / ltrim / rtrim names.
isURL accepts only http and https schemes. Other schemes such as ftp:// are rejected.
isBase64 has a urlSafe option. By default it validates the standard Base64 alphabet (with padding); pass urlSafe: true to validate the URL- and filename-safe alphabet instead.
isBoolean accepts 'true', 'false', '1' and '0'. Any other value is not a boolean string.
isFloat rejects non-finite values.'Infinity' and 'NaN' return false, even though Dart's double.tryParse can parse them.
normalizeEmail applies Gmail-specific rules. For gmail.com / googlemail.com addresses it lowercases the local part, removes dots, and strips any +tag suffix. For other providers it only lowercases the domain. It returns null if the input isn't a valid email.
Contributions, issues, and feature requests are welcome! Feel free to check the issues page.
This project is MIT licensed.
