0
# Validation
1
2
Robust validation framework with built-in validators for common use cases and support for custom validation logic. The validation system provides data requirements, length constraints, format validation, cross-field comparisons, and extensible custom validation patterns.
3
4
## Capabilities
5
6
### Validation Exceptions
7
8
Core exception classes for validation error handling.
9
10
```python { .api }
11
class ValidationError(ValueError):
12
"""
13
Exception raised when validation fails.
14
15
Parameters:
16
- message: Error message string
17
"""
18
def __init__(self, message=""): ...
19
20
class StopValidation(Exception):
21
"""
22
Exception that stops the validation chain.
23
Used by validators like Optional to halt further validation.
24
25
Parameters:
26
- message: Optional error message
27
"""
28
def __init__(self, message=""): ...
29
```
30
31
### Data Requirement Validators
32
33
Validators that check for presence and validity of input data.
34
35
```python { .api }
36
class DataRequired:
37
"""
38
Validates that field contains truthy data.
39
Fails on empty strings, empty lists, None, etc.
40
41
Parameters:
42
- message: Custom error message
43
"""
44
def __init__(self, message=None): ...
45
def __call__(self, form, field): ...
46
47
class InputRequired:
48
"""
49
Validates that form input was provided (even if empty).
50
Unlike DataRequired, accepts empty strings as valid input.
51
Sets 'required' flag on field.
52
53
Parameters:
54
- message: Custom error message
55
"""
56
def __init__(self, message=None): ...
57
def __call__(self, form, field): ...
58
59
class Optional:
60
"""
61
Allows empty input and stops validation chain.
62
If field is empty, no further validators are called.
63
Sets 'optional' flag on field.
64
65
Parameters:
66
- strip_whitespace: Whether to strip whitespace before checking (default: True)
67
"""
68
def __init__(self, strip_whitespace=True): ...
69
def __call__(self, form, field): ...
70
```
71
72
### Length and Range Validators
73
74
Validators for constraining input length and numeric ranges.
75
76
```python { .api }
77
class Length:
78
"""
79
Validates string length within specified bounds.
80
Sets 'minlength' and 'maxlength' flags on field.
81
82
Parameters:
83
- min: Minimum length (-1 for no minimum, default: -1)
84
- max: Maximum length (-1 for no maximum, default: -1)
85
- message: Custom error message
86
"""
87
def __init__(self, min=-1, max=-1, message=None): ...
88
def __call__(self, form, field): ...
89
90
class NumberRange:
91
"""
92
Validates numeric value within specified range.
93
Sets 'min' and 'max' flags on field.
94
95
Parameters:
96
- min: Minimum value (None for no minimum)
97
- max: Maximum value (None for no maximum)
98
- message: Custom error message
99
"""
100
def __init__(self, min=None, max=None, message=None): ...
101
def __call__(self, form, field): ...
102
```
103
104
### Comparison and Equality Validators
105
106
Validators for comparing field values.
107
108
```python { .api }
109
class EqualTo:
110
"""
111
Compares field value to another field in the same form.
112
Commonly used for password confirmation fields.
113
114
Parameters:
115
- fieldname: Name of field to compare against
116
- message: Custom error message
117
"""
118
def __init__(self, fieldname, message=None): ...
119
def __call__(self, form, field): ...
120
```
121
122
### Pattern and Format Validators
123
124
Validators for specific data formats and patterns.
125
126
```python { .api }
127
class Regexp:
128
"""
129
Validates field data against regular expression pattern.
130
131
Parameters:
132
- regex: Regular expression pattern (string or compiled regex)
133
- flags: Regex flags (default: 0)
134
- message: Custom error message
135
"""
136
def __init__(self, regex, flags=0, message=None): ...
137
def __call__(self, form, field): ...
138
139
class Email:
140
"""
141
Validates email address format.
142
Requires 'email_validator' package for full validation.
143
144
Parameters:
145
- message: Custom error message
146
- granular_message: Whether to show specific validation errors
147
- check_deliverability: Whether to check if domain accepts email
148
- allow_smtputf8: Whether to allow international domains
149
- allow_empty_local: Whether to allow empty local part
150
"""
151
def __init__(self, message=None, granular_message=False,
152
check_deliverability=True, allow_smtputf8=True,
153
allow_empty_local=False): ...
154
def __call__(self, form, field): ...
155
156
class URL:
157
"""
158
Validates URL format.
159
160
Parameters:
161
- require_tld: Whether to require top-level domain (default: True)
162
- message: Custom error message
163
"""
164
def __init__(self, require_tld=True, message=None): ...
165
def __call__(self, form, field): ...
166
167
class IPAddress:
168
"""
169
Validates IP address format.
170
171
Parameters:
172
- ipv4: Whether to allow IPv4 addresses (default: True)
173
- ipv6: Whether to allow IPv6 addresses (default: False)
174
- message: Custom error message
175
"""
176
def __init__(self, ipv4=True, ipv6=False, message=None): ...
177
def __call__(self, form, field): ...
178
179
class MacAddress:
180
"""
181
Validates MAC address format.
182
183
Parameters:
184
- message: Custom error message
185
"""
186
def __init__(self, message=None): ...
187
def __call__(self, form, field): ...
188
189
class UUID:
190
"""
191
Validates UUID format.
192
193
Parameters:
194
- message: Custom error message
195
"""
196
def __init__(self, message=None): ...
197
def __call__(self, form, field): ...
198
```
199
200
### Choice Validators
201
202
Validators for constraining values to specific choices.
203
204
```python { .api }
205
class AnyOf:
206
"""
207
Validates that field value is in allowed list.
208
209
Parameters:
210
- values: Iterable of allowed values
211
- message: Custom error message
212
- values_formatter: Function to format values in error message
213
"""
214
def __init__(self, values, message=None, values_formatter=None): ...
215
def __call__(self, form, field): ...
216
217
class NoneOf:
218
"""
219
Validates that field value is NOT in disallowed list.
220
221
Parameters:
222
- values: Iterable of disallowed values
223
- message: Custom error message
224
- values_formatter: Function to format values in error message
225
"""
226
def __init__(self, values, message=None, values_formatter=None): ...
227
def __call__(self, form, field): ...
228
```
229
230
### State Validators
231
232
Validators that set field state or behavior.
233
234
```python { .api }
235
class ReadOnly:
236
"""
237
Marks field as read-only for display purposes.
238
Sets 'readonly' flag on field.
239
240
Parameters:
241
- message: Custom error message if field is modified
242
"""
243
def __init__(self, message=None): ...
244
def __call__(self, form, field): ...
245
246
class Disabled:
247
"""
248
Marks field as disabled.
249
Sets 'disabled' flag on field.
250
251
Parameters:
252
- message: Custom error message if field is modified
253
"""
254
def __init__(self, message=None): ...
255
def __call__(self, form, field): ...
256
```
257
258
### Validator Function Aliases
259
260
WTForms provides lowercase function aliases for all validator classes for convenience.
261
262
```python { .api }
263
# Lowercase aliases for validator classes
264
data_required = DataRequired
265
input_required = InputRequired
266
optional = Optional
267
length = Length
268
number_range = NumberRange
269
equal_to = EqualTo
270
regexp = Regexp
271
email = Email
272
url = URL
273
ip_address = IPAddress
274
mac_address = MacAddress
275
any_of = AnyOf
276
none_of = NoneOf
277
readonly = ReadOnly
278
disabled = Disabled
279
```
280
281
## Validation Usage Examples
282
283
### Basic Validation
284
285
```python
286
from wtforms import Form, StringField, IntegerField, validators
287
288
class UserForm(Form):
289
username = StringField('Username', [
290
validators.DataRequired(message="Username is required"),
291
validators.Length(min=4, max=25, message="Username must be 4-25 characters")
292
])
293
294
email = StringField('Email', [
295
validators.DataRequired(),
296
validators.Email(message="Invalid email address")
297
])
298
299
age = IntegerField('Age', [
300
validators.NumberRange(min=13, max=120, message="Age must be 13-120")
301
])
302
303
bio = StringField('Bio', [
304
validators.Optional(), # Field is optional
305
validators.Length(max=500) # But if provided, max 500 chars
306
])
307
308
# Validation
309
form = UserForm(formdata=request.form)
310
if form.validate():
311
# All fields passed validation
312
process_user_data(form.data)
313
else:
314
# Display errors
315
for field, errors in form.errors.items():
316
for error in errors:
317
print(f"{field}: {error}")
318
```
319
320
### Password Confirmation
321
322
```python
323
class RegistrationForm(Form):
324
password = StringField('Password', [
325
validators.DataRequired(),
326
validators.Length(min=8, message="Password must be at least 8 characters")
327
])
328
329
confirm_password = StringField('Confirm Password', [
330
validators.DataRequired(),
331
validators.EqualTo('password', message='Passwords must match')
332
])
333
```
334
335
### Email Validation with Options
336
337
```python
338
class ContactForm(Form):
339
# Basic email validation
340
email = StringField('Email', [validators.Email()])
341
342
# Email with custom message and options
343
business_email = StringField('Business Email', [
344
validators.Email(
345
message="Please enter a valid business email",
346
check_deliverability=True, # Verify domain accepts email
347
granular_message=True # Show specific validation errors
348
)
349
])
350
```
351
352
### Pattern Validation
353
354
```python
355
class ProductForm(Form):
356
# SKU must be format: ABC-1234
357
sku = StringField('SKU', [
358
validators.Regexp(
359
r'^[A-Z]{3}-\d{4}$',
360
message="SKU must be format ABC-1234"
361
)
362
])
363
364
# Phone number validation
365
phone = StringField('Phone', [
366
validators.Regexp(
367
r'^\+?1?-?\(?(\d{3})\)?-?(\d{3})-?(\d{4})$',
368
message="Invalid phone number format"
369
)
370
])
371
```
372
373
### Choice Validation
374
375
```python
376
class SurveyForm(Form):
377
rating = IntegerField('Rating', [
378
validators.AnyOf([1, 2, 3, 4, 5], message="Rating must be 1-5")
379
])
380
381
language = StringField('Language', [
382
validators.AnyOf(['en', 'es', 'fr'], message="Unsupported language")
383
])
384
385
# Forbidden words
386
comment = StringField('Comment', [
387
validators.NoneOf(['spam', 'test'], message="Comment contains forbidden words")
388
])
389
```
390
391
### Network Address Validation
392
393
```python
394
class ServerForm(Form):
395
# IPv4 only
396
ipv4_address = StringField('IPv4 Address', [
397
validators.IPAddress(ipv4=True, ipv6=False)
398
])
399
400
# IPv6 only
401
ipv6_address = StringField('IPv6 Address', [
402
validators.IPAddress(ipv4=False, ipv6=True)
403
])
404
405
# Either IPv4 or IPv6
406
ip_address = StringField('IP Address', [
407
validators.IPAddress(ipv4=True, ipv6=True)
408
])
409
410
mac_address = StringField('MAC Address', [
411
validators.MacAddress()
412
])
413
414
server_id = StringField('Server ID', [
415
validators.UUID(message="Must be valid UUID")
416
])
417
```
418
419
### Custom Validators
420
421
```python
422
def validate_username_available(form, field):
423
"""Custom validator function."""
424
if User.query.filter_by(username=field.data).first():
425
raise ValidationError('Username already taken.')
426
427
class UsernameAvailable:
428
"""Custom validator class."""
429
def __init__(self, message=None):
430
self.message = message or 'Username already taken.'
431
432
def __call__(self, form, field):
433
if User.query.filter_by(username=field.data).first():
434
raise ValidationError(self.message)
435
436
class RegistrationForm(Form):
437
username = StringField('Username', [
438
validators.DataRequired(),
439
validate_username_available, # Function validator
440
UsernameAvailable() # Class validator
441
])
442
```
443
444
### Conditional Validation
445
446
```python
447
class ShippingForm(Form):
448
same_as_billing = BooleanField('Same as billing address')
449
shipping_address = StringField('Shipping Address')
450
451
def validate_shipping_address(self, field):
452
"""Custom validation method - only required if different from billing."""
453
if not self.same_as_billing.data and not field.data:
454
raise ValidationError('Shipping address is required.')
455
```
456
457
### Validation with Filters
458
459
```python
460
from wtforms.filters import strip_filter
461
462
def uppercase_filter(data):
463
"""Custom filter to uppercase data."""
464
return data.upper() if data else data
465
466
class ProductForm(Form):
467
name = StringField('Product Name',
468
filters=[strip_filter], # Remove leading/trailing whitespace
469
validators=[validators.DataRequired()]
470
)
471
472
code = StringField('Product Code',
473
filters=[strip_filter, uppercase_filter], # Strip and uppercase
474
validators=[validators.DataRequired()]
475
)
476
477
# Usage
478
form = ProductForm(formdata={'name': ' Widget ', 'code': ' abc123 '})
479
form.process()
480
print(form.name.data) # "Widget" (stripped)
481
print(form.code.data) # "ABC123" (stripped and uppercased)
482
```
483
484
### Validation Error Handling
485
486
```python
487
form = MyForm(formdata=request.form)
488
489
try:
490
if form.validate():
491
# Process valid form
492
save_form_data(form.data)
493
else:
494
# Handle validation errors
495
for field_name, errors in form.errors.items():
496
for error in errors:
497
flash(f"Error in {field_name}: {error}", 'error')
498
499
except ValidationError as e:
500
# Handle specific validation exception
501
flash(f"Validation failed: {e}", 'error')
502
503
# Access individual field errors
504
if form.email.errors:
505
email_errors = form.email.errors
506
print(f"Email validation failed: {', '.join(email_errors)}")
507
```
508
509
### Dynamic Validation
510
511
```python
512
class DynamicForm(Form):
513
email = StringField('Email')
514
515
def __init__(self, require_email=False, *args, **kwargs):
516
super().__init__(*args, **kwargs)
517
518
# Add validators dynamically
519
if require_email:
520
self.email.validators = [
521
validators.DataRequired(),
522
validators.Email()
523
]
524
else:
525
self.email.validators = [validators.Optional()]
526
527
# Usage
528
form = DynamicForm(require_email=True, formdata=request.form)
529
```
530
531
### Validation with Extra Validators
532
533
```python
534
def validate_during_business_hours(form, field):
535
"""Validator that only applies during business hours."""
536
if datetime.now().hour < 9 or datetime.now().hour > 17:
537
raise ValidationError('This form can only be submitted during business hours.')
538
539
class ContactForm(Form):
540
message = StringField('Message', [validators.DataRequired()])
541
542
# Add extra validation at runtime
543
form = ContactForm(formdata=request.form)
544
extra_validators = {
545
'message': [validate_during_business_hours]
546
}
547
548
if form.validate(extra_validators=extra_validators):
549
# Process form
550
pass
551
```