Handling forms in Laravel Inertia with Vue

Forms are the components primarily used to obtain data from the user, and they are one of the main features we have in Laravel Inertia. We can use them perfectly while maintaining the classic Laravel server-side schema, but using Vue with Inertia on the client side.

Working with a form in Laravel Inertia is one of the best ways to combine the robustness of the Laravel backend with the fluidity of Vue on the client.
When I started working with Inertia, something that really surprised me was how natural it feels to handle forms: they feel like traditional Laravel forms, but with the advantages of an SPA, without having to build a complete API.

In this guide, I show you exactly how to implement a form for both creating and editing records, including:

  • Laravel controllers
  • validation
  • props
  • error handling

Forms are a fundamental tool for collecting user information. When we talk about the update process, we usually start from a listing where the user chooses which record they want to modify.
Upon selecting an item, the application redirects them to the edit form. This form already includes the record's identifier, which typically travels in the URL and allows the backend to know which data should be loaded from the database.

Just like in any HTML form, we can include any type of field: inputs, selects, textareas, uploads, etc.
The key difference with Inertia is that everything flows like an SPA, without full page reloads, maintaining the simplicity of the typical Laravel lifecycle.

Once the user has edited the information in the form, they can submit the form by clicking an "Update" or "Submit" button so that Laravel handles the validations and subsequent saving to the database if the data is correct, or displays the validation errors.

Laravel Inertia is nothing more than a scaffolding or skeleton for Laravel, which adds functionality to a Laravel project to integrate Vue as the client-side technology, with Laravel as the server-side technology.

1. Laravel Inertia and why it facilitates form handling?

Inertia is a bridge that eliminates the typical divorce between backend and frontend. Technically, it's not a framework, but a scaffolding that allows you to use Vue, React, or Svelte as views, while Laravel continues to control routes, controllers, and validation.

How Vue and Laravel integrate into the same flow

In my case, I describe Inertia as "Vue on steroids," because I can continue using my Laravel controllers exactly as they are, but instead of returning a view(), I return an inertia(), which displays a Vue component with included data.

Real advantages when creating dynamic forms

  • You can use v-model just like in any Vue app.
  • You can submit forms with Inertia.post/put/delete without reloading the page.
  • Laravel still handles validation with FormRequest.
  • The form state (errors, processing, etc.) is managed by useForm.

Inertia greatly simplifies the form handling process with Vue, since we don't have to implement a Rest API, and the useForm object facilitates the validation process.

2. Preparing the Laravel controller to handle forms

In Laravel, in a controller that works with categories, we will create the methods to present the Vue component, using the inertia() function instead of the classic view() function that allows rendering a Vue component.

create() method and data submission to the component

And the store function, to store the category data in the database; that is, to create the category:

app/Http/Controllers/Dashboard/CategoryController.php

<?php
namespace App\Http\Controllers\Dashboard;
use App\Http\Controllers\Controller;
use App\Models\Category;
use Illuminate\Http\Request;
class CategoryController extends Controller
{
    public function create()
    {
        return inertia("Dashboard/Category/Create");
    }
}

store() method to save records with Inertia

In my projects, I usually process it like this:

class CategoryController extends Controller
{
    public function store(Request $request)
    {
        Category::create(
            [
                'title' => request('title'),
                'slug' => request('slug'),
            ]
            );
        dd($request->all());
    }
}

3. Creating a basic form in Inertia with Vue

The form is created with its respective v-models and I process the submit to send the form to the previous method:

resources/js/Pages/Dashboard/Category/Create.vue

<template>
  <form @submit.prevent="submit">
    <label for="">Title</label>
    <input type="text" v-model="form.title" />
    <label for="">Slug</label>
    <input type="text" v-model="form.slug" />
    <button type="submit">Send</button>
  </form>
</template>

For the script section, we use the useForm() function which allows us to manage the form in a simple way; it's a helper that facilitates error handling and overall form status:

