spatie/laravel-typescript-transformer
Convert PHP classes, enums, and more into TypeScript types automatically in Laravel. Mark PHP code with attributes, handle complex types and generics, and generate TS-friendly definitions (and even functions) to keep frontend types in sync.
Installation:
composer require spatie/laravel-typescript-transformer
php artisan vendor:publish --provider="Spatie\LaravelTypeScriptTransformer\TypeScriptTransformerServiceProvider"
This publishes the service provider stub to app/Providers/TypeScriptTransformerServiceProvider.php.
Basic Usage:
Add the #[TypeScript] attribute to any PHP class to generate TypeScript types:
use Spatie\LaravelTypeScriptTransformer\Attributes\TypeScript;
#[TypeScript]
class User {
public int $id;
public string $name;
}
Run the transformer:
php artisan typescript:transform
Output will be generated in resources/js/types.
First Use Case: Transform a DTO or model to TypeScript for frontend API contracts. Example:
#[TypeScript]
class CreateUserRequest {
public string $name;
public ?string $email = null;
public array $roles = [];
}
Generates:
export type CreateUserRequest = {
name: string;
email: string | null;
roles: string[];
}
Class/Enum Transformation:
#[TypeScript] to auto-generate types.#[TypeScript(name: "CustomName")].Controller/Route Generation:
#[TypeScript]
class UserController {
public function store(StoreUserRequest $request): UserResource { ... }
}
Outputs:
export type UserController = {
store: (request: StoreUserRequest) => UserResource;
};
Watch Mode: Enable real-time updates during development:
php artisan typescript:watch
Automatically regenerates types on file changes.
Custom Transformers: Extend default behavior by creating custom transformers:
use Spatie\TypeScriptTransformer\Transformers\Transformer;
class CustomTransformer extends Transformer {
public function transform($value): string {
return 'CustomType';
}
}
Register in TypeScriptTransformerServiceProvider.
Frontend Integration:
Place generated types in resources/js/types and import them in your frontend framework (Vue/React).
Example:
import type { User } from '@/types/User';
API Contracts: Use for OpenAPI/Swagger documentation or frontend validation (e.g., with Zod).
Laravel Collections: Transform collections to typed arrays:
#[TypeScript]
class UserCollection extends Collection {
public function toTypeScript(): string {
return $this->map(fn ($user) => $user->toTypeScript())->implode(', ');
}
}
Dynamic Types: Generate types for database queries or Eloquent models:
#[TypeScript]
class UserModel extends Model {
protected $fillable = ['name', 'email'];
}
Namespace Conflicts:
#[TypeScript(name: "CustomName")] to override names.#[TypeScript(name: "UserDto")]
class User { ... }
Circular Dependencies:
User referencing Profile which references User). Use #[TypeScript(ignore: true)] or flatten structures.PHP 8.2+ Requirement:
Watch Mode Quirks:
node_modules or vendor from watch patterns in typescript-transformer.php:
'watch' => [
'paths' => ['app/Models', 'app/Dtos'],
'ignore' => ['node_modules', 'vendor'],
],
Enum Handling:
Transformer for Spatie\TypeScriptTransformer\Transformers\EnumTransformer.Verbose Output: Enable debug mode in the service provider:
$this->app->singleton(TypeScriptTransformer::class, fn () => new TypeScriptTransformer(
debug: true
));
Logs transformation steps to storage/logs/laravel.log.
Type Mismatches: If types don’t match expectations, inspect the raw PHP structure with:
php artisan typescript:inspect App\Models\User
Cache Issues: Clear the TypeScript cache after changes:
php artisan typescript:clear
Custom Writers: Override the default file writer to output types to a custom location or format:
$this->app->singleton(Writer::class, fn () => new CustomWriter(
path: storage_path('app/types'),
formatter: new PrettierFormatter()
));
TypeScript Formatting: Integrate Prettier or ESLint for consistent formatting:
use Spatie\TypeScriptTransformer\Formatters\PrettierFormatter;
$this->app->singleton(Formatter::class, fn () => new PrettierFormatter());
Dynamic Type Generation: Generate types at runtime for dynamic queries:
$transformer = app(TypeScriptTransformer::class);
$type = $transformer->transform(new User());
Excluding Classes: Skip specific classes with:
#[TypeScript(ignore: true)]
class ExcludedClass { ... }
Or globally in the service provider:
'ignore' => [
'App\\Models\\Excluded*',
],
Handling Laravel-Specific Types: Customize transformations for Laravel collections, paginators, or Eloquent models by extending:
class LaravelCollectionTransformer extends Transformer {
public function transform(Collection $collection): string {
return 'Array<' . $collection->first()->toTypeScript() . '>';
}
}
Register in the service provider:
$this->app->singleton(Transformer::class, fn () => new LaravelCollectionTransformer());
TypeScript Interfaces vs. Types:
Use #[TypeScript(as: 'interface')] to generate interfaces instead of types:
#[TypeScript(as: 'interface')]
class User { ... }
Outputs:
export interface User { ... }
Union Types: Transform nullable or union properties automatically:
public ?string $name; // => string | null
public int|string $id; // => number | string
Generics Support: Handle generic classes:
#[TypeScript]
class ApiResponse<T> {
public T $data;
}
Outputs:
export type ApiResponse<T> = {
data: T;
};
Inline Types: Generate types inline for one-off usage:
$transformer = app(TypeScriptTransformer::class);
$type = $transformer->transform(new User());
echo "type UserInline = {$type};";
Testing: Mock the transformer in tests:
$transformer = Mockery::mock(TypeScriptTransformer::class);
$transformer->shouldReceive('transform')
->with(new User())
->andReturn('type User = { ... }');
How can I help you explore Laravel packages today?