synergitech/laravel-typescript-generator

Generate TypeScript type definitions from Laravel Eloquent models via schema introspection and model metadata.

Maintainers

Package info

github.com/SynergiTech/laravel-typescript-generator

pkg:composer/synergitech/laravel-typescript-generator

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-04-21 09:58 UTC

This package is auto-updated.

Last update: 2026-04-21 10:30:21 UTC


README

Tests

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 $casts override the raw DB type (e.g. a json column cast to array becomes unknown[])
  • Relationship support — optionally include hasMany, belongsTo, etc. as typed properties
  • API Resource support — optionally generate types from JsonResource classes (what your API actually returns)
  • Per-model files — generates one .d.ts per model plus a barrel index.d.ts
  • Configurable — nullable style (| null vs ?), 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
email 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:

  1. Manual override (type_overrides in config) — highest priority
  2. Laravel cast ($casts on the model) — takes precedence over raw DB type
  3. 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