flutter_form_builder_pro 1.0.1 copy "flutter_form_builder_pro: ^1.0.1" to clipboard
flutter_form_builder_pro: ^1.0.1 copied to clipboard

A schema-driven Flutter form engine with conditional fields, async validation, multi-step wizard support, and JSON/YAML schema loading.

flutter_form_builder_pro

Schema-driven Flutter form engine

Define a schema → get a complete, validated, reactive form.
No boilerplate. No repeated validation logic. No hand-wired state.

pub.dev pub points likes MIT license CI

Why flutter_form_builder_pro? #

Every Flutter app has forms — login, registration, checkout, onboarding, settings. Most solutions make you:

  • Hand-wire every TextEditingController and FocusNode
  • Re-implement validation logic on every screen
  • Rebuild multi-step wizard state from scratch each time
  • Write 80 lines of Column + if statements for conditional fields

flutter_form_builder_pro fixes all of this. You write a schema describing what your form contains. The engine figures out how to build it, validate it, and hand you typed output.

// Before flutter_form_builder_pro — ~80 lines of boilerplate per form
// After flutter_form_builder_pro — this is the entire form:

SmartForm(
  schema: FormSchema(
    fields: [
      FieldSchema.text(id: 'name', label: 'Full name',
          validators: [Validators.required()]),
      FieldSchema.email(id: 'email', label: 'Email',
          validators: [Validators.required(), Validators.email()]),
      FieldSchema.dropdown(id: 'country', label: 'Country',
          options: OptionsSource.static([...]),
          validators: [Validators.required()]),
      // Appears only when country == India — zero extra code
      FieldSchema.text(id: 'state', label: 'State',
          visibleWhen: FieldCondition.equals('country', 'India')),
    ],
  ),
  onSubmit: (values) async => await api.register(values),
)

Features #

Feature Details
Schema-driven Define forms in Dart or load from JSON at runtime
Conditional fields Show, hide, require, or disable fields based on other field values
Async validation Server-side checks (email uniqueness, username availability) with Validators.remote()
Multi-step wizard Built-in step controller, progress indicator, per-step validation, and step skipping
20+ field types text, email, password, phone, number, textarea, dropdown, radio, checkbox, checkbox group, date, slider, heading, divider, and more
Custom field types Register any widget via FieldRegistry
JSON schema loading FormSchema.fromJson() — load schemas from an API or remote config
Programmatic control SmartFormController — pre-fill, reset, and submit a form from outside its widget tree
Autosave / drafts Opt-in draft persistence via AutosaveConfig, with debounce and restore-on-mount
Fully themeable Override decoration, labels, buttons, or the entire step indicator
Reactive state ChangeNotifier-based — no external state management required
Dart 3 + null-safe Sealed classes, exhaustive switches, strict analysis options

Installation #

# pubspec.yaml
dependencies:
  flutter_form_builder_pro: ^1.0.0
flutter pub get

Quick start #

1. Single-page form #

import 'package:flutter_form_builder_pro/flutter_form_builder_pro.dart';

SmartForm(
  schema: FormSchema(
    title: 'Create account',
    fields: [
      FieldSchema.text(
        id: 'full_name',
        label: 'Full name',
        validators: [Validators.required(), Validators.minLength(2)],
      ),
      FieldSchema.email(
        id: 'email',
        label: 'Email address',
        validators: [Validators.required(), Validators.email()],
      ),
      FieldSchema.password(
        id: 'password',
        label: 'Password',
        validators: [
          Validators.required(),
          Validators.minLength(8),
        ],
      ),
    ],
  ),
  onSubmit: (Map<String, dynamic> values) async {
    await api.register(values);
  },
)

2. Multi-step wizard #

