Flutter Form Validation Regex: Email, Phone, and Password Examples
Practical regex patterns for validating emails, phone numbers, and passwords in Flutter forms, with full code examples.

Flutter form validation regex patterns are useful when you want to catch obvious input mistakes before a user submits a form. Client-side validation improves the experience, but it is not security. Your backend still needs to validate everything again.
Use Flutter validation to guide users. Use server validation to protect the system.
Flutter Form Validation Regex: The Basics
Flutter’s TextFormField has a validator callback. It receives the current value and returns:
nullwhen the field is valid- A string message when the field is invalid
TextFormField(
decoration: const InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Email is required';
}
return null;
},
)
The form runs validators when you call:
final isValid = formKey.currentState!.validate();
For production code, avoid using ! unless the key is definitely attached. In most form submit buttons inside the same widget, it is acceptable because you control the form lifecycle.
Email Regex
Email validation can become impossibly complex if you try to match every valid email in the world. For app forms, a practical pattern is enough:
final emailRegex = RegExp(r'^[^\s@]+@[^\s@]+\.[^\s@]+$');
String? validateEmail(String? value) {
final email = value?.trim() ?? '';
if (email.isEmpty) {
return 'Email is required';
}
if (!emailRegex.hasMatch(email)) {
return 'Enter a valid email address';
}
return null;
}
This catches missing @, missing domain parts, and accidental spaces. The backend should still verify deliverability if that matters.
Password Strength Regex
Password rules depend on your product. A common rule is at least eight characters, one uppercase letter, one lowercase letter, and one number:
final passwordRegex = RegExp(
r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$',
);
String? validatePassword(String? value) {
final password = value ?? '';
if (password.isEmpty) {
return 'Password is required';
}
if (!passwordRegex.hasMatch(password)) {
return 'Use 8+ chars with uppercase, lowercase, and a number';
}
return null;
}
Be careful with overly strict password rules. Blocking symbols, limiting length, or forcing awkward patterns can make accounts less secure if users start reusing predictable passwords.
Phone Number Validation
Phone numbers are harder than they look. Countries have different lengths, prefixes, spacing, and formatting rules.
For a simple international format, you can require a plus sign and 8 to 15 digits:
final phoneRegex = RegExp(r'^\+[1-9]\d{7,14}$');
String? validatePhone(String? value) {
final phone = value?.replaceAll(' ', '') ?? '';
if (phone.isEmpty) {
return 'Phone number is required';
}
if (!phoneRegex.hasMatch(phone)) {
return 'Use international format, e.g. +2348012345678';
}
return null;
}
For serious phone validation, use a library or backend service that understands regional numbering rules.
Full Working Form Example
import 'package:flutter/material.dart';
class SignupForm extends StatefulWidget {
const SignupForm({super.key});
@override
State<SignupForm> createState() => _SignupFormState();
}
class _SignupFormState extends State<SignupForm> {
final formKey = GlobalKey<FormState>();
final emailRegex = RegExp(r'^[^\s@]+@[^\s@]+\.[^\s@]+$');
final passwordRegex = RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$');
final phoneRegex = RegExp(r'^\+[1-9]\d{7,14}$');
String? validateEmail(String? value) {
final email = value?.trim() ?? '';
if (email.isEmpty) return 'Email is required';
if (!emailRegex.hasMatch(email)) return 'Enter a valid email address';
return null;
}
String? validatePassword(String? value) {
final password = value ?? '';
if (password.isEmpty) return 'Password is required';
if (!passwordRegex.hasMatch(password)) {
return 'Use 8+ chars with uppercase, lowercase, and a number';
}
return null;
}
String? validatePhone(String? value) {
final phone = value?.replaceAll(' ', '') ?? '';
if (phone.isEmpty) return 'Phone number is required';
if (!phoneRegex.hasMatch(phone)) {
return 'Use international format, e.g. +2348012345678';
}
return null;
}
void submit() {
final formState = formKey.currentState;
if (formState == null) return;
if (formState.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Form looks good')),
);
}
}
@override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(labelText: 'Email'),
keyboardType: TextInputType.emailAddress,
validator: validateEmail,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Phone'),
keyboardType: TextInputType.phone,
validator: validatePhone,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
validator: validatePassword,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: submit,
child: const Text('Create account'),
),
],
),
);
}
}
Related Posts
- Null Safety in Dart: How to Avoid the Errors That Break Your Build
- Connecting a Flutter App to a REST API: Common Pitfalls
If form validation is part of a bigger onboarding flow, Beyond Just Digital can help make the mobile experience clear before the API ever receives bad data.