array:10 [
  "title" => "Test"
  "slug" => "test-slug"
  "isDirty" => true
  "errors" => []
  "hasErrors" => false
  "processing" => false
  "progress" => null
  "wasSuccessful" => false
  "recentlySuccessful" => false
  "__rememberable" => true
]

Submitting data using useForm and Inertia.post

Finally, we have the use of the get, post, put, path, or delete functions, via the Inertia object in JavaScript which we use to submit the form:

<script>
import { Inertia } from "@inertiajs/inertia";
import { useForm } from "@inertiajs/inertia-vue3";
export default {
  setup() {
    const form = useForm({
      title: null,
      slug: null,
    });
    function submit() {
      Inertia.post(route("category.store"), form);
    }
    return { form, submit };
  },
};
</script>

State management: processing, errors, wasSuccessful

The helper automatically provides this:

form.processing
form.errors
form.wasSuccessful

And it synchronizes without you having to manually manage any state.

Edit Records based on Click Event

Video thumbnail

Editing an element directly in the listing —without using modals or additional screens— provides a very fluid user experience and is extremely simple to implement with Vue and Inertia.

In this case, the idea is that when the user clicks on the name of a to-do, that <span> is replaced by an <input> to allow for inline editing. Upon pressing Enter, the update is sent to the server using Inertia.

Below I will explain step-by-step how to implement it.

1. Modify the list to enable edit mode

File: resources/js/Pages/Todo/Index.vue

<li v-for="t in todos" class="border py-3 px-4 mt-2" :key="t">
 <span v-show="!t.editMode" @click="t.editMode = true">
   {{ t.name }}
 </span>
 <TextInput
   v-show="t.editMode"
   v-model="t.name"
   @keyup.enter="update(t)"
 />
 <button @click="remove" class="float-right">
   ***
 </button>
</li>

Explanation

To each to-do, we add an additional property called editMode, which doesn't exist in the model but is added dynamically on the client side.

  • editMode === true → The <input> for editing is shown.
  • editMode === false → The <span> with the to-do name is shown.

Thanks to Vue's reactivity, changing editMode or the name is automatically reflected in the interface.

Behavior

When the user clicks on the <span>, edit mode is activated.

When they press Enter inside the <input>, the update(t) method is executed.

2. The update method on the client (Vue + Inertia)

update(todo) {
 todo.editMode = false;
 router.put(route("todo.update", todo.id), {
   name: todo.name,
 });
},

What happens here?

The edit mode is deactivated (editMode = false).

A PUT request is sent to the backend using Inertia.router.put.

The new name of the to-do is sent.

This allows for a fluid experience without full page reloads.

3. Laravel Controller

File: app/Http/Controllers/TodoController.php

public function update(Todo $todo, Request $request)
{
   $request->validate([
       'name' => 'required|string|max:255'
   ]);
   Todo::where("id", $todo->id)
       ->where('user_id', auth()->id())
       ->update([
           'name' => $request->name,
       ]);
   return back();
}

Important points:

  • The name field is validated.
  • It is ensured that only the user who owns the to-do can edit it (where('user_id', auth()->id())).
  • Only the name is updated.

4. Define the update route

File: routes/web.php

Route::group([
   'prefix' => 'todo',
   'middleware' => ['auth:sanctum', config('jetstream.auth_session'), 'verified']
], function () {
   Route::get('/', [TodoController::class, 'index'])->name('todo.index');
   Route::post('store', [TodoController::class, 'store'])->name('todo.store');
   Route::put('update/{todo}', [TodoController::class, 'update'])->name('todo.update');
});

The next step is for you to learn how to implement file uploads with Drag and Drop in Laravel Inertia.

I agree to receive announcements of interest about this Blog.

We will know how to handle forms in Laravel Inertia with Vue, the basic flow to use a component in Vue and send the request to Laravel.

| 👤 Andrés Cruz

🇪🇸 En español