Inertia.js hands data from Laravel to React as page props — and in plain JavaScript, nothing verifies that the props your component expects are the props the controller sends. TypeScript closes that gap: the page props contract becomes explicit, refactors stop being scary, and editors autocomplete props the way they autocomplete everything else. Here is the setup we use.
- If Inertia is new to you: Inertia.js with Laravel and React — SPA without a separate API.
The PageProps contract
Start with a single source of truth for page props
// resources/js/types/inertia.d.ts
import '@inertiajs/core';
export interface SharedProps {
auth: {
user: { id: number; name: string; email: string } | null;
};
flash: { success?: string; error?: string };
[key: string]: unknown;
}
declare module '@inertiajs/core' {
interface PageProps extends SharedProps {}
}
Then each page declares its own props
// resources/js/Pages/Orders/Index.tsx
import { Head } from '@inertiajs/react';
import type { SharedProps } from '@/types/inertia';
interface Order {
id: number;
number: string;
total: number;
status: 'new' | 'paid' | 'shipped';
}
interface Props extends SharedProps {
orders: { data: Order[]; links: { url: string | null; label: string }[] };
filters: { status?: string; search?: string };
}
export default function OrdersIndex({ orders, filters, auth }: Props) {
return (
<>
<Head title="Orders" />
<h1>Orders for {auth.user?.name}</h1>
{/* ... */}
</>
);
}
The Laravel side needs no annotations — but the resource is the natural place to document the payload
class OrderResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'number' => $this->number,
'total' => (float) $this->total,
'status' => $this->status,
];
}
}
Tip: Keep prop interfaces next to the page component, not in a global types folder. The contract belongs to the page — colocation makes drift visible in code review.
Typing useForm
useForm accepts a generic — pass the shape of your form data
interface CreateOrderForm {
customer_id: number | '';
note: string;
}
const { data, setData, post, errors } = useForm<CreateOrderForm>({
customer_id: '',
note: '',
});
Now setData('custmer_id', ...) is a compile error, and errors is keyed by your field names. Same story for usePage<SharedProps>() when reading shared data outside a page component.
Generating types from Laravel
For enums and value objects, hand-maintaining TS twins gets old fast. We generate them
composer require spatie/laravel-typescript-transformer
php artisan typescript:transform
Generated enum unions and resource types land in resources/js/types/generated.d.ts and are committed — CI fails if they drift from the PHP source.
Making it stick in CI
npm run typecheck # tsc --noEmit
Three habits make typing pay off in real projects
- Strict mode on.
"strict": truein tsconfig — otherwise half the value evaporates. - Typecheck in CI. A failing
tsc --noEmitblocks the build; prop drift never reaches production. - Type the seam, not everything. Page props, shared props, form data and API resources — the boundaries. Internal component props can stay light.
What it buys you
In an Inertia codebase the props seam is where bugs live: a controller renames a key, a resource changes a shape, a page silently renders undefined. TypeScript turns those into red squiggles at commit time. Combined with form validation patterns and testing, it rounds out a setup where the compiler and the test suite catch what code review misses.
We set up typed Inertia stacks as a default on new projects — see Laravel development services.
FAQ
Can TypeScript types be generated from Laravel?
Yes — spatie/laravel-typescript-transformer or similar generates TS definitions from resources and enums. We generate enums and payload types, and hand-write page prop interfaces.
Does TypeScript slow down Inertia.js development?
Setup costs an afternoon; afterwards typing props prevents the most common Inertia bug — components reading props that no longer exist.
Is TypeScript worth it for a small project?
If it has more than a handful of pages and will outlive a prototype — yes. One-time cost, compounding benefit.
👉 Want a typed, refactor-safe Inertia.js codebase? Check our Laravel development services or hire a Laravel developer.
📖 Read more: Inertia.js with Laravel and React, forms and validation in Inertia.js and testing Inertia.js applications.