Forms

Handle form submissions and validation with Inertia.

See also

Official Inertia.js docs: Forms

Form Submissions

Inertia handles forms via XHR. The client-side form helpers manage state, validation errors, and submissions.

Backend Handler

from litestar import post
from litestar.request import Request
from litestar_vite.inertia import InertiaRedirect

@post("/users")
async def create_user(request: Request, data: UserCreate) -> InertiaRedirect:
    # Validation errors are automatically available via Pydantic
    user = await User.create(**data.dict())
    return InertiaRedirect(request, f"/users/{user.id}")

Frontend Form

import { useForm } from "@inertiajs/react";

export default function CreateUser() {
  const { data, setData, post, processing, errors } = useForm({
    name: "",
    email: "",
  });

  function submit(e: React.FormEvent) {
    e.preventDefault();
    post("/users");
  }

  return (
    <form onSubmit={submit}>
      <input
        value={data.name}
        onChange={(e) => setData("name", e.target.value)}
      />
      {errors.name && <span>{errors.name}</span>}

      <input
        value={data.email}
        onChange={(e) => setData("email", e.target.value)}
      />
      {errors.email && <span>{errors.email}</span>}

      <button disabled={processing}>Create</button>
    </form>
  );
}
<script setup lang="ts">
import { useForm } from "@inertiajs/vue3";

const form = useForm({
  name: "",
  email: "",
});

function submit() {
  form.post("/users");
}
</script>

<template>
  <form @submit.prevent="submit">
    <input v-model="form.name" />
    <span v-if="form.errors.name">{{ form.errors.name }}</span>

    <input v-model="form.email" />
    <span v-if="form.errors.email">{{ form.errors.email }}</span>

    <button :disabled="form.processing">Create</button>
  </form>
</template>

Validation Errors

Set validation errors using the error() helper:

from litestar_vite.inertia import error, InertiaBack

@post("/users")
async def create_user(request: Request) -> InertiaBack:
    data = await request.json()

    # Custom validation
    if await User.email_exists(data["email"]):
        error(request, "email", "Email already exists")
        return InertiaBack(request)

    await User.create(**data)
    return InertiaRedirect(request, "/users")

Errors are available in the errors prop on the frontend.

Error Bags

Use error bags to scope validation errors:

# Backend: errors are scoped by the X-Inertia-Error-Bag header
# Frontend sends this header automatically when using form.setError()

Flash Messages

Add flash messages with the flash() helper:

from litestar_vite.inertia import flash, InertiaRedirect

@post("/users")
async def create_user(request: Request, data: UserCreate) -> InertiaRedirect:
    user = await User.create(**data.dict())
    flash(request, "User created successfully!", "success")
    return InertiaRedirect(request, f"/users/{user.id}")

Flash messages are available in the flash prop:

interface Props {
  flash: {
    success?: string[];
    error?: string[];
    info?: string[];
  };
}

export default function Layout({ flash, children }: Props) {
  return (
    <div>
      {flash.success?.map((msg) => (
        <div className="alert-success">{msg}</div>
      ))}
      {children}
    </div>
  );
}

File Uploads

Use FormData for file uploads on the frontend:

const { data, setData, post } = useForm({
  name: "",
  avatar: null as File | null,
});

function submit(e: React.FormEvent) {
  e.preventDefault();
  post("/users", {
    forceFormData: true,  // Required for file uploads
  });
}

See Also