Declare validation rules as HTML attributes. The framework handles both client-side and server-side validation automatically.
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.
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>
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 | user@example.com |
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 |
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>
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]+)*$">
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.
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.
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.
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>
Validation operates in two layers:
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.
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.fieldname##old.fieldname##has_errors# flag is set to true| 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" |