spatie/typescript-transformer
Automatically generate TypeScript definitions from your PHP/Laravel code. spatie/typescript-transformer scans classes and types, then outputs .d.ts files so your frontend stays in sync with backend models, DTOs and enums with minimal manual typing.
composer require spatie/typescript-transformer
config/app.php:
Spatie\TypeScriptTransformer\TypeScriptTransformerServiceProvider::class,
Publish the config:
php artisan vendor:publish --provider="Spatie\TypeScriptTransformer\TypeScriptTransformerServiceProvider"
#[TypeScript] attribute to a PHP class:
#[TypeScript]
class User {
public int $id;
public string $name;
}
Run the Artisan command:
php artisan typescript:transform
Output will be generated in resources/js/types/ (default) as User.d.ts.config/typescript-transformer.php for default settings.AttributedClassTransformer for classes marked with #[TypeScript].output_directory in config (e.g., resources/js/types/).Annotated Classes:
Use #[TypeScript] on classes to auto-generate TypeScript types. Ideal for models, DTOs, or domain objects.
#[TypeScript]
class Post {
public int $id;
public string $title;
public ?Carbon $publishedAt;
}
Enums: Transform PHP enums to TypeScript union types:
enum Status { case PUBLISHED = 'published'; case DRAFT = 'draft'; }
Output:
export type Status = 'published' | 'draft';
Custom Type Replacements:
Replace complex types (e.g., Carbon) in config:
$config->replaceType(Carbon::class, 'string');
Laravel Eloquent Models:
Combine with #[TypeScript] and replace Carbon/DateTime to strings:
$config->replaceType(Carbon::class, 'string');
$config->transformer(new LaravelAttributedClassTransformer());
Generics:
Use #[TypeScript] on generic classes:
#[TypeScript]
class Collection<T> {
public array $items;
}
Artisan Command: Extend the default command for custom logic:
$config->transformer(new CustomTransformer());
CI/CD:
Add to deployment pipeline to auto-generate types on git push:
# .github/workflows/deploy.yml
- run: php artisan typescript:transform
Circular Dependencies:
Avoid circular references between transformed classes (e.g., ClassA references ClassB, which references ClassA).
Fix: Use #[TypeScript(ignore: true)] or refactor.
Unresolved Types:
Properties without type hints (e.g., public $name) default to any. Explicitly type them:
public string $name; // Correct
public $name; // Avoid (becomes `any`)
Namespace Collisions:
GlobalNamespaceWriter may cause conflicts if multiple packages generate types with the same namespace.
Fix: Use ModuleWriter for isolated output per directory.
Laravel-Specific Quirks:
string or custom types to avoid runtime issues.#[TypeScript] on custom collections, not Laravel’s base Collection.Verbose Output: Enable debug mode in config:
'debug' => true,
Run with:
php artisan typescript:transform --verbose
Dry Runs:
Use RunnerMode::DryRun to preview changes without writing files:
$runner->run(mode: RunnerMode::DryRun);
Transformer Order:
Transformers execute in registration order. Place AttributedClassTransformer first if using #[TypeScript].
Custom Transformers:
Implement Transformer interface for bespoke logic:
class CustomTransformer implements Transformer {
public function transform(ReflectionClass $reflectionClass, TransformationContext $context) {
if ($reflectionClass->isSubclassOf(MyCustomClass::class)) {
return new Transformed(new TypeScriptObject([...]));
}
return new Untransformable();
}
}
Dynamic Replacements: Use closures for context-aware replacements:
$config->replaceType(MyClass::class, function (TypeScriptReference $reference) {
return new TypeScriptString($reference->getName() . '_prefix');
});
Post-Processing:
Hook into TypeScriptTransformerConfigFactory to modify config dynamically:
$factory->modifyUsing(function (TypeScriptTransformerConfig $config) {
$config->outputDirectory(__DIR__ . '/custom-output');
});
Exclude Directories: Skip irrelevant paths (e.g., tests, vendors) in config:
$config->excludeDirectories([
app_path('Tests'),
vendor_path(),
]);
Cache: Enable caching for large projects:
$config->cacheDirectory(storage_path('framework/cache/typescript'));
How can I help you explore Laravel packages today?