SmartForm(
  schema: FormSchema.wizard(
    title: 'Onboarding',
    steps: [
      FormStep(
        id: 'personal',
        title: 'Personal info',
        fields: [
          FieldSchema.text(id: 'name', label: 'Full name',
              validators: [Validators.required()]),
          FieldSchema.email(id: 'email', label: 'Email',
              validators: [Validators.required(), Validators.email()]),
        ],
      ),
      FormStep(
        id: 'business',
        title: 'Business details',
        // This entire step is skipped for individual accounts
        visibleWhen: FieldCondition.equals('account_type', 'business'),
        fields: [
          FieldSchema.text(id: 'company', label: 'Company name',
              validators: [Validators.required()]),
        ],
      ),
      FormStep(
        id: 'finish',
        title: 'All done',
        fields: [
          FieldSchema.textarea(id: 'bio', label: 'Short bio'),
        ],
      ),
    ],
  ),
  onSubmit: (values) async => await api.onboard(values),
  onStepChange: (from, to) => analytics.track('wizard_step_$to'),
)

3. JSON-driven form (remote config / no-code) #

// Load schema from your API — the form builds itself
final response = await http.get(Uri.parse('/api/forms/registration'));

SmartForm(
  schema: FormSchema.fromJson(jsonDecode(response.body)),
  onSubmit: (values) => api.submit('registration', values),
)

Conditional fields #

Fields can be conditionally visible, required, or disabled based on other field values. Conditions are evaluated reactively — the form updates instantly as the user types.

// Simple equals
FieldSchema.text(
  id: 'state',
  label: 'State',
  visibleWhen: FieldCondition.equals('country', 'India'),
)

// OR — show business name for business or freelancer accounts
FieldSchema.text(
  id: 'company',
  label: 'Company name',
  visibleWhen: FieldCondition.or([
    FieldCondition.equals('type', 'business'),
    FieldCondition.equals('type', 'freelancer'),
  ]),
)

// AND — compound condition
FieldSchema.text(
  id: 'gst_number',
  label: 'GST number',
  visibleWhen: FieldCondition.and([
    FieldCondition.equals('country', 'India'),
    FieldCondition.equals('type', 'business'),
  ]),
)

// Conditional required — field is always shown, but only required sometimes
FieldSchema.text(
  id: 'referral_code',
  label: 'Referral code',
  requiredWhen: FieldCondition.equals('has_referral', true),
)

// Conditional disabled
FieldSchema.text(
  id: 'email',
  label: 'Email',
  disabledWhen: FieldCondition.equals('sso_enabled', true),
)

Available condition operators #

Operator Usage
FieldCondition.equals(id, value) field == value
FieldCondition.notEquals(id, value) field != value
FieldCondition.contains(id, value) List contains / String includes
FieldCondition.notContains(id, value) Inverse of contains
FieldCondition.greaterThan(id, num) Numeric >
FieldCondition.lessThan(id, num) Numeric <
FieldCondition.isEmpty(id) null, '', []
FieldCondition.isNotEmpty(id) Inverse of isEmpty
FieldCondition.and(conditions) All must pass
FieldCondition.or(conditions) At least one must pass
FieldCondition.not(condition) Negates any condition

Validation #

Built-in validators #

validators: [
  Validators.required(),                          // Non-null, non-empty
  Validators.minLength(8),                        // String length >= 8
  Validators.maxLength(120),                      // String length <= 120
  Validators.email(),                             // Valid email format
  Validators.phone(),                             // Valid phone format
  Validators.pattern(RegExp(r'^\d{6}$'),          // RegExp match
      message: 'Must be a 6-digit PIN'),
  Validators.minValue(0),                         // Numeric >= 0
  Validators.maxValue(100),                       // Numeric <= 100
  Validators.matchField('password',               // Must equal another field
      message: 'Passwords do not match'),
]

Async / server-side validation #

Validators.remote((value, formValues) async {
  final taken = await api.checkEmailTaken(value as String);
  return taken ? 'This email is already registered' : null;
})

Async validators run in parallel with all other validators via Future.wait. The form blocks submission until all async validators resolve.

Custom inline validator #

Validators.custom((value, formValues) {
  if (value == formValues['username']) {
    return 'Password cannot be the same as your username';
  }
  return null; // null = valid
})

Field types #

Text variants #

