based/laravel-typescript
based/laravel-typescript generates TypeScript definitions from Laravel models, resources, and routes, keeping frontend and backend types in sync via artisan commands and configurable output.
based/laravel-typescript is a small code-generation utility: it reflects on your Eloquent models and emits TypeScript interfaces so your frontend can share the backend's shape instead of duplicating it by hand.
Minimal setup:
composer require based/laravel-typescript
php artisan vendor:publish --tag=typescript # confirm the tag/provider from the publish list
Then open the published config/typescript.php. This is the first file to read — it controls the output directory (typically something like resources/js/types) and which models are transformed. Register a model, run the transform command, and inspect the generated file:
php artisan typescript:transform
The fastest first use case is a single User model consumed by an Inertia or API-backed page:
import type { User } from '@/types'
Once that import type-checks, expand registration model by model rather than pointing the package at your entire app/Models directory. Where to look next: the package's src/ transformers to understand how casts and relations are interpreted, and the generated output itself to see what was inferred versus omitted.
Treat generated interfaces as build artifacts. Decide up front whether the output directory is committed or generated during npm run dev / CI. Committing gives reviewers a visible schema diff; generating avoids merge conflicts. Either way, never hand-edit generated files.
Wire generation into the frontend build. A typical package.json:
"scripts": {
"types": "php artisan typescript:transform",
"dev": "npm run types && vite"
}
For CI, regenerate and fail on drift:
- run: php artisan typescript:transform
- run: git diff --exit-code resources/js/types
Let casts drive the types. The quality of the output depends on how explicit your models are. Set $casts for dates, decimals, JSON, and enums; the transformer has much less to infer otherwise. If a column is nullable or an accessor can return null, make that explicit in the model or correct it in a wrapper type.
Layer hand-written types on top. Generated interfaces describe persistence, not page props or API envelopes. Keep a small types/app.ts that composes them:
import type { User } from './generated'
export type UserForm = Pick<User, 'name' | 'email'>
export type Paginated<T> = { data: T[]; links: Record<string, string> }
Handle relations deliberately. Nested interfaces are useful but circular relations (User → Post → User) are a common source of broken or recursive output. Prefer generating flat interfaces and composing relations in hand-written types, or break cycles at the transformer/config level.
Use literal unions for enums and statuses. If your model casts to a PHP enum, make sure the frontend gets a union ('draft' | 'published') rather than string. If the package version doesn't emit that automatically, maintain a small mapping and reference it from your wrapper types.
Type Inertia props and API resources from the same interfaces. The main day-to-day payoff is that a controller change that alters a model's shape produces a TypeScript error at the component or client call site — provided the generation step runs before type-checking.
$appends accessors, computed properties, and custom Attribute casts often won't appear or will appear with the wrong type. Generated interfaces reflect what the package can see, not what your API actually returns.string instead of string | null will pass TypeScript but fail at runtime. Audit generated output for null-able fields after the first run.App\Models\User and Admin\Models\User both want to emit User. Namespace or rename via config before this becomes a debugging session.$casts and relation methods through the transformer source. There's no verbose mode to rely on.How can I help you explore Laravel packages today?