Validation
Declare validation rules as HTML attributes. The framework handles both client-side and server-side validation automatically.
Enabling Validation
Add the w-validate attribute to any form to activate validation:
<form w-action="create" w-store="users" w-validate>
<input name="email" type="email" w-required w-type="email">
<button type="submit">Sign Up</button>
</form>
When the engine renders this form, it scans each input for w-* validation attributes, serializes the rules as a JWT, and injects a hidden <input name="w-rules"> field. On submission, the server decodes and validates against those signed rules.
Required Fields
The w-required attribute marks a field as mandatory. The engine also injects the standard HTML5 required attribute for browser-native validation.
<input name="username" w-required>
<input name="email" w-required w-type="email">
<textarea name="bio" w-required></textarea>
Type Checks
Use w-type to validate the format of a field's value:
<input name="email" w-type="email">
<input name="website" w-type="url">
<input name="age" w-type="number">
<input name="phone" w-type="phone">
<input name="birthday" w-type="date">
<input name="start_time" w-type="time">
| Type | Validates | Example Match |
|---|---|---|
email | Standard email format | [email protected] |
url | Valid URL with protocol | https://example.com |
number | Numeric value | 42, 3.14 |
phone | Phone number (digits, spaces, dashes, parens) | +1 (555) 123-4567 |
date | ISO date format | 2025-03-15 |
time | 24-hour time format | 14:30, 09:15:00 |
Length Limits
Enforce minimum and maximum character length with w-min and w-max. The engine also injects minlength and maxlength HTML5 attributes.
<input name="username" w-required w-min="3" w-max="20">
<textarea name="bio" w-max="500"></textarea>
Pattern Matching
Use w-pattern to validate against a regular expression:
<!-- US zip code -->
<input name="zip" w-pattern="^\d{5}$">
<!-- Two or three uppercase letters -->
<input name="code" w-pattern="[A-Z]{2,3}">
<!-- Slug format -->
<input name="slug" w-pattern="^[a-z0-9]+(-[a-z0-9]+)*$">
Field Matching
Use w-match to require that one field's value equals another field's value. Common for password confirmation:
<input name="password" type="password" w-required w-min="8">
<input name="confirm" type="password" w-required w-match="password">
The w-match value is the name of the field to match against.
Uniqueness
Check that a value does not already exist in a collection with w-unique:
<input name="email" w-required w-type="email" w-unique="users.email">
<input name="slug" w-required w-unique="posts.slug">
The format is collection.field. On server-side validation, the framework queries the database to ensure no existing record has the same value.
Custom Error Messages
Override the default error message for any field with w-error:
<input name="email" w-required w-type="email"
w-error="Please enter a valid email address">
<input name="zip" w-pattern="^\d{5}$"
w-error="Invalid zip code">
The custom message replaces all validation messages for that field. If multiple rules fail, this single message is shown.
Error Display
When validation fails, the framework redirects back to the form with errors and previously submitted values available as template variables.
| Variable | Description |
|---|---|
#errors.fieldname# | Error message for a specific field |
#has_errors# | true if any validation errors exist |
#old.fieldname# | Previously submitted value (form repopulation) |
<form w-action="create" w-store="users" w-validate w-redirect="/welcome">
<div class="form-group">
<label>Username</label>
<input name="username" value="#old.username#"
w-required w-min="3" w-max="20">
<if errors.username>
<span class="text-danger text-sm">#errors.username#</span>
</if>
</div>
<div class="form-group">
<label>Email</label>
<input name="email" value="#old.email#"
w-required w-type="email" w-unique="users.email"
w-error="This email is already taken or invalid">
<if errors.email>
<span class="text-danger text-sm">#errors.email#</span>
</if>
</div>
<div class="form-group">
<label>Password</label>
<input name="password" type="password" w-required w-min="8">
<if errors.password>
<span class="text-danger text-sm">#errors.password#</span>
</if>
</div>
<div class="form-group">
<label>Confirm Password</label>
<input name="confirm" type="password" w-required w-match="password">
<if errors.confirm>
<span class="text-danger text-sm">#errors.confirm#</span>
</if>
</div>
<button type="submit" class="btn btn-primary">Create Account</button>
<button type="reset" class="btn btn-outline">Cancel</button>
</form>
How It Works
Validation operates in two layers:
1. Client-side (on blur + submit)
The what.js client script decodes the JWT payload from the hidden w-rules field (base64, no secret needed for reading). On each blur event and on form submit, it checks the rules and displays error messages inline. This gives instant feedback without a server round-trip.
2. Server-side (before action)
On form submission, the action handler decodes the JWT using the server's secret to verify the rules have not been tampered with. It then validates all submitted fields against the signed rules. If validation fails:
- Errors are stored in the flash session as
#errors.fieldname# - Submitted values are preserved as
#old.fieldname# - The
#has_errors#flag is set totrue - The user is redirected back to the form
Attribute Reference
| Attribute | Purpose | Example |
|---|---|---|
w-validate | Enable validation on a form | <form w-validate> |
w-required | Field must not be empty | <input w-required> |
w-type | Format validation | w-type="email" |
w-min | Minimum character length | w-min="3" |
w-max | Maximum character length | w-max="100" |
w-pattern | Regex pattern match | w-pattern="^\d{5}$" |
w-match | Must equal another field | w-match="password" |
w-unique | Uniqueness check in database | w-unique="users.email" |
w-error | Custom error message | w-error="Invalid input" |