Form Builder Validators offer a set of validators for any FormField
widget or widgets that extend the FormField
class - e.g., TextFormField
, DropdownFormField
, et cetera. It provides standard ready-made validation rules and a way to compose new validation rules combining multiple rules, including custom ones.
It also includes the l10n
/ i18n
of error text messages to multiple languages.
We are looking for maintainers to contribute to the development and maintenance of Flutter Form Builder Ecosystem. It is very important to keep the project alive and growing, so we need your help to keep it up to date and with new features. You can contribute in many ways, we describe some of them in Support section.
- Features
- Validators
- Supported languages
- Use
- Migrations
- How this package works
- Support
- Roadmap
- Ecosystem
- Thanks to
- Ready-made validation rules
- Compose multiple reusable validation rules
- Default error messages in multiple languages
This package comes with several of the most common FormFieldValidator
s such as required, numeric, email,
URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit card, etc., with default errorText
messages.
Generally, the validators are separated in three main groups:
- Field Requirement Validators: makes a field optional or required.
- Type Validators: checks the type of the field.
- Other validators: make any other kind of check that is not related to null/emptiness or type validation.
Generally, we build a validator composing those three types in the following way:
<fieldRequirementValidator>(<typeValidator>(<otherValidator>()))
For example:
- Make the field required, check if it is of type
num
or aString
parsable to num and then check if it is greater than 10.Validators.required(Validators.num(Validators.greaterThan(10)))
As easy as that! This validator is meant to be used with form fields like in the following example:
// Example from the folder `examples`
TextFormField(
decoration: const InputDecoration(
labelText: 'Min Value Field',
prefixIcon: Icon(Icons.exposure_neg_1),
),
keyboardType: TextInputType.number,
validator: Validators.required(Validators.num(Validators.greaterThan(10))),
textInputAction: TextInputAction.next,
autovalidateMode: AutovalidateMode.always,
);
Validators.equalLength(expectedLength)
: Checks if the field contains a collection (must be aString
,Iterable
, orMap
) with length equalsexpectedLength
.Validators.minLength(min)
: Checks if the field contains a collection (must be aString
,Iterable
, orMap
) with length greater than or equal tomin
.Validators.maxLength(max)
: Checks if the field contains a collection (must be aString
,Iterable
, orMap
) with length less than or equal tomax
.Validators.betweenLength(min, max)
: Checks if the field contains a collection (must be aString
,Iterable
, orMap
) with length betweenmin
andmax
, inclusive.
Validators.and(validators)
: Validates the field by requiring it to pass all validators in thevalidators
list.Validators.or(validators)
: Validates the field by requiring it to pass at least one of the validators in thevalidators
list.
Validators.validateIf(condition, v)
: Validates the field with validatorv
only ifcondition
istrue
.Validators.skipIf(condition, v)
: Validates the field with validatorv
only ifcondition
isfalse
.
Validators.debugPrintValidator()
: Print, for debug purposes, the user input value.
Validators.equal(value)
: Checks if the field contains an input that is equal tovalue
(==).Validators.notEqual(value)
: Checks if the field contains an input that is not equal tovalue
(!=).
Validators.required(next)
: Makes the field required by checking if it contains a non-null and non-empty value, passing it to thenext
validator as a not-nullable type.Validators.optional(next)
: Makes the field optional by passing it to thenext
validator if it contains a non-null and non-empty value. If the field is null or empty, null is returned.Validators.validateWithDefault(defaultValue, next)
: Validates the field withnext
validator. If the input is null, it uses thedefaultValue
instead.
Validators.transformAndValidate<IN, OUT>(transformFunction, next:next)
: Transforms an input fromIN
type toOUT
type through the functiontransformFunction
and pass it to thenext
validator.
Validators.after(reference)
: Checks if the field contains aDateTime
that is afterreference
.Validators.before(reference)
: Checks if the field contains aDateTime
that is beforereference
.Validators.betweenDateTime(minReference, maxReference)
: Checks if the field contains aDateTime
that is afterminReference
and beforemaxReference
.
Validators.maxFileSize(max, base:base)
: Checks if the field contains a file size that is less than themax
size withbase
1000 or 1024.- TODO [ ]
Validators.minFileSize(min, base:base)
: Checks if the field contains a file size that is less than themax
size withbase
1000 or 1024.
Validators.bic()
: Checks if the field contains a valid BIC (Bank Identifier Code).Validators.iban()
- Checks if the field contains a valid IBAN (International Bank Account Number).
Validators that check a generic type user input.
Validators.inList(values)
: Checks if the field contains a value that is in the listvalues
.Validators.notInList(values)
: Checks if the field DOES NOT contain a value that is in the listvalues
.Validators.isTrue()
: Checks if the field contains a boolean or a parsableString
of thetrue
value.Validators.isFalse()
: Checks if the field contains a boolean or a parsableString
of thefalse
value.Validators.satisfy(condition)
: Checks if the field satisfies thecondition
.
Validators.colorCode()
: checks if the field contains a valid color.Validators.isbn()
: checks if the field contains a valid ISBN code.
Validators.ip()
: Checks if the field contains a properly formattedInternet Protocol
(IP) address. It may check for eitherIPv4
, orIPv6
or even for both.Validators.url()
: Checks if the field contains a properly formattedUniform Resource Locators
(URL).Validators.macAddress()
: Checks if the field is a valid MAC address.
Validators.between(min, max)
: Checks if the field contains a number that is in the inclusive range [min, max].Validators.greaterThan(reference)
: Checks if the field contains a number that is greater thanreference
.Validators.greaterThanOrEqualTo(reference)
: Checks if the field contains a number that is greater than or equal toreference
.Validators.lessThan(reference)
: Checks if the field contains a number that is less thanreference
.Validators.lessThanOrEqualTo(reference)
: Checks if the field contains a number that is less than or equal toreference
.
Validators.matchesAllowedExtensions(extensions)
: Checks if the field contains aString
that is in the listextensions
.
Validators.contains(substring)
- Checks if the field contains thesubstring
.- TODO [ ]
Validators.notContains(substring)
- Checks if the field does not contain thesubstring
. - TODO [ ]
FormBuilderValidators.endsWith()
- requires the substring to be the end of the field's value. - TODO [ ]
FormBuilderValidators.notEndsWith()
- requires the substring not to be the end of the field's value. - TODO [ ]
FormBuilderValidators.startsWith()
- requires the substring to be the start of the field's value. - TODO [ ]
FormBuilderValidators.notStartsWith()
- requires the substring not to be the start of the field's value. - TODO [ ]
FormBuilderValidators.lowercase()
- requires the field's value to be lowercase. - TODO [ ]
FormBuilderValidators.uppercase()
- requires the field's value to be uppercase. Validators.hasMinUppercaseChars(min:min)
- Checks if the field has a minimum number of uppercase chars.- TODO [ ]
Validators.hasMaxUppercaseChars(max:max)
- Checks if the field has a maximum number of uppercase chars. Validators.hasMinLowercaseChars(min:min)
- Checks if the field has a minimum number of lowercase chars.- TODO [ ]
Validators.hasMaxLowercaseChars(max:max)
- Checks if the field has a maximum number of lowercase chars. Validators.hasMinNumericChars(min:min)
- Checks if the field has a minimum number of numeric chars.- TODO [ ]
Validators.hasMaxNumericChars(max:max)
- Checks if the field has a maximum number of numeric chars. Validators.hasMinSpecialChars(min:min)
- Checks if the field has a minimum number of special chars.- TODO [ ]
Validators.hasMaxSpecialChars(max:max)
- Checks if the field has a maximum number of special chars. Validators.match(regExp)
- Checks if the field matches with the regular expressionregExp
.- TODO [ ]
Validators.notMatch(regExp)
- Checks if the field does not match with the regular expressionregExp
. Validators.uuid()
- Checks if the field is a valid Universally Unique Identifier (UUID).- TODO [ ]
FormBuilderValidators.maxWordsCount()
- requires the word count of the field's value to be less than or equal to the provided maximum count. - TODO [ ]
FormBuilderValidators.minWordsCount()
- requires the word count of the field's value to be greater than or equal to the provided minimum count.
Validators.string(next)
: Checks if the field contains a validString
and passes the input asString
to thenext
validator.Validators.int(next)
: Checks if the field contains a validint
or parsableString
toint
and passes the input asint
to thenext
validator.Validators.double(next)
: Checks if the field contains a validdouble
or parsableString
todouble
and passes the input asdouble
to thenext
validator.Validators.num(next)
: Checks if the field contains a validnum
or parsableString
tonum
and passes the input asnum
to thenext
validator.Validators.bool(next)
: Checks if the field contains a validbool
or parsableString
tobool
and passes the input asbool
to thenext
validator.Validators.dateTime(next)
: Checks if the field contains a validDateTime
or parsableString
toDateTime
and passes the input asDateTime
to thenext
validator.
Validators.email()
: Checks if the field contains a valid email.Validators.password()
: Checks if the field contains a valid password. A password may require some conditions to be met in order to be considered as valid.Validators.phoneNumber()
: Checks if the field contains a valid phone number.
Validators support default errorText
messages in these languages:
- Albanian (al)
- Arabic (ar)
- Bangla (bn)
- Bosnian (bs)
- Bulgarian (bg)
- Catalan (ca)
- Chinese Simplified (zh_Hans)
- Chinese Traditional (zh_Hant)
- Croatian (hr)
- Czech (cs)
- Danish (da)
- Dutch (nl)
- English (en)
- Estonian (et)
- Finnish (fi)
- Farsi/Persian (fa)
- French (fr)
- German (de)
- Greek (el)
- Hebrew (he)
- Hungarian (hu)
- Hindi (hi)
- Indonesian (id)
- Italian (it)
- Japanese (ja)
- Kurdish (ku)
- Korean (ko)
- Khmer (km)
- Lao (lo)
- Latvian (lv)
- Malay (ms)
- Mongolian (mn)
- Norwegian (no)
- Polish (pl)
- Portuguese (pt)
- Romanian (ro)
- Russian (ru)
- Slovak (sk)
- Slovenian (sl)
- Spanish (es)
- Swahili (sw)
- Swedish (se)
- Tamil(ta)
- Thai (th)
- Turkish (tr)
- Ukrainian (uk)
- Vietnamese (vi)
And you can still add your custom error messages.
The default error message is in English. To allow for localization of default error messages within your app, add FormBuilderLocalizations.delegate
in the list of your app's localizationsDelegates
.
For example, in your MaterialApp
:
return MaterialApp(
supportedLocales: [
Locale('de'),
Locale('en'),
Locale('es'),
Locale('fr'),
Locale('it'),
...
],
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
FormBuilderLocalizations.delegate, // here
],
In the following example, we have a required field for the Name
of the user:
TextFormField(
decoration: InputDecoration(labelText: 'Name'),
autovalidateMode: AutovalidateMode.always,
validator: Validators.required(),
),
See pub.dev example tab or github code for more details
The Validators
class comes with a handy static function named and()
, which takes a list of Validator
functions. Composing allows you to create once and reuse validation rules across multiple fields, widgets, or apps.
On validation, each validator is run, and if any validator returns a non-null value (i.e., a String), validation fails, and the errorText
for the field is set as the first returned string or as a composition of all failures.
In the following example, we have a form field for the user's Age
, which is required, must be numeric (with custom error message if not numeric given by "La edad debe ser numérica."), must be less than 70, and, finally, must be non-negative (with custom non-negative validator):
TextFormField(
decoration: InputDecoration(labelText: 'Age'),
keyboardType: TextInputType.number,
autovalidateMode: AutovalidateMode.always,
validator: Validators.required(
Validators.and(<Validator<String>>[
Validators.num(Validators.lessThan(70), (_) => 'La edad debe ser numérica.'),
/// Include your own custom `Validator` function, if you want.
/// Ensures positive values only. We could also have used `Validators.greaterThanOrEqualTo(0)` instead.
(String? val) {
if (val != null) {
final int? number = int.tryParse(val);
// todo bug here: if it is not int, it accepts negative
// numbers
if (number == null) return null;
if (number < 0) return 'We cannot have a negative age';
}
return null;
}
]))
),
see override_form_builder_localizations_en for more detail.
- Deprecate
FormBuilderValidators
class with its static methods as validators. - Instead, you should use
Validators
class. - Check the file migrations for detailed instructions.
- All validators now first check for null or empty value and return an error if so. You can set
checkNullOrEmpty
tofalse
if you want to avoid this behavior. dateString()
changed todate()
for constancy in api naming. Simply change the name to fix the code.- The positional parameter for the validator
match()
is not aString
pattern anymore, but aRegExp
regex.
Remove context
as a parameter to validator functions. For example, FormBuilderValidators.required(context)
becomes FormBuilderValidators.required()
without context
passed in.
This package comes with several of the most common Validator
s and FormFieldValidator
s such as required, numeric, mail,
URL, min, max, minLength, maxLength, minWordsCount, maxWordsCount, IP, credit card, etc., with default errorText
messages.
- But what is a
FormFieldValidator
? It is a function that takes user input of any nullable type and returns either null (for valid input) or an error message string (for invalid input). The input parameter can beString?
,int?
,num?
, or any other nullable type that represents user-provided data. The input must be nullable since form fields may be empty, resulting in null values.
For example, here's a FormFieldValidator
that checks if a number is even:
String? isEven(int? userInput) {
return (userInput != null && userInput % 2 == 0) ? null : 'This field must be even';
}
The challenge with the previous approach is that we must handle null checks in every validator implementation. This leads to:
- Tangled validator logic: Each validator must handle both validation rules and null checking,making the code harder to understand and maintain.
- Code duplication: When composing validators, the same null-checking logic must be repeated across multiple validators, violating the DRY principle.
- Poor separation of concerns: A validator named
isEven
should focus solely on checking if a number is even. It shouldn't need to handle null cases or type validation - those are separate responsibilities that deserve their own focused validators. - Verbose implementations: The combination of null checks, type validation, and business logic in each validator results in unnecessarily lengthy code that's harder to test and maintain.
- Potential problems with null safety: imagine an unsafe version of isEven that simply uses the
!
operator to throw an error during runtime if the user input is null:
/// `userInput` may not be null.
String? isEven(int? userInput) {
return (userInput! % 2 == 0) ? null : 'This field must be even';
}
This package introduces a more precise approach that separates null-value handling from the actual validation logic. Instead of the previous isEven implementation, we can compose two focused validators:
String? Function(int) isEven(){
return (int userInput){
return (userInput % 2 == 0) ? null:'This field must be even';
};
}
String? Function(int?) required(String? Function(int) next){
return (int? userInput) {
return (userInput != null) ? next(userInput):'This field is required';
};
}
// Important: isEven() does not return a FormFieldValidator, but the composition required(isEven()), does.
final validator = required(isEven());
By introducing this level of indirection, we achieve:
- Clean separation between null checks and validation logic
- More composable validators
- Specific error messages for missing vs invalid input
- Type-safe validator chains
You have some ways to contribute to this package.
- Beginner: Reporting bugs or requesting new features
- Intermediate: Answer questions, implement new features (from issues or not), and create pull requests
- Advanced: Join organization like a member and help to code, manage issues, discuss new features, and other things
See the contribution file for more details
We welcome efforts to internationalize/localize the package by translating the default validation errorText
strings for built-in validation rules.
-
Add ARB files
Create one ARB file inside the
lib/l10n
folder for each locale you need to add support. Name the files in the following way:intl_<LOCALE_ISO_CODE>.arb
. For example:intl_fr.arb
orintl_fr_FR.arb
. -
Translate the error messages
Copy and paste the contents of
intl_en.arb
into your newly created ARB file. Then translate the error messages by overwriting the default messages. -
Generate localization code
To generate boilerplate code for localization, run the generate command inside the package directory where
pubspec.yaml
file is located:flutter gen-l10n
The command will automatically create/update files inside the
lib/localization
directory, including your newly added locale support. The files in here are only necessary for local development and will not be committed to Github. -
Update README
Remember to update README, adding the new language (and language code) under Supported languages section in alphabetic order, so that everyone knows your new language is now supported!
-
Submit PR
Submit your PR and be of help to millions of developers all over the world!
- Add a new validator to one of the folders in the
src/validators
folder. - Implement it as a function which returns
Validator<T>
withT
being the type of the user input to be validated. - Add the @macro tag for documentation using the template name:
validator_<validator_snake_case_name>
. This will refer to the actual documentation, which will be on theValidators
static method. - If your validator uses localized error message, you can use
FormBuilderLocalizations.current.<name_of_localized_message>
Next we have the example of the numeric validatorgreaterThan
. As we can see, it has its@macro
docstring, it uses a localized error message (FormBuilderLocalizations.current.greaterThanErrorText(reference)
) and it returnsValidator<T extends num>
:/// {@macro validator_greater_than} Validator<T> greaterThan<T extends num>(T reference, {String Function(T input, T reference)? greaterThanMsg}) { return (T input) { return input > reference ? null : greaterThanMsg?.call(input, reference) ?? FormBuilderLocalizations.current.greaterThanErrorText(reference); }; }
- Add the validator as static method to
form_builder_validators.dart
in theValidators
class. Do not forget to add documentation to the new static method, using the@template
element to give a name to the docstring. Follow the pattern:validator_<validator_snake_case_name>
. Here, an example of how to add the static method of the validator to theValidators
class:final class Validators{ //Other validators... /// {@template validator_greater_than} /// Creates a validator function that checks if a numeric input exceeds `reference`. /// /// ## Type Parameters /// - `T`: A numeric type that extends [num], allowing `int`, `double` or /// `num` validations /// /// ## Parameters /// - `reference` (`T`): The threshold value that the input must exceed /// - `greaterThanMsg` (`String Function(T input, T reference)?`): Optional custom error /// message generator that takes the input value and threshold as parameters /// /// ## Returns /// Returns a [Validator] function that: /// - Returns `null` if the input is greater than the threshold value `reference` /// - Returns an error message string if validation fails, either from the custom /// `greaterThanMsg` function or the default localized error text /// /// ## Examples /// ```dart /// // Basic usage with integers /// final ageValidator = greaterThan<int>(18); /// /// // Custom error message /// final priceValidator = greaterThan<double>( /// 0.0, /// greaterThanMsg: (_, ref) => 'Price must be greater than \$${ref.toStringAsFixed(2)}', /// ); /// ``` /// /// ## Caveats /// - The validator uses strict greater than comparison (`>`) /// {@endtemplate} static Validator<T> greaterThan<T extends c.num>(T reference, {String Function(c.num input, c.num reference)? greaterThanMsg}) => val.greaterThan(reference, greaterThanMsg: greaterThanMsg); } // OBS.: the core package is imported with prefix c to avoid name collision!
- Implement tests
- Add to validators with name and description
- Add message error translated on all languages (yes, all languages). To accomplish this need:
a. Add property to all
intl_*.arb
files, in alphabetic order. b. Translate message in all languages. c. Runflutter gen-l10n
command - Run dart
dart fix --apply
anddart format .
- Submit PR
You can ask questions or search for answers on Github discussion or on StackOverflow
Donate or become a sponsor of Flutter Form Builder Ecosystem
Take a look at our fantastic ecosystem and all packages in there