Auto-Sync Laravel and TypeScript Types with Spatie Data Objects
The Spatie team has a way to stop syncing Laravel and TypeScript types by hand: spatie/laravel-data and spatie/laravel-typescript-transformer. PHP classes become the source of truth, and the TypeScript definitions are generated from them automatically. They use this approach in There There, the helpdesk they are building.
Here is the background. Spatie builds SaaS apps with Laravel on the backend and Inertia with React on the frontend, so two type systems have to agree on every payload that crosses between them. In There There, tickets, messages, members, and even custom sidebar sections all cross that bridge. Maintaining all those types manually would be maddening, so they automated it. There There itself is currently in private beta.
From Data class to TypeScript definition
A payload starts life as a Data class. Take MemberData, which is sent down every time the member detail page renders. It holds constructor properties such as id, ulid, name, email, a nullable avatar_url, a role that is the WorkspaceMemberRole enum, joined_at, and restricted_channel_access, which defaults to false. The #[TypeScript] attribute works as the opt-in marker: any PHP class that carries it gets picked up by the transformer.
The transformer is a regular Laravel package with a config file. Three parts are worth your attention:
- auto_discover_types scans the app/ folder for annotated classes.
- transformers handles data classes, DTOs, enums, and Spatie state machines.
- default_type_replacements swaps PHP types for their TypeScript equivalents. That is how every Carbon date becomes a plain string on the frontend.
The result is written to resources/js/types/generated.d.ts. For MemberData, the output is a type whose fields follow the constructor exactly, for example avatar_url typed as string | null. The namespace mirrors the PHP namespace, and enums are cross-referenced to their own generated types. So if you rename an enum case in PHP, the change propagates to every TypeScript caller.
Automatic type updates through Vite
You can regenerate the file manually with php artisan typescript:transform. The Spatie team chose to wire it into Vite through a small custom plugin. While the dev server is running and a PHP file under Data/ or Enums/ changes, the handleHotUpdate hook runs php artisan typescript:transform, and the frontend types update right away. No manual step, no stale definitions.
Version 3 of spatie/laravel-typescript-transformer offers a simpler option: a built-in watch mode. Once chokidar is installed, run php artisan typescript:transform --watch. Spatie is still on v2.6, which has no --watch option, so the Vite plugin handles the changes instead.
According to the Spatie team, this setup erased a whole category of small annoyances. When they rename a PHP property, the TypeScript compiler flags every frontend caller. When they add a field, it shows up in autocomplete within seconds. If you use Laravel with Inertia and React, the pattern is easy to copy, and if you are on v3, the built-in watch mode makes the setup even leaner.