FieldSchema.text(id: 'name', label: 'Full name')
FieldSchema.textarea(id: 'bio', label: 'Bio', maxLines: 5)
FieldSchema.email(id: 'email', label: 'Email')
FieldSchema.password(id: 'password', label: 'Password')   // toggleable visibility
FieldSchema.phone(id: 'phone', label: 'Phone')
FieldSchema.number(id: 'age', label: 'Age', min: 0, max: 120)

Selection #

FieldSchema.dropdown(
  id: 'country',
  label: 'Country',
  options: OptionsSource.static([
    FieldOption(value: 'IN', label: 'India'),
    FieldOption(value: 'US', label: 'United States'),
  ]),
)

FieldSchema.radio(
  id: 'plan',
  label: 'Plan',
  options: OptionsSource.static([
    FieldOption(value: 'free', label: 'Free',
        description: 'Up to 3 projects'),
    FieldOption(value: 'pro', label: 'Pro',
        description: 'Unlimited projects'),
  ]),
)

FieldSchema.checkbox(id: 'agree', label: 'I agree to the Terms')

FieldSchema.checkboxGroup(
  id: 'interests',
  label: 'Interests',
  options: OptionsSource.static([...]),
  maxSelections: 3,
)

Async options (loaded from an API) #

FieldSchema.dropdown(
  id: 'city',
  label: 'City',
  options: OptionsSource.async(() async {
    final cities = await api.getCities();
    return cities.map((c) => FieldOption(value: c.id, label: c.name)).toList();
  }),
)
// Shows a loading indicator while fetching, retry button on error.
// Result is cached — loader only runs once.

Date & range #

FieldSchema.date(
  id: 'dob',
  label: 'Date of birth',
  max: DateTime.now(),
  min: DateTime(1900),
)

FieldSchema.slider(
  id: 'budget',
  label: 'Monthly budget (₹)',
  min: 0,
  max: 100000,
  step: 1000,
  initialValue: 10000,
)

Layout / decorative #

FieldSchema.heading(id: 'h1', label: 'Account details',
    helperText: 'These details appear on your invoices.')

FieldSchema.divider(id: 'div1')

Controlling a form programmatically #

Use SmartFormController whenever you need to read or change a form's values from outside its own widget — pre-filling after an async API call, resetting on a button tap, or triggering submission from a different part of the screen.

class MyFormPage extends StatefulWidget {
  const MyFormPage({super.key});

  @override
  State<MyFormPage> createState() => _MyFormPageState();
}

class _MyFormPageState extends State<MyFormPage> {
  final _controller = SmartFormController();

  @override
  void initState() {
    super.initState();
    _prefillFromApi();
  }

  Future<void> _prefillFromApi() async {
    final profile = await api.getProfile();
    // Safe to call even before SmartForm has mounted — values are queued
    // and applied automatically once the form attaches.
    _controller.setValues({
      'name': profile.name,
      'email': profile.email,
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SmartForm(
      controller: _controller,
      schema: mySchema,
      onSubmit: (values) async => await api.updateProfile(values),
    );
  }
}

SmartFormController API #

Member Description
setValue(id, value) Sets a single field's value
setValues(Map<String, dynamic>) Sets multiple field values at once
getValue(id) Reads a single field's current value
values / visibleValues All values, or only values for currently visible fields
submit() Triggers submission; returns a Future<void> that completes once validation and onSubmit have finished
reset() Resets all fields to their initial values and clears errors
clearErrors() Clears validation errors without changing values
isDirty / isSubmitting / hasErrors Read-only state flags

Calls made before the form mounts (setValue, setValues, submit) are queued and automatically replayed once the form attaches — this makes the "pre-fill from an API response that might resolve before first build" case work without any extra bookkeeping on your end. A single controller can only be attached to one mounted SmartForm at a time, and any use of a disposed controller throws a clear StateError rather than failing silently.


Autosave & draft restore #

SmartForm(
  schema: schema,
  autosave: AutosaveConfig(
    key: 'registration_draft',
    debounce: const Duration(milliseconds: 500),
    onDraftRestored: () {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Draft restored')),
      );
    },
  ),
  onSubmit: (values) async => await api.register(values),
)

The draft is persisted on every debounced change and cleared automatically on a successful submission. To inspect what was restored (rather than just being notified that something was), read it from a SmartFormController attached to the same form.


Theming #

SmartForm uses your ambient ThemeData by default. Override anything via FormTheme:

SmartForm(
  schema: schema,
  theme: FormTheme(
    // Custom input decoration applied to all fields
    inputDecoration: InputDecoration(
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(16),
      ),
      filled: true,
      fillColor: Colors.grey.shade50,
    ),

    // Per-field decoration override
    inputDecorationBuilder: (base, field) => base.copyWith(
      prefixIcon: field.type == FieldType.email
          ? const Icon(Icons.email_outlined)
          : null,
    ),

    // Button builders
    submitButtonBuilder: (context, isLoading, onPressed) => ElevatedButton(
      onPressed: onPressed,
      style: ElevatedButton.styleFrom(minimumSize: const Size.fromHeight(52)),
      child: isLoading
          ? const CircularProgressIndicator.adaptive()
          : const Text('Create account'),
    ),

    // Wizard step indicator
    activeStepColor: Colors.deepPurple,
    showLinearProgress: true,
  ),
)

