synergitech / laravel-typescript-generator
Generate TypeScript type definitions from Laravel Eloquent models via schema introspection and model metadata.
Package info
github.com/SynergiTech/laravel-typescript-generator
pkg:composer/synergitech/laravel-typescript-generator
Requires
- php: ^8.1
- illuminate/console: ^10.0|^11.0|^12.0|^13.0
- illuminate/database: ^10.0|^11.0|^12.0|^13.0
- illuminate/filesystem: ^10.0|^11.0|^12.0|^13.0
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- phpunit/phpunit: ^10.5|^11.0
This package is auto-updated.
Last update: 2026-04-21 10:30:21 UTC
README
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