Formlink is a type-safe form-handling library for Laravel + Vue.js applications. It abstracts away form submissions, file uploads, and validation error handling, offering seamless integration with Laravel and Vue.js applications, inspired by Inertia.js's simplicity.
- β¨ Type Safety: Full TypeScript support with type inference
- π Zero Configuration: Works out of the box with Laravel
- π Built-in CSRF Protection: Automatic CSRF token handling
- π Progress Tracking: Real-time file upload progress
- π― Smart Error Handling: Automatic Laravel validation error management
- β‘ Event Hooks: Rich lifecycle hooks for form submission events
- π± Vue 3 Ready: Reactive forms with Vue 3 composition API
- π οΈ Framework Agnostic: Can be used with any backend, not limited to Laravel
- π HTTP Method Support: Comprehensive support for all HTTP methods
- π§Ή Form Reset & State Management: Easily reset form data to initial state
- π Debounced Submissions: Support for debounced form submissions
npm install formlink
# or
yarn add formlink
# or
pnpm add formlink
import { useForm } from 'formlink';
interface ContactForm {
name: string;
email: string;
message: string;
}
const form = useForm<ContactForm>({
name: '',
email: '',
message: ''
});
await form.post('/api/contact');
<template>
<form @submit.prevent="submit">
<!-- Name field -->
<div>
<input v-model="form.name" type="text" :class="{ error: form.errors.name }" />
<span v-if="form.errors.name">{{ form.errors.name }}</span>
</div>
<!-- Email field -->
<div>
<input v-model="form.email" type="email" :class="{ error: form.errors.email }" />
<span v-if="form.errors.email">{{ form.errors.email }}</span>
</div>
<!-- File upload with progress -->
<div>
<input type="file" @change="handleFile" />
<div v-if="form.progress">{{ form.progress.percentage }}% uploaded</div>
</div>
<!-- Submit button -->
<button type="submit" :disabled="form.processing">
{{ form.processing ? 'Sending...' : 'Send Message' }}
</button>
</form>
</template>
<script setup lang="ts">
import { useForm } from 'formlink';
interface ContactForm {
name: string;
email: string;
file: File | null;
}
const form = useForm<ContactForm>({
name: '',
email: '',
file: null
});
const handleFile = (e: Event) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
form.file = file;
}
};
const submit = async () => {
await form.post('/api/contact', {
onBefore: () => {
console.log('Request starting');
},
onProgress: (progress) => {
console.log(`${progress.percentage}% uploaded`);
},
onSuccess: (response) => {
console.log('Submission successful', response.data);
},
onError: (errors) => {
console.log('Validation errors', errors);
},
onFinish: () => {
console.log('Request finished');
}
});
};
</script>
The FormOptions
object allows you to configure hooks and behaviors for form submissions. Here are the available options:
Option | Type | Description |
---|---|---|
resetOnSuccess |
boolean |
Whether to reset the form to its initial state after a successful submission. |
onBefore |
() => void |
Hook that is called before the form submission begins. |
onSuccess |
(response: AxiosResponse) => void |
Hook that is called when the form submission is successful. |
onCanceled |
() => void |
Hook that is called when the form submission is canceled. |
onError |
(errors: Partial<Record<keyof TForm, string>>) |
Hook that is called when validation errors occur (e.g., from a Laravel backend). |
onFinish |
() => void |
Hook that is called when the form submission finishes, whether successful or not. |
onProgress |
(progress: Progress) => void |
Hook that tracks file upload progress or long-running requests. |
await form.post('/api/contact', {
resetOnSuccess: true,
onBefore: () => console.log('Submitting...'),
onSuccess: (response) => console.log('Submitted', response.data),
onError: (errors) => console.error('Validation errors:', errors),
onFinish: () => console.log('Request finished'),
onProgress: (progress) => console.log(`Upload ${progress.percentage}% complete`),
});
Formlink provides various reactive states:
form.processing; // Is the form being submitted?
form.progress; // Upload progress data
form.errors; // Validation errors
form.isDirty; // Has the form been modified?
form.wasSuccessful; // Was the form submission successful?
form.recentlySuccessful; // Was the form submission successful recently?
Formlink supports multiple HTTP methods:
form.get(url); // GET request
form.post(url); // POST request
form.put(url); // PUT request
form.patch(url); // PATCH request
form.delete(url); // DELETE request
form.options(url); // OPTIONS request
You can transform form data before it is submitted:
form.transform((data) => ({
...data,
name: data.name.trim().toLowerCase()
}));
Set or clear errors manually:
// Set a single error
form.setError('email', 'Invalid email format');
// Set multiple errors at once
form.setErrors({
email: 'Invalid email format',
name: 'Name is required',
formError: 'Please fix the errors before submitting'
});
// Clear all errors
form.clearErrors();
Reset the form data to its initial state:
// Reset all fields
form.reset();
// Reset specific fields
form.reset('email', 'name');
Set new default values for the form:
// Set all current data as new defaults
form.setDefaults();
// Set a specific field's default value
form.setDefaults('email', '[email protected]');
// Set multiple field defaults at once
form.setDefaults({
name: 'John Doe',
email: '[email protected]'
});
Formlink provides a simple validation system:
// Define validation rules
form.rules = {
email: [
{ validate: (value) => !!value, message: 'Email is required' },
{ validate: (value) => /\S+@\S+\.\S+/.test(value as string), message: 'Invalid email format' }
],
name: [
{ validate: (value) => !!value, message: 'Name is required' }
]
};
// Run validation
const isValid = await form.validate();
if (isValid) {
await form.post('/api/contact');
}
For search forms or auto-save functionality:
// Debounce form submission (default 300ms)
form.submitDebounced('get', '/api/search');
// Custom debounce time (1000ms)
form.submitDebounced('post', '/api/auto-save', {}, 1000);
Cancel an ongoing form submission:
// Start submission
const submissionPromise = form.post('/api/upload-large-file');
// Cancel it if needed
form.cancel();
You can use a custom Axios instance for your form requests:
import axios from 'axios';
const customAxios = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
'X-Custom-Header': 'value'
}
});
const form = useForm(data, customAxios);
For single-page applications, ensure proper cleanup:
// In your component's onUnmounted lifecycle hook
onUnmounted(() => {
form.dispose();
});
Contributions are welcome! See our Contributing Guide for details.
To get started:
- Fork the repository.
- Create your feature branch (
git checkout -b feature/your-feature
). - Commit your changes (
git commit -m 'Add feature'
). - Push to your branch (
git push origin feature/your-feature
). - Open a pull request.
# Clone the repository
git clone https://github.com/Thavarshan/formlink.git
# Install dependencies
npm install
# Run tests
npm test
# Build the package
npm run build
Formlink is open-sourced software licensed under the MIT license.
Special thanks to Jonathan Reinink for his work on InertiaJS, which inspired this project.