Skip to content

Validation for Dart that returns a result instead of throwing.

Build a validator by chaining l.string().email().required(). Validate any input and get a sealed result back, typed, with every issue carrying a code, a path and a message. Point luthor_generator at a freezed, json_serializable or dart_mappable model and it writes the schema for you.

dart pub add luthor
Release
1.0.0
Requires
Dart 3.11 or Flutter 3.41
Dependencies
none
user.dart
final user = l.schema({
'email': l.string().email().required(),
'age': l.int().min(18),
'profile': l.schema({
'displayName': l.string().min(2).required(),
}).required(),
});
user.validate(input)+2-1
  1. valid: {'email': '[email protected]', 'profile': {'displayName': 'Ada'}}
  2. valid: {'email': '[email protected]', 'age': 42, 'profile': {'displayName': 'Ada'}}
  3. invalid: {'email': 'bad', 'age': 12, 'profile': {'displayName': 'x'}}email: email must be a valid email addressage: age must be greater than or equal to 18profile.displayName: displayName must be at least 2 characters long

From install to a generated schema in four steps

Section titled “From install to a generated schema in four steps”
  1. Install luthor, and luthor_generator if you want generated schemas. Luthor needs Dart 3.11 or Flutter 3.41 and has no dependencies of its own.

  2. Write your first validator. Chain a type and some modifiers, call validate, and read the typed result.

  3. Write your first schema. Name the fields of a map, validate it, and look up errors by path.

  4. Generate a schema from a model. Annotate a class with @luthor, run build_runner, and use the generated keys instead of strings.

Coming from 0.x? Upgrading to 1.0 lists every renamed call, and What’s new in 1.0 has the additions.

validate returns a ValidationResult<T>. It is sealed, so a switch over it is exhaustive, and T follows the validator: l.string().required() succeeds with a String, l.list(l.int().required()) with a List<int>.

final result = l.string().email().required().validate(input);
switch (result) {
case ValidationSuccess(:final data):
sendTo(data); // data is a String
case ValidationFailure(:final issues):
print(issues.first.code); // IssueCode.invalidEmail
}
l.string().email().required().validate(input)+1-2
  1. valid: '[email protected]'
  2. invalid: 'bad'value must be a valid email address
  3. invalid: nullvalue is required

Values are optional until you say otherwise. Drop .required() from the validator above and null passes. Optional by default explains why.

A schema is a map of field names to validators. You can write it by hand, or annotate a model and let luthor_generator write it, along with typed keys for the fields and for the error paths. The two tabs below stay in sync across the whole site, so pick the one you use.

import 'package:luthor/luthor.dart';
final userSchema = l.schema({
'name': l.string().min(2).required(),
'email': l.string().email().required(),
'age': l.int().min(18),
});
final result = userSchema.validateSchema(json, fromJson: User.fromJson);

Each entry links to a reference page with the signature, the parameters, the issue code and default message, and the matching generator annotation. Every example output on this site comes from running the code in this repository.

Types

Modifiers

String modifiers

Number modifiers

Results and settings