vue-laravel-form-validator
v1.0.2
Published
A Vue 3 composable for Laravel-style form validation. Supports string, numeric, array, file rules, conditionals (required_if, same), custom labels, and multi-language error messages.
Maintainers
Readme
vue-laravel-form-validator
A Vue 3 composable for Laravel-style form validation on the frontend.
What is it?
vue-laravel-form-validator is a lightweight Vue 3 composable that brings the familiar Laravel validation syntax to your frontend forms.
Instead of learning a new DSL or installing a heavy library, you write rules the same way you write them in Laravel:
required|string|min:3|max:100
required|email
required|integer|between:18,65
required_if:has_car,true|alpha_num|size:6It handles error messages in multiple languages, supports custom field labels, outputs a ready-to-send FormData or a plain Object, and optionally auto-scrolls to the first error on submit.
Features
- ✅ Laravel-compatible rule syntax (
|separated) - ✅
required,string,numeric,integer,boolean,email,url,date,json,array - ✅ Size rules:
min,max,size,between,digits,digits_between - ✅ Format rules:
alpha,alpha_spaces,alpha_num,alpha_dash,regex - ✅ File rules:
file,mimes,max(in KB) - ✅ List rules:
in,not_in - ✅ Conditional rules:
required_if,required_unless,required_with,same,confirmation - ✅ Custom field labels for user-friendly error messages
- ✅ Multi-language error messages (
es,enincluded — easily extendable) - ✅ Returns
FormData(with file support) or a plainObject - ✅ Optional scroll to first error on submit
- ✅ TypeScript support with full type definitions
Installation
npm install vue-laravel-form-validatorBasic Usage
<script setup lang="ts">
import { reactive } from 'vue';
import { useFormValidator } from 'vue-laravel-form-validator';
const { validateErrors, getFirstError, errors, resetErrors } = useFormValidator();
const form = reactive({
name: { value: '', rules: 'required|alpha_spaces|min:3', label: 'Full Name' },
email: { value: '', rules: 'required|email', label: 'Email' },
age: { value: '', rules: 'required|integer|between:18,65', label: 'Age' },
});
const submit = () => {
const data = validateErrors(form, 'en');
if (data) {
// data is a FormData — ready for axios or fetch
axios.post('/api/register', data);
}
};
</script>
<template>
<form @submit.prevent="submit">
<input v-model="form.name.value" />
<p v-if="getFirstError('name')">{{ getFirstError('name') }}</p>
<input v-model="form.email.value" type="email" />
<p v-if="getFirstError('email')">{{ getFirstError('email') }}</p>
<input v-model="form.age.value" type="number" />
<p v-if="getFirstError('age')">{{ getFirstError('age') }}</p>
<button type="submit">Submit</button>
</form>
</template>Form Field Structure
Each field in your form object must follow this shape:
{
value: any; // The field's current value (required)
rules?: string; // Laravel-style validation rules (optional)
label?: string; // Human-friendly name used in error messages (optional)
ignore?: boolean; // If true, the field is excluded from the output FormData/Object
}validateErrors(form, lang?, objectFormat?)
The main validation function. Returns:
- ✅
FormData— if validation passes andobjectFormatisfalse(default) - ✅
Record<string, any>— if validation passes andobjectFormatistrue - ❌
false— if there are validation errors (errors are stored inerrors)
// Returns FormData (default) — great for file uploads
const data = validateErrors(form, 'es');
// Returns a plain Object — great for JSON APIs
const data = validateErrors(form, 'es', true);API Reference
| Function | Description |
|---|---|
| validateErrors(form, lang?, objectFormat?) | Validates the form and returns FormData, Object, or false |
| errors | Reactive object containing all current errors { field: string[] } |
| getError(name) | Returns the array of errors for a field |
| getFirstError(name) | Returns the first error string for a field, or null |
| setErrors(errors) | Manually set errors (e.g. from a server 422 response) |
| clearError(name) | Clear errors for a specific field |
| resetErrors() | Clear all errors |
Available Rules
Type Rules
| Rule | Description |
|---|---|
| required | Field must not be empty |
| string | Must be a string |
| numeric | Must be a numeric value |
| integer | Must be a whole number |
| boolean | Must be true, false, 1, 0, "1", or "0" |
| email | Must be a valid email address |
| url | Must be a valid URL |
| date | Must be a parseable date |
| array | Must be a JavaScript array |
| json | Must be a valid JSON string |
| file | Must be a File object |
Size Rules
Behavior adapts based on type:
- string / alpha: counts characters
- numeric / integer: compares numeric value
- array: counts elements
- file: compares size in KB
| Rule | Description |
|---|---|
| min:3 | Minimum length / value / items / KB |
| max:255 | Maximum length / value / items / KB |
| size:6 | Exact length / value / items / KB |
| between:18,65 | Value/length must be within range |
| digits:4 | Must have exactly N digits |
| digits_between:4,6 | Must have between N and M digits |
Format Rules
| Rule | Description |
|---|---|
| alpha | Letters only (including accented chars) |
| alpha_spaces | Letters and spaces only |
| alpha_num | Letters and numbers only |
| alpha_dash | Letters, numbers, - and _ only |
| regex:^\\d{4}-\\d{4}$ | Must match a custom regular expression |
File Rules
| Rule | Description |
|---|---|
| file | Must be a File object |
| mimes:pdf,jpg,png | Must have one of the specified extensions |
| max:2048 | Must not exceed the size in KB |
List Rules
| Rule | Description |
|---|---|
| in:admin,user,editor | Value must be one of the listed options |
| not_in:banned,deleted | Value must NOT be one of the listed options |
Conditional Rules
| Rule | Description |
|---|---|
| required_if:field,value | Required if another field equals a value |
| required_unless:field,value | Required unless another field equals a value |
| required_with:field1,field2 | Required if any of the listed fields have a value |
| same:other_field | Must match another field's value |
| confirmation | Must match the field named without _confirmation |
Options
Pass options when initializing the composable:
const validator = useFormValidator({
// Scroll smoothly to the first invalid field on submit
scrollToError: true,
// Provide custom or additional language messages
messages: {
es: {
required: 'Por favor completa el campo {field}.',
},
fr: {
required: 'Le champ {field} est obligatoire.',
}
}
});Message Placeholders
You can use these placeholders in your custom messages:
| Placeholder | Replaced with |
|---|---|
| {field} | The field's label, or the key with underscores replaced by spaces |
| {min} | The minimum value |
| {max} | The maximum value |
| {size} | The exact size value |
| {digits} | The exact digit count |
| {other} | The name of the other field (for same, required_if, etc.) |
| {value} | The expected value (for required_if, required_unless) |
| {values} | Comma-separated field names (for required_with) |
| {mimes} | Allowed file extensions (for mimes) |
Multi-language Support
The package ships with es (Spanish) and en (English) out of the box.
Pass the language code as the second argument to validateErrors:
// Spanish
validateErrors(form, 'es');
// English
validateErrors(form, 'en');You can also add your own language by providing custom messages via options:
const { validateErrors } = useFormValidator({
messages: {
fr: {
required: 'Le champ {field} est obligatoire.',
email: 'Le champ {field} doit être une adresse e-mail valide.',
// ... add all the keys you need
}
}
});
validateErrors(form, 'fr');Handling Server Errors (422)
If Laravel returns a 422 validation error, you can inject those errors directly into the composable:
const { validateErrors, setErrors } = useFormValidator();
const submit = async () => {
const data = validateErrors(form, 'es');
if (!data) return;
try {
await axios.post('/api/register', data);
} catch (error) {
if (error.response?.status === 422) {
setErrors(error.response.data.errors);
}
}
};Working Example
<script setup lang="ts">
import { reactive } from 'vue';
import { useFormValidator } from 'vue-laravel-form-validator';
const { validateErrors, getFirstError, errors, resetErrors } = useFormValidator({
scrollToError: true,
});
const form = reactive({
name: { value: '', rules: 'required|alpha_spaces|min:3', label: 'Full Name' },
age: { value: '', rules: 'required|integer|between:18,65', label: 'Age' },
email: { value: '', rules: 'required|email', label: 'Email' },
phone: { value: '', rules: 'required|regex:^\\d{4}-\\d{4}$', label: 'Phone' },
role: { value: '', rules: 'required|in:admin,user,editor', label: 'Role' },
skills: { value: [], rules: 'array|min:2', label: 'Skills' },
password: { value: '', rules: 'required|min:8', label: 'Password' },
password_confirmation: { value: '', rules: 'required|same:password', label: 'Confirm Password' },
avatar: { value: null, rules: 'file|mimes:jpg,png|max:1024', label: 'Avatar' },
});
const handleFile = (e: Event) => {
const target = e.target as HTMLInputElement;
form.avatar.value = target.files?.[0] ?? null;
};
const submit = () => {
const data = validateErrors(form, 'en');
if (data) {
axios.post('/api/users', data); // data is a FormData
}
};
</script>License
MIT — Made with ❤️ by Didier Alvarez
