# vue-forms
> Inertia form handling with DTOs. Use when creating forms that submit to Laravel. Always initialize from props and use form.submit().
- Author: Nick Ritel
- Repository: hardimpactdev/claude-plugins
- Version: 20260107151222
- Stars: 0
- Forks: 0
- Last Updated: 2026-02-07
- Source: https://github.com/hardimpactdev/claude-plugins
- Web: https://mule.run/skillshub/@@hardimpactdev/claude-plugins~vue-forms:20260107151222
---
---
name: vue-forms
description: Inertia form handling with DTOs. Use when creating forms that submit to Laravel. Always initialize from props and use form.submit().
allowed-tools: Read, Grep, Glob, Write, Edit, Bash
---
# Inertia Form Handling
## When to Apply
Apply when:
- Creating forms with Inertia
- Handling form validation
- Submitting data to Laravel
- Managing form state
## Form Initialization
```typescript
// Always initialize from props (never empty forms)
const props = defineProps<{
resource: App.Data.UserData;
}>();
const form = useForm(props.resource);
// With defaults for optional fields
const form = useForm({
...props.resource,
tags: props.resource.tags ?? [],
});
```
## Form Submission
```typescript
// Always use form.submit() - Wayfinder includes HTTP method
const createUser = () => {
form.submit(Controllers.UserController.store(), {
onSuccess: () => {
form.reset();
toast.success('User created');
},
onError: () => {
toast.error('Please check the form errors');
},
});
};
// Update with route parameters
const updateProject = () => {
form.submit(Controllers.ProjectController.update({
project: props.project.id
}), {
preserveScroll: true,
onSuccess: () => toast.success('Project updated'),
});
};
// Delete with confirmation
const deleteResource = () => {
if (!confirm('Are you sure?')) return;
form.submit(Controllers.ResourceController.destroy({
resource: props.resource.id
}));
};
```
## Validation Errors
```vue
```
## Form State
```vue
```
## Dynamic Fields
```typescript
// Array fields
const form = useForm({
...props.resource,
contacts: props.resource.contacts || [{ name: '', email: '' }],
});
const addContact = () => {
form.contacts.push({ name: '', email: '' });
};
const removeContact = (index: number) => {
form.contacts.splice(index, 1);
};
```
## File Uploads
```typescript
const form = useForm({
...props.resource,
avatar: null as File | null,
});
const handleFileChange = (event: Event) => {
const file = (event.target as HTMLInputElement).files?.[0];
if (file) {
form.avatar = file;
}
};
```
## Unsaved Changes Warning
```typescript
import { onBeforeRouteLeave } from 'vue-router';
onBeforeRouteLeave((to, from, next) => {
if (form.isDirty && !confirm('You have unsaved changes. Leave anyway?')) {
next(false);
} else {
next();
}
});
```
## Common Mistakes
| Wrong | Right |
|-------|-------|
| `useForm({ name: '', email: '' })` | `useForm(props.resource)` |
| `form.post(url)` | `form.submit(Controllers.X.store())` |
| `form.patch(url)` | `form.submit(Controllers.X.update())` |
| Manual interface definition | Use `App.Data.XData` types |
| Ignoring `form.errors` | Display errors for each field |
| No loading state | Check `form.processing` |
## Don'ts
- Never create empty forms - initialize from props
- Never use `form.post()`, `form.patch()`, `form.delete()` - use `form.submit()`
- Never manually construct URLs - use Wayfinder Controllers
- Never define TypeScript interfaces manually - use generated types
- Never ignore validation errors
- Never forget to disable submit button during processing
- Never modify the DTO structure (field names must match backend)