Topzee logo
Open menu
Mobile Development

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.

Topzee4 min read
Abstract Flutter validation cover with form fields, check marks, and Topzee brand colors.

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:

  • null when 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'),
          ),
        ],
      ),
    );
  }
}

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.