Flutter 3.38 and Dart 3.10 are here!Learn more
Build a form with validation
How to build a form that validates input.
Apps often require users to enter information into a text field. For example, you might require users to log in with an email address and password combination.
To make apps secure and easy to use, check whether the information the user has provided is valid. If the user has correctly filled out the form, process the information. If the user submits incorrect information, display a friendly error message letting them know what went wrong.
In this example, learn how to add validation to a form that has a single text field using the following steps:
- Create a
Formwith aGlobalKey. - Add a
TextFormFieldwith validation logic. - Create a button to validate and submit the form.
1. Create aForm with aGlobalKey
# Create aForm. TheForm widget acts as a container for grouping and validating multiple form fields.
When creating the form, provide aGlobalKey. This assigns a unique identifier to yourForm. It also allows you to validate the form later.
Create the form as aStatefulWidget. This allows you to create a uniqueGlobalKey<FormState>() once. You can then store it as a variable and access it at different points.
If you made this aStatelessWidget, you'd need to store this keysomewhere. As it is resource expensive, you wouldn't want to generate a newGlobalKey each time you run thebuild method.
import'package:flutter/material.dart';// Define a custom Form widget.classMyCustomFormextendsStatefulWidget{constMyCustomForm({super.key});@overrideMyCustomFormStatecreateState(){returnMyCustomFormState();}}// Define a corresponding State class.// This class holds data related to the form.classMyCustomFormStateextendsState<MyCustomForm>{// Create a global key that uniquely identifies the Form widget// and allows validation of the form.//// Note: This is a `GlobalKey<FormState>`,// not a GlobalKey<MyCustomFormState>.final_formKey=GlobalKey<FormState>();@overrideWidgetbuild(BuildContextcontext){// Build a Form widget using the _formKey created above.returnForm(key:_formKey,child:constColumn(children:<Widget>[// Add TextFormFields and ElevatedButton here.],),);}} Using aGlobalKey is the recommended way to access a form. However, if you have a more complex widget tree, you can use theForm.of() method to access the form within nested widgets.
2. Add aTextFormField with validation logic
# Although theForm is in place, it doesn't have a way for users to enter text. That's the job of aTextFormField. TheTextFormField widget renders a material design text field and can display validation errors when they occur.
Validate the input by providing avalidator() function to theTextFormField. If the user's input isn't valid, thevalidator function returns aString containing an error message. If there are no errors, the validator must return null.
For this example, create avalidator that ensures theTextFormField isn't empty. If it is empty, return a friendly error message.
TextFormField(// The validator receives the text that the user has entered.validator:(value){if(value==null||value.isEmpty){return'Please enter some text';}returnnull;},),3. Create a button to validate and submit the form
#Now that you have a form with a text field, provide a button that the user can tap to submit the information.
When the user attempts to submit the form, check if the form is valid. If it is, display a success message. If it isn't (the text field has no content) display the error message.
ElevatedButton(onPressed:(){// Validate returns true if the form is valid, or false otherwise.if(_formKey.currentState!.validate()){// If the form is valid, display a snackbar. In the real world,// you'd often call a server or save the information in a database.ScaffoldMessenger.of(context).showSnackBar(constSnackBar(content:Text('Processing Data')),);}},child:constText('Submit'),),How does this work?
# To validate the form, use the_formKey created in step 1. You can use the_formKey.currentState accessor to access theFormState, which is automatically created by Flutter when building aForm.
TheFormState class contains thevalidate() method. When thevalidate() method is called, it runs thevalidator() function for each text field in the form. If everything looks good, thevalidate() method returnstrue. If any text field contains errors, thevalidate() method rebuilds the form to display any error messages and returnsfalse.
Interactive example
#import 'package:flutter/material.dart';void main() => runApp(const MyApp());class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const appTitle = 'Form Validation Demo'; return MaterialApp( title: appTitle, home: Scaffold( appBar: AppBar(title: const Text(appTitle)), body: const MyCustomForm(), ), ); }}// Create a Form widget.class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override MyCustomFormState createState() { return MyCustomFormState(); }}// Create a corresponding State class.// This class holds data related to the form.class MyCustomFormState extends State<MyCustomForm> { // Create a global key that uniquely identifies the Form widget // and allows validation of the form. // // Note: This is a GlobalKey<FormState>, // not a GlobalKey<MyCustomFormState>. final _formKey = GlobalKey<FormState>(); @override Widget build(BuildContext context) { // Build a Form widget using the _formKey created above. return Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextFormField( // The validator receives the text that the user has entered. validator: (value) { if (value == null || value.isEmpty) { return 'Please enter some text'; } return null; }, ), Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: ElevatedButton( onPressed: () { // Validate returns true if the form is valid, or false otherwise. if (_formKey.currentState!.validate()) { // If the form is valid, display a snackbar. In the real world, // you'd often call a server or save the information in a database. ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Processing Data')), ); } }, child: const Text('Submit'), ), ), ], ), ); }}To learn how to retrieve these values, check out theRetrieve the value of a text field recipe.
Unless stated otherwise, the documentation on this site reflects Flutter 3.38.1. Page last updated on 2025-10-28.View source orreport an issue.