Validation
Gale integrates seamlessly with Laravel's validation system. Validation errors
automatically appear next to form fields using the x-message directive,
with no JavaScript code required.
Basic Validation
Use Laravel's standard validation in your controller. Gale automatically sends validation errors to the frontend when validation fails.
public function register(Request $request)
{
// Standard Laravel validation
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
]);
// Process on success...
User::create($validated);
return gale()->navigate('/dashboard');
}
public function register(Request $request)
{
// Standard Laravel validation
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
]);
// Process on success...
User::create($validated);
return gale()->navigate('/dashboard');
}
The form displays errors automatically:
<form x-data="{ name: '', email: '', password: '', password_confirmation: '' }"
@submit.prevent="$postx('/register')">
<div>
<input type="text" x-model="name" placeholder="Name">
<span x-message="name" class="text-red-500"></span>
</div>
<div>
<input type="email" x-model="email" placeholder="Email">
<span x-message="email" class="text-red-500"></span>
</div>
<div>
<input type="password" x-model="password" placeholder="Password">
<span x-message="password" class="text-red-500"></span>
</div>
<div>
<input type="password" x-model="password_confirmation" placeholder="Confirm Password">
</div>
<button type="submit">Register</button>
</form>
<form x-data="{ name: '', email: '', password: '', password_confirmation: '' }"
@submit.prevent="$postx('/register')">
<div>
<input type="text" x-model="name" placeholder="Name">
<span x-message="name" class="text-red-500"></span>
</div>
<div>
<input type="email" x-model="email" placeholder="Email">
<span x-message="email" class="text-red-500"></span>
</div>
<div>
<input type="password" x-model="password" placeholder="Password">
<span x-message="password" class="text-red-500"></span>
</div>
<div>
<input type="password" x-model="password_confirmation" placeholder="Confirm Password">
</div>
<button type="submit">Register</button>
</form>
Form Request Validation
For complex validation, use Laravel's Form Request classes. Gale handles them identically:
// app/Http/Requests/RegisterRequest.php
class RegisterRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => ['required', 'confirmed', Password::defaults()],
];
}
public function messages(): array
{
return [
'email.unique' => 'This email is already registered.',
];
}
}
// app/Http/Requests/RegisterRequest.php
class RegisterRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => ['required', 'confirmed', Password::defaults()],
];
}
public function messages(): array
{
return [
'email.unique' => 'This email is already registered.',
];
}
}
// Controller
public function register(RegisterRequest $request)
{
// Validation already passed
User::create($request->validated());
return gale()->navigate('/dashboard');
}
// Controller
public function register(RegisterRequest $request)
{
// Validation already passed
User::create($request->validated());
return gale()->navigate('/dashboard');
}
Nested Field Validation
Laravel's dot notation for nested fields works seamlessly with x-message:
<form x-data="{
user: { name: '', email: '' },
address: { street: '', city: '', zip: '' }
}" @submit.prevent="$postx('/save')">
<input x-model="user.name" placeholder="Name">
<span x-message="user.name" class="error"></span>
<input x-model="user.email" placeholder="Email">
<span x-message="user.email" class="error"></span>
<input x-model="address.street" placeholder="Street">
<span x-message="address.street" class="error"></span>
<button type="submit">Save</button>
</form>
<form x-data="{
user: { name: '', email: '' },
address: { street: '', city: '', zip: '' }
}" @submit.prevent="$postx('/save')">
<input x-model="user.name" placeholder="Name">
<span x-message="user.name" class="error"></span>
<input x-model="user.email" placeholder="Email">
<span x-message="user.email" class="error"></span>
<input x-model="address.street" placeholder="Street">
<span x-message="address.street" class="error"></span>
<button type="submit">Save</button>
</form>
// Backend validation rules
$request->validate([
'user.name' => 'required|string',
'user.email' => 'required|email',
'address.street' => 'required|string',
'address.city' => 'required|string',
'address.zip' => 'required|string|size:5',
]);
// Backend validation rules
$request->validate([
'user.name' => 'required|string',
'user.email' => 'required|email',
'address.street' => 'required|string',
'address.city' => 'required|string',
'address.zip' => 'required|string|size:5',
]);
Array Validation
For dynamic arrays like repeatable fields, use template literals in x-message:
<form x-data="{ items: [{ name: '', price: '' }] }"
@submit.prevent="$postx('/save-items')">
<template x-for="(item, index) in items" :key="index">
<div class="item-row">
<input x-model="item.name" placeholder="Item name">
<!-- Dynamic field key using template literal -->
<span x-message=`items.${index}.name` class="error"></span>
<input x-model="item.price" placeholder="Price">
<span x-message=`items.${index}.price` class="error"></span>
</div>
</template>
<button type="button" @click="items.push({ name: '', price: '' })">
Add Item
</button>
<button type="submit">Save</button>
</form>
<form x-data="{ items: [{ name: '', price: '' }] }"
@submit.prevent="$postx('/save-items')">
<template x-for="(item, index) in items" :key="index">
<div class="item-row">
<input x-model="item.name" placeholder="Item name">
<!-- Dynamic field key using template literal -->
<span x-message="`items.${index}.name`" class="error"></span>
<input x-model="item.price" placeholder="Price">
<span x-message="`items.${index}.price`" class="error"></span>
</div>
</template>
<button type="button" @click="items.push({ name: '', price: '' })">
Add Item
</button>
<button type="submit">Save</button>
</form>
// Laravel validation for arrays
$request->validate([
'items' => 'required|array|min:1',
'items.*.name' => 'required|string|max:255',
'items.*.price' => 'required|numeric|min:0',
]);
// Errors are returned as: 'items.0.name', 'items.1.price', etc.
// Laravel validation for arrays
$request->validate([
'items' => 'required|array|min:1',
'items.*.name' => 'required|string|max:255',
'items.*.price' => 'required|numeric|min:0',
]);
// Errors are returned as: 'items.0.name', 'items.1.price', etc.
Custom Error Messages
Send custom messages for business logic validation beyond Laravel's built-in rules:
public function applyDiscount(Request $request)
{
$code = $request->input('coupon_code');
$coupon = Coupon::where('code', $code)->first();
if (!$coupon) {
return gale()->messages([
'coupon_code' => 'This coupon code is invalid.',
]);
}
if ($coupon->isExpired()) {
return gale()->messages([
'coupon_code' => 'This coupon has expired.',
]);
}
if ($coupon->usage_limit_reached) {
return gale()->messages([
'coupon_code' => 'This coupon has reached its usage limit.',
]);
}
// Apply discount...
return gale()
->state('discount', $coupon->amount)
->clearMessages();
}
public function applyDiscount(Request $request)
{
$code = $request->input('coupon_code');
$coupon = Coupon::where('code', $code)->first();
if (!$coupon) {
return gale()->messages([
'coupon_code' => 'This coupon code is invalid.',
]);
}
if ($coupon->isExpired()) {
return gale()->messages([
'coupon_code' => 'This coupon has expired.',
]);
}
if ($coupon->usage_limit_reached) {
return gale()->messages([
'coupon_code' => 'This coupon has reached its usage limit.',
]);
}
// Apply discount...
return gale()
->state('discount', $coupon->amount)
->clearMessages();
}
Clearing Messages
Clear validation messages after successful submission or when starting fresh:
// Clear all messages
gale()->clearMessages();
// Clear specific fields
gale()->messages([
'email' => null, // Clears email message
'name' => null, // Clears name message
]);
// Send success and clear errors
gale()
->clearMessages()
->messages(['_success' => 'Saved successfully!']);
// Clear all messages
gale()->clearMessages();
// Clear specific fields
gale()->messages([
'email' => null, // Clears email message
'name' => null, // Clears name message
]);
// Send success and clear errors
gale()
->clearMessages()
->messages(['_success' => 'Saved successfully!']);
Message Types
Use underscore-prefixed keys for special message types:
| Key | Purpose | Example |
|---|---|---|
_success |
Success messages | Form saved! |
_error |
General errors | Something went wrong. |
_warning |
Warning messages | Session expires in 5 minutes. |
_info |
Informational | Changes will take effect after refresh. |
<!-- Display different message types -->
<div x-message="_success" class="bg-green-100 text-green-700 p-3"></div>
<div x-message="_error" class="bg-red-100 text-red-700 p-3"></div>
<div x-message="_warning" class="bg-yellow-100 text-yellow-700 p-3"></div>
<div x-message="_info" class="bg-blue-100 text-blue-700 p-3"></div>
<!-- Display different message types --> <div x-message="_success" class="bg-green-100 text-green-700 p-3"></div> <div x-message="_error" class="bg-red-100 text-red-700 p-3"></div> <div x-message="_warning" class="bg-yellow-100 text-yellow-700 p-3"></div> <div x-message="_info" class="bg-blue-100 text-blue-700 p-3"></div>
Auto-Show and Auto-Hide
The x-message directive automatically shows and hides based on whether
a message exists. Elements are hidden with display: none when empty.
<!-- Hidden when no error, shown when error exists -->
<span x-message="email" class="error"></span>
<!-- Use x-show for custom visibility logic -->
<div x-data="{ messages: {} }">
<div x-show="messages.email" class="error-box">
<span x-message="email"></span>
</div>
</div>
<!-- Hidden when no error, shown when error exists -->
<span x-message="email" class="error"></span>
<!-- Use x-show for custom visibility logic -->
<div x-data="{ messages: {} }">
<div x-show="messages.email" class="error-box">
<span x-message="email"></span>
</div>
</div>
Complete Validation Example
<form
x-data="{
name: '',
email: '',
password: '',
password_confirmation: ''
}"
@submit.prevent="$postx('/register')"
class="space-y-4 max-w-md">
<!-- Global messages -->
<div x-message="_success" class="bg-green-50 border border-green-200 p-3 rounded"></div>
<div x-message="_error" class="bg-red-50 border border-red-200 p-3 rounded"></div>
<!-- Name -->
<div>
<label class="block font-medium">Name</label>
<input
type="text"
x-model="name"
class="border rounded w-full p-2">
<span x-message="name" class="text-red-500 text-sm"></span>
</div>
<!-- Email -->
<div>
<label class="block font-medium">Email</label>
<input
type="email"
x-model="email"
class="border rounded w-full p-2">
<span x-message="email" class="text-red-500 text-sm"></span>
</div>
<!-- Password -->
<div>
<label class="block font-medium">Password</label>
<input
type="password"
x-model="password"
class="border rounded w-full p-2">
<span x-message="password" class="text-red-500 text-sm"></span>
</div>
<!-- Confirm Password -->
<div>
<label class="block font-medium">Confirm Password</label>
<input
type="password"
x-model="password_confirmation"
class="border rounded w-full p-2">
</div>
<!-- Submit -->
<button
type="submit"
x-loading.disabled
class="bg-blue-500 text-white px-4 py-2 rounded">
<span x-loading.remove>Register</span>
<span x-loading>Creating account...</span>
</button>
</form>
<form
x-data="{
name: '',
email: '',
password: '',
password_confirmation: ''
}"
@submit.prevent="$postx('/register')"
class="space-y-4 max-w-md">
<!-- Global messages -->
<div x-message="_success" class="bg-green-50 border border-green-200 p-3 rounded"></div>
<div x-message="_error" class="bg-red-50 border border-red-200 p-3 rounded"></div>
<!-- Name -->
<div>
<label class="block font-medium">Name</label>
<input
type="text"
x-model="name"
class="border rounded w-full p-2">
<span x-message="name" class="text-red-500 text-sm"></span>
</div>
<!-- Email -->
<div>
<label class="block font-medium">Email</label>
<input
type="email"
x-model="email"
class="border rounded w-full p-2">
<span x-message="email" class="text-red-500 text-sm"></span>
</div>
<!-- Password -->
<div>
<label class="block font-medium">Password</label>
<input
type="password"
x-model="password"
class="border rounded w-full p-2">
<span x-message="password" class="text-red-500 text-sm"></span>
</div>
<!-- Confirm Password -->
<div>
<label class="block font-medium">Confirm Password</label>
<input
type="password"
x-model="password_confirmation"
class="border rounded w-full p-2">
</div>
<!-- Submit -->
<button
type="submit"
x-loading.disabled
class="bg-blue-500 text-white px-4 py-2 rounded">
<span x-loading.remove>Register</span>
<span x-loading>Creating account...</span>
</button>
</form>