Validation
Validate a request inline with Laravel's rule strings, or with typed rule
objects from Rules:
final data = await request.validate({
'email': 'required|email|unique:users',
'password': 'required|min:8|confirmed',
'role': [Rules.required(), Rules.inList(['admin', 'editor'])],
'tags.*': 'string|max:20',
});
data.string('email'); // typed access to validated input only
Failures throw ValidationException, rendered as Laravel's 422 body:
{
"message": "The email field is required. (and 1 more error)",
"errors": {
"email": ["The email field is required."],
"password": ["The password field must be at least 8 characters."]
}
}
The default English messages are identical to Laravel 12's
lang/en/validation.php (checked by the conformance suite against the
upstream file). Indonesian ships as the second locale: set
request.attributes['locale'] = 'id' (for example in a middleware).
Semantics locked to Laravel#
nullablestops the remaining rules when the value isnull.- A field that is absent and not
requiredskips non-implicit rules;sometimesvalidates only when the key is present. confirmedlooks for{field}_confirmation.bailstops at the first failure of a field.- Nested data uses dot paths and
*wildcards.
Form requests#
final class StorePostRequest extends FormRequest {
const StorePostRequest();
@override
Map<String, Object> rules(Request request) => {
'title': 'required|string|max:120',
'body': 'nullable|string',
'published': [Rules.sometimes(), Rules.boolean()],
};
}
await FormRequest.run(const StorePostRequest(), request) runs
authorize() (403 when it returns false), then the rules, and returns the
validated data. dart run bin/dartisan.dart make:request StorePostRequest
creates one.
Declare it on the route with .validates(const StorePostRequest()) instead
and read request.validated() in the handler; see
Routing.
File uploads#
multipart/form-data bodies are parsed as they arrive: fields become input
like a form, and each file is streamed to a temporary file (deleted after the
response) and appears as an UploadedFile. Names such as photos[] and
user[avatar] nest as in Laravel.
router.post('/avatar', (request) async {
final data = request.validated();
final path = await data.file('avatar').store('avatars', disk: 'public');
return {'url': Storage.disk('public').url(path)};
}).rules(const {'avatar': 'required|image|max:2048'});
request.file('avatar'),request.hasFile('photos')andrequest.uploadedFiles()read the uploads;all()includes them.UploadedFile:clientName,clientExtension,clientMimeType(sent by the client, not trusted),mimeTypeandextension(detected from the content),size,bytes(),text(),openRead(),hashName(),store(directory, disk:)andstoreAs(directory, name, disk:).- Rules, as in Laravel 12:
file,image(jpg, jpeg, png, gif, bmp, webp;image:allow_svgadds SVG),mimes:pdf,docx(the extension of the detected type),mimetypes:image/*,extensions:csv,txt(the client's file name), andmin,max,size,betweenin kilobytes. PHP files are refused unlessphpis listed. - Types are detected from the content (PDF, images, Office and OpenDocument
files, archives, text), never from the file name: a text file named
photo.pngis not an image. - The body size limit of
BodyLimitsMiddleware(50 MiB by default) covers uploads; a request also accepts at most 1 000 fields and 20 files.
In tests, send files with client.post(uri, files: {...}) (or
Request.fake(files: ...)) and build them with UploadedFile.fake:
await client.post('/api/avatar', files: {
'avatar': UploadedFile.fake.image('me.png', kilobytes: 120),
'photos': [UploadedFile.fake.image('1.png'), UploadedFile.fake.image('2.gif')],
});