laravel-typescript-generator maintained by synergitech
Laravel TypeScript Generator
Generate TypeScript type definitions from your Laravel Eloquent models automatically. Uses both database schema introspection and model metadata ($casts, $timestamps, etc.) to produce accurate types.
Version Support
| Laravel | PHP | Supported |
|---|---|---|
| 13.x | ^8.3 | ✓ |
| 12.x | ^8.2 | ✓ |
| 11.x | ^8.2 | ✓ |
| 10.x | ^8.1 | ✓ |
Features
- Schema introspection — reads your actual database columns, types, and nullability
- Cast-aware — Laravel
$castsoverride the raw DB type (e.g. ajsoncolumn cast toarraybecomesunknown[]) - Relationship support — optionally include
hasMany,belongsTo, etc. as typed properties - API Resource support — optionally generate types from
JsonResourceclasses (what your API actually returns) - Per-model files — generates one
.d.tsper model plus a barrelindex.d.ts - Configurable — nullable style (
| nullvs?), excluded models, manual type overrides
Installation
composer require synergitech/laravel-typescript-generator
The service provider is auto-discovered by Laravel. No manual registration needed.
Publish the config (optional)
php artisan vendor:publish --tag=typescript-generator-config
This creates config/typescript-generator.php where you can customise everything.
Usage
Basic generation
php artisan types:generate
This scans App\Models, introspects each model's database table, and writes .d.ts files to resources/js/types/.
With relationships
php artisan types:generate --with-relationships
With API resources
php artisan types:generate --with-resources
Resources are off by default. Pass --with-resources (or set include_resources: true in config) to scan App\Http\Resources and generate a .d.ts per resource in resources/js/types/resources/.
Dry run (preview without writing)
php artisan types:generate --dry-run
Example Output
Given a User model with this table:
| Column | Type | Nullable |
|---|---|---|
| id | bigint | no |
| name | varchar(255) | no |
| varchar(255) | no | |
| is_admin | boolean | no |
| metadata | json | yes |
| created_at | timestamp | yes |
| updated_at | timestamp | yes |
And these casts:
protected $casts = [
'is_admin' => 'boolean',
'metadata' => 'array',
];
The generator produces resources/js/types/User.d.ts:
// Auto-generated by laravel-typescript-generator
// Model: App\Models\User
// Generated at: 2026-04-08T12:00:00+00:00
export interface User {
/** DB: bigint */
id: number;
/** DB: varchar | Cast: string */
name: string;
/** DB: varchar | Cast: string */
email: string;
/** DB: boolean | Cast: boolean */
is_admin: boolean;
/** DB: json | Cast: array | nullable */
metadata: unknown[] | null;
/** DB: timestamp | nullable */
created_at: string | null;
/** DB: timestamp | nullable */
updated_at: string | null;
}
Resource output
Given a UserResource that returns:
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'role' => $this->when($request->user()?->isAdmin(), $this->role),
'address' => new AddressResource($this->address),
];
}
The generator produces resources/js/types/resources/UserResource.d.ts:
// Auto-generated by laravel-typescript-generator
// Resource: App\Http\Resources\UserResource
import type { AddressResource } from './AddressResource';
export interface UserResource {
id: unknown | null;
name: unknown | null;
email: unknown | null;
role?: unknown;
address: AddressResource;
}
Fields from $this->when(...) whose condition is false at generation time are marked optional (?). Fields whose runtime value is null (empty model attributes) are typed unknown | null. You can refine these with type_overrides in config if needed.
With --with-relationships, if User hasMany Post:
export interface User {
// ... attributes ...
// Relationships
posts?: Post[];
}
Configuration
| Key | Default | Description |
|---|---|---|
model_namespace |
App\Models |
Namespace to scan for models |
model_directory |
app/Models |
Directory path corresponding to the namespace |
output_directory |
resources/js/types/models |
Where model .d.ts files are written |
include_relationships |
false |
Include relationships by default (overridden by CLI flags) |
include_timestamps |
false |
Force-include timestamp columns even if model has $timestamps = false |
nullable_style |
union |
'union' → type | null, 'optional' → type? |
excluded_models |
[] |
FQCNs of models to skip |
type_overrides |
[] |
Manual TS type overrides per model per column |
resource_namespace |
App\Http\Resources |
Namespace to scan for API resources |
resource_directory |
app/Http/Resources |
Directory path corresponding to the resource namespace |
resource_output_directory |
resources/js/types/resources |
Where resource .d.ts files are written |
include_resources |
false |
Generate resource types by default (overridden by CLI flags) |
excluded_resources |
[] |
FQCNs of resources to skip |
Type Overrides Example
// config/typescript-generator.php
'type_overrides' => [
App\Models\User::class => [
'metadata' => '{ avatar: string; theme: "light" | "dark" }',
'role' => '"admin" | "editor" | "viewer"',
],
],
How Type Resolution Works
For each column the generator applies this priority:
- Manual override (
type_overridesin config) — highest priority - Laravel cast (
$castson the model) — takes precedence over raw DB type - Database column type (via schema introspection) — fallback
This means your casts are always respected. If you cast a json column to array, the TS type will be unknown[], not Record<string, unknown>.
Tip: Add to Your Workflow
Add it to your composer.json scripts so types regenerate after migrations:
{
"scripts": {
"post-migrate": [
"php artisan types:generate"
]
}
}
Or add it to a git pre-commit hook or CI pipeline to keep types in sync.
License
MIT