Custom field types #

Register any widget as a field type via FieldRegistry:

SmartForm(
  schema: FormSchema(
    fields: [
      FieldSchema(
        id: 'brand_color',
        type: FieldType.text, // base type ignored — custom builder takes over
        label: 'Brand color',
        extra: {'defaultColor': '#5C6BC0'},
      ),
    ],
  ),
  registry: buildDefaultRegistry(theme: const FormTheme())
    ..register(
      FieldType.custom('color_picker'),
      (context, field, state) => ColorPickerField(
        label: field.label,
        value: state.getValue(field.id) as Color?,
        onChanged: (color) => state.setValue(field.id, color),
        defaultColor: Color(
          int.parse((field.extra['defaultColor'] as String)
              .replaceAll('#', '0xFF')),
        ),
      ),
    ),
  onSubmit: (values) async => print(values),
)

JSON schema reference #

All field types and conditions can be defined in JSON. Useful for:

  • Remote form configuration (A/B test form variants without a release)
  • No-code / low-code form builders
  • Server-driven UI patterns
{
  "title": "Contact us",
  "fields": [
    {
      "id": "name",
      "type": "text",
      "label": "Your name",
      "validators": [
        { "type": "required" },
        { "type": "min_length", "value": 2 }
      ]
    },
    {
      "id": "email",
      "type": "email",
      "label": "Email address",
      "validators": [
        { "type": "required" },
        { "type": "email" }
      ]
    },
    {
      "id": "subject",
      "type": "dropdown",
      "label": "Subject",
      "options": {
        "type": "static",
        "options": [
          { "value": "support", "label": "Technical support" },
          { "value": "billing", "label": "Billing" },
          { "value": "other",   "label": "Other" }
        ]
      },
      "validators": [{ "type": "required" }]
    },
    {
      "id": "order_id",
      "type": "text",
      "label": "Order ID",
      "visible_when": {
        "op": "equals",
        "field": "subject",
        "value": "billing"
      }
    },
    {
      "id": "message",
      "type": "textarea",
      "label": "Message",
      "min_lines": 4,
      "max_lines": 8,
      "validators": [
        { "type": "required" },
        { "type": "min_length", "value": 20 }
      ]
    }
  ]
}

JSON condition operators #

{ "op": "equals",       "field": "country",  "value": "India" }
{ "op": "not_equals",   "field": "type",     "value": "free" }
{ "op": "contains",     "field": "roles",    "value": "admin" }
{ "op": "greater_than", "field": "age",      "value": 18 }
{ "op": "less_than",    "field": "price",    "value": 1000 }
{ "op": "is_empty",     "field": "coupon" }
{ "op": "is_not_empty", "field": "company" }
{ "op": "and", "conditions": [ {...}, {...} ] }
{ "op": "or",  "conditions": [ {...}, {...} ] }
{ "op": "not", "condition":  { "op": "equals", "field": "x", "value": "y" } }

JSON validator types #

{ "type": "required",    "message": "Custom message" }
{ "type": "min_length",  "value": 8 }
{ "type": "max_length",  "value": 200 }
{ "type": "email" }
{ "type": "phone" }
{ "type": "pattern",     "pattern": "^\\d{6}$", "message": "Must be 6 digits" }
{ "type": "min_value",   "value": 0 }
{ "type": "max_value",   "value": 100 }
{ "type": "match_field", "field": "password",  "message": "Passwords must match" }

Architecture overview #

flutter_form_builder_pro/
├── schema/          Pure Dart — no Flutter dependency — 100% unit testable
│   ├── FieldType        Sealed enum of all field types
│   ├── FieldCondition   Sealed class, 9 composable operators
│   ├── FieldValidator   Sync + async, 10 built-ins + custom
│   ├── OptionsSource    Static list or async loader with caching
│   ├── FieldSchema      Full field descriptor, named constructors, JSON
│   └── FormSchema       Root schema — flat or wizard, JSON roundtrip
│
├── engine/          Flutter (ChangeNotifier) — reactive form runtime
│   ├── FormStateNotifier    Values, errors, dirty/touched, parallel validation
│   ├── StepController       Wizard navigation, step skipping, progress
│   ├── FieldRegistry        Type → widget builder map, custom type support
│   └── SmartFormController  External handle — read, write, reset, submit
│
├── autosave/
│   ├── AutosaveConfig     Storage key, debounce, restore callback
│   └── AutosaveEngine     Persistence, restore-on-mount, clear-on-submit
│
├── theme/
│   └── FormTheme          Full visual configuration, copyWith
│
└── widgets/         Flutter widgets — the visible layer
    ├── SmartForm          Main entry point — flat + wizard modes
    ├── BaseFieldWrapper   Shared label/error/helper layout
    ├── default_registry   Wires all 14 built-in type mappings
    ├── fields/            One file per field widget
    └── wizard/            StepIndicator + WizardNavigationBar

Comparison #

flutter_form_builder_pro flutter_form_builder reactive_forms
Schema-driven
JSON schema
Conditional fields Manual
Async validation
Multi-step wizard
Step skip via condition
Custom field types
Dart 3 sealed classes
External state management Optional Optional Required

Contributing #

Contributions are very welcome! Here is how to get started:

  1. Fork the repository on GitHub
  2. Clone your fork: git clone https://github.com/Sarvesh223/flutter_form_builder_pro
  3. Create a feature branch: git checkout -b feat/my-feature
  4. Write tests for your change — all PRs require test coverage
  5. Run tests: flutter test
  6. Run the analyzer: flutter analyze
  7. Open a pull request with a clear description of what and why

Development setup #

git clone https://github.com/Sarvesh223/flutter_form_builder_pro
cd flutter_form_builder_pro
flutter pub get
flutter test               # run all tests
cd example && flutter run  # run the example app

Reporting bugs #

Please open an issue and include:

  • Flutter version (flutter --version)
  • A minimal code snippet that reproduces the problem
  • What you expected vs. what happened

Requesting features #

Feature requests are tracked as GitHub issues. Please check existing issues before opening a new one.


Sponsorship #

flutter_form_builder_pro is free and open source. If it saves you time on your projects, consider sponsoring continued development:

  • Sponsor

Sponsors receive:

  • Priority responses on issues and PRs
  • Acknowledgement in the changelog and README
  • Early access to upcoming features

License #

MIT © 2026 flutter_form_builder_pro contributors

1
likes
160
points
47
downloads

Documentation

API reference

Publisher

verified publisherv-nerdy.cloud

Weekly Downloads

A schema-driven Flutter form engine with conditional fields, async validation, multi-step wizard support, and JSON/YAML schema loading.

Homepage
Repository (GitHub)
View/report issues
Contributing

License

MIT (license)

Dependencies

cupertino_icons, file_picker, flutter, flutter_quill, hive, hive_flutter, http, yaml

More

Packages that depend on flutter_form_builder_pro