mrpunyapal/laravel-client-validation
| Install | |
|---|---|
composer require mrpunyapal/laravel-client-validation |
|
| Latest Version: | 0.0.3 |
| PHP: | ^8.3 |
| License: | MIT |
| Last Updated: | Aug 10, 2026 |
| Links: | GitHub · Packagist |
Laravel Client Validation
A powerful validation package that brings Laravel validation rules to the client-side. Validate forms in real-time using the same rules you know from Laravel — works with any backend or as a standalone NPM package.
Features
- 104 client-side rules — Comprehensive coverage of Laravel validation rules
- 5 remote rules — Server-side AJAX validation for
unique,exists, etc. - TypeScript declarations — Full type definitions for IDE autocompletion
- Laravel 12/13 support — Compatible with the latest Laravel versions
- Livewire v3 & v4 — Automatic version detection and optimized integration
- Filament v3 & v5 — Panel plugin with field-level validation
- Backend-agnostic — Use with Laravel, Django, Express, Rails, or any backend
- Multiple integrations — Alpine.js, Livewire, Filament, Vanilla JS, React, Vue
- Real-time validation — Instant feedback on blur, input, or submit
- FormRequest support — Extract rules from existing Laravel FormRequest classes
- Validation hooks —
beforeValidate,afterValidate, field-level events - Bail support — Stop on first failure per field with
bailrule - Batch validation — Validate multiple fields in a single AJAX request
- Rate limiting — Built-in request throttling for AJAX validation
- Zero configuration — Works out of the box with sensible defaults
- Tree-shakeable — ES module subpath exports for minimal bundle size
Quick Start
Option A: Laravel Package (Composer)
composer require mrpunyapal/laravel-client-validation
php artisan client-validation:install
Laravel Boost
If your Laravel project uses Laravel Boost, this package ships a third-party Boost skill for package-aware code generation and debugging.
Install Boost if needed:
composer require laravel/boost --dev
php artisan boost:install
If Boost is already installed in the app, refresh package-provided skills after adding or updating this package:
php artisan boost:update --discover
When skills are enabled, Boost can install the laravel-client-validation-development skill and guide AI agents toward the package's recommended Blade, Alpine, Livewire, Filament, and remote-validation patterns.
Option B: Standalone NPM Package
npm install laravel-client-validation
Import only what you need:
// Core engine only (no framework dependency)
import { LaravelValidator, RuleRegistry } from 'laravel-client-validation/core';
// Alpine.js adapter
import { createAlpineValidator } from 'laravel-client-validation/alpine';
// Vanilla JS adapter
import { createFormValidator } from 'laravel-client-validation/vanilla';
Option C: CDN / Script Tag
<script src="https://unpkg.com/laravel-client-validation/resources/js/dist/client-validation.iife.js"></script>
<script>
const validator = new LaravelClientValidation.Validator({
rules: { email: 'required|email' }
});
</script>
Usage with Alpine.js
Simple Field Validation
{{-- Validate on blur (default) --}}
<input x-validate="'required|email'" name="email">
{{-- Validate as you type --}}
<input x-validate.live="'required|min:3'" name="username">
{{-- Validate on form submit only --}}
<input x-validate.submit="'required|min:8'" name="password">
{{-- Bail on first failure --}}
<input x-validate="'bail|required|email|unique:users'" name="email">
Complete Form Component
<div x-data="validation({
rules: {
email: 'required|email',
password: 'required|min:8',
password_confirmation: 'required|same:password'
},
messages: {
'email.required': 'Please enter your email',
'password.min': 'Password is too short'
}
})">
<form @submit.prevent="submit(async (data) => {
console.log('Valid!', data);
})">
<input x-model="form.email" @blur="validate('email')">
<span x-text="error('email')" x-show="hasError('email')"></span>
<input type="password" x-model="form.password" @blur="validate('password')">
<span x-text="error('password')" x-show="hasError('password')"></span>
<input type="password" x-model="form.password_confirmation">
<button :disabled="validating || !isValid()">Submit</button>
</form>
</div>
Available Methods in validation() Component
| Method | Description |
|---|---|
validate('field') |
Validate a single field |
validateAll() |
Validate all fields |
submit(callback) |
Validate then call callback if valid |
error('field') |
Get first error for field |
hasError('field') |
Check if field has errors |
hasErrors() |
Check if any errors exist |
isValid() |
Check if form is valid |
clearError('field') |
Clear errors for field |
reset() |
Reset form to initial state |
Usage with Livewire
Supports both Livewire v3 and v4. Version is detected automatically.
Using the x-wire-validate Directive
<div wire:id="my-component">
<form wire:submit="save">
<input type="email"
wire:model="email"
x-wire-validate="'required|email'"
name="email">
<span class="validation-error" data-error="email"></span>
{{-- Livewire v4: use wire:model.live for real-time sync --}}
<input type="text"
wire:model.live="username"
x-wire-validate.live="'required|alpha_dash|min:3'"
name="username">
<span class="validation-error" data-error="username"></span>
<button type="submit">Submit</button>
</form>
</div>
Using the PHP Trait
use Livewire\Component;
use MrPunyapal\ClientValidation\Livewire\WithClientValidation;
class CreateUser extends Component
{
use WithClientValidation;
public string $name = '';
public string $email = '';
public string $password = '';
public string $password_confirmation = '';
protected $rules = [
'name' => 'required|string|min:2|max:50',
'email' => 'required|email',
'password' => 'required|min:8|confirmed',
];
protected $messages = [
'email.required' => 'Please enter your email address',
'password.confirmed' => 'Passwords do not match',
];
public function render()
{
return view('livewire.create-user');
}
}
In your Blade view:
<div x-data="{ clientRules: @json($this->getClientRules()) }">
<input wire:model="email"
x-wire-validate="clientRules.email"
name="email">
</div>
Livewire Events
protected $listeners = [
'client-validation-error' => 'handleClientError',
'client-validation-cleared' => 'handleClientCleared',
];
public function handleClientError($data)
{
// $data = ['field' => 'email', 'errors' => ['The email field is required.']]
}
Usage with Filament
Register the Plugin
use MrPunyapal\ClientValidation\Filament\ClientValidationPlugin;
class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
ClientValidationPlugin::make()
->enableRemoteValidation()
->validationMode('live'),
]);
}
}
Add Validation to Fields
Use the HasClientValidation trait on your custom Filament fields:
use Filament\Forms\Components\Field;
use MrPunyapal\ClientValidation\Filament\HasClientValidation;
class MyField extends Field
{
use HasClientValidation;
}
Or use the pre-built ClientValidatedField:
use MrPunyapal\ClientValidation\Filament\ClientValidatedField;
ClientValidatedField::make('email')
->clientValidation('required|email')
->clientValidationMode('live');
Usage with Vanilla JS (No Framework)
Using Data Attributes
<form data-validate>
<input name="email"
data-rules="required|email"
data-validate-on="blur">
<input name="username"
data-rules="required|alpha_dash|min:3"
data-validate-on="input">
<button type="submit">Submit</button>
</form>
Programmatic Validation
import { LaravelValidator } from 'laravel-client-validation/core';
const validator = new LaravelValidator({
rules: {
email: 'required|email',
password: 'required|min:8'
},
messages: {
'email.required': 'Email is required'
}
});
const result = await validator.validateField('email', 'test@example.com');
console.log(result.valid, result.errors);
const formResult = await validator.validateAll({
email: 'test@example.com',
password: '12345678'
});
Using Blade Directives
<form data-validate>
<input name="email" @rules('email', 'required|email', ['mode' => 'blur'])>
<input name="username" @validateLive('username', 'required|min:3')>
<input name="password" @validateBlur('password', 'required|min:8')>
</form>
Using with FormRequest
In Controller
use MrPunyapal\ClientValidation\Facades\ClientValidation;
public function create()
{
$validation = ClientValidation::fromRequest(CreateUserRequest::class);
return view('users.create', compact('validation'));
}
In Blade
<div x-data="validation(@js($validation))">
{{-- Form fields --}}
</div>
Backend-Agnostic Remote Validation
The RemoteValidator works with any backend, not just Laravel. Configure it for your stack:
Express.js / Node
import { RemoteValidator } from 'laravel-client-validation/core';
const remote = new RemoteValidator({
endpoint: '/api/validate',
csrf: false,
requestFormatter: (field, value, rule, params) => ({
field_name: field,
field_value: value,
validation_rule: rule,
rule_params: params
}),
responseParser: (response) => ({
valid: response.success,
message: response.error || null
})
});
Django
const remote = new RemoteValidator({
endpoint: '/validate/',
csrfHeaderName: 'X-CSRFToken',
csrfTokenResolver: () => document.cookie.match(/csrftoken=([^;]+)/)?.[1]
});
Custom Adapter
const remote = new RemoteValidator();
remote.setAdapter(async (field, value, rule, params) => {
const res = await myHttpClient.post('/validate', { field, value, rule });
return { valid: res.ok, message: res.error };
});
Validation Rules
Client-Side Rules (104 — Instant)
Core: required, nullable, filled, present, bail
String: string, email, url, active_url, alpha, alpha:ascii, alpha_num, alpha_num:ascii, alpha_dash, alpha_dash:ascii, regex, not_regex, contains, doesnt_contain, lowercase, uppercase, starts_with, ends_with, doesnt_start_with, doesnt_end_with, ascii, uuid, uuid:version, ulid, json, hex_color
Numeric: numeric, numeric:strict, integer, integer:strict, decimal, multiple_of, digits, digits_between, min_digits, max_digits
Size: min, max, between, size
Comparison: confirmed, confirmed:field, same, different, gt, gte, lt, lte, in, not_in, enum
Date: date, after, before, after_or_equal, before_or_equal, date_equals, date_format, timezone
Conditional: required_if, required_unless, required_with, required_without, required_with_all, required_without_all, required_if_accepted, required_if_declined, required_array_keys
Presence / Missing: present_if, present_unless, present_with, present_with_all, missing, missing_if, missing_unless, missing_with, missing_with_all
Prohibition: prohibited, prohibited_if, prohibited_unless, prohibited_if_accepted, prohibited_if_declined, prohibits
Boolean: boolean, boolean:strict, accepted, accepted_if, declined, declined_if
Network: ip, ipv4, ipv6, mac_address
Array: array, distinct, in_array, in_array_keys, list
File: file, image, mimes, mimetypes, extensions, dimensions
Advanced: any_of, password_strength
Remote Rules (5 — AJAX)
unique, exists, password, current_password, encoding
<input x-validate.live="'required|email|unique:users,email'" name="email">
See docs/md/validation-rules.md for the canonical validation rules guide and examples.
Configuration
php artisan vendor:publish --tag=client-validation-config
Key options:
return [
'validation_mode' => 'blur', // 'blur', 'input', 'submit'
'debounce_ms' => 300, // Debounce for live validation
'enable_ajax_validation' => true, // Enable AJAX for remote rules
'rate_limit' => [
'max_attempts' => 60, // Requests per window (0 = disabled)
'decay_seconds' => 60, // Window duration
],
'error_template' => [
'container_class' => 'text-red-500 text-sm mt-1',
],
'field_styling' => [
'valid_class' => 'border-green-500',
'invalid_class' => 'border-red-500',
],
];
Validation Hooks
const validator = new LaravelClientValidation.Validator({ rules });
validator
.beforeValidate(({ data }) => console.log('Starting...'))
.afterValidate(({ valid, errors }) => console.log('Done!', valid));
Custom Rules
Client-Side
LaravelClientValidation.extend('phone', (value, params, field, context) => {
if (!value) return true;
return /^\+?[\d\s-]{10,}$/.test(value);
}, 'The :attribute must be a valid phone number.');
<input x-validate="'required|phone'" name="phone">
Server-Side (PHP)
ClientValidation::extend('strong_password', function ($value) {
return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/', $value);
}, 'Password must contain uppercase, lowercase, and numbers.');
NPM Subpath Exports
| Import Path | Contents |
|---|---|
laravel-client-validation |
Full bundle (all adapters) |
laravel-client-validation/core |
LaravelValidator, RuleRegistry, RemoteValidator, EventEmitter |
laravel-client-validation/alpine |
Alpine.js x-validate directive |
laravel-client-validation/vanilla |
Vanilla JS data-validate form validator |
laravel-client-validation/livewire |
Livewire adapter |
laravel-client-validation/react |
React hook adapter |
laravel-client-validation/vue |
Vue composable adapter |
Examples
See the examples/ directory for complete demos:
Testing
# PHP tests (Pest)
composer test
# JavaScript tests (Vitest)
npm test
License
The MIT License (MIT). See License File for more information.
Related Packages
Validate forms transparently with Javascript reusing your Laravel Validation Rul...
This package makes validation rules defined in laravel work client-side by conve...
Validate forms transparently with Javascript reusing your Laravel Validation Rul...
Validate forms transparently with Javascript reusing your Laravel Validation Rul...
This package makes validation rules defined in laravel work client-side by conve...