diephp/laravel-resources-typescript
Installation
composer require diephp/laravel-resources-typescript
Publish the config (optional):
php artisan vendor:publish --provider="Diephp\LaravelResourcesTypescript\LaravelResourcesTypescriptServiceProvider" --tag="config"
Generate Types
Run the Artisan command to generate TypeScript interfaces in resources/ts (default):
php artisan resources:typescript
Or specify a custom output path:
php artisan resources:typescript --output=path/to/types
First Use Case
Define a Laravel JsonResource (e.g., app/Http/Resources/UserResource.php):
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'role' => $this->role,
];
}
}
Run the command, and the package generates a TypeScript interface like:
export interface UserResource {
id: number;
name: string;
email: string;
role: string;
}
Resource Definition
Use standard Laravel JsonResource classes with:
toArray() methods.public int $id).#[ArrayShape] annotations.Type Generation
php artisan resources:typescript in your dev environment.--force or --watch (if supported in future versions).Integration with Frontend
import { UserResource } from './path/to/generated/types';
interface ApiResponse {
data: UserResource[];
}
Enum Support
Define enums in PHP (e.g., app/Enums/UserRole.php):
namespace App\Enums;
enum UserRole: string {
case ADMIN = 'admin';
case USER = 'user';
}
Reference them in resources:
public function toArray($request): array {
return ['role' => $this->role->value];
}
The package generates:
export type UserRole = 'admin' | 'user';
export interface UserResource {
role: UserRole;
}
Customizing Output
Override default behavior via resources-typescript.php config:
return [
'output_path' => 'path/to/custom/types',
'namespace' => 'CustomNamespace',
'exclude' => [
'App\Http\Resources\ExcludedResource',
],
];
Unsafe Type Inference
any for ambiguous types (e.g., dynamic toArray() logic). Explicitly type properties or use PHPDoc to avoid this:
/**
* @return array{id: int, name: string}
*/
public function toArray($request): array { ... }
Circular Dependencies
UserResource includes PostResource), the generated types may fail. Use @mixin in TypeScript or refactor to avoid circularity.Enum Limitations
Cache Invalidation
--force to overwrite cached files:
php artisan resources:typescript --force
Verbose Output: Enable debug mode in config:
'debug' => true,
Run the command to see analyzed resources and generated types.
Manual Overrides: Override generated types in your frontend project to handle edge cases:
// Override a specific interface
interface UserResource {
customField?: string; // Not detected by the package
}
Custom Type Mappings
Extend the package by implementing a TypeMapper to handle custom PHP types (e.g., Carbon instances):
namespace App\Services;
use Diephp\LaravelResourcesTypescript\Contracts\TypeMapper;
class CarbonTypeMapper implements TypeMapper
{
public function getType(string $value): string
{
return 'string'; // Map Carbon to ISO string
}
}
Register in AppServiceProvider:
public function boot(): void
{
$this->app->bind(TypeMapper::class, CarbonTypeMapper::class);
}
Post-Processing
Use a build tool (e.g., Vite, Webpack) to transform generated types. Example: Add readonly modifiers or rename fields:
// vite.config.ts
import { defineConfig } from 'vite';
import ts from '@rollup/plugin-typescript';
export default defineConfig({
plugins: [
ts({
transform: (code) => {
return code.replace(
/interface (\w+)/g,
'interface $1 { readonly '
);
},
}),
],
});
Testing Test type generation by mocking resources and asserting output:
use Diephp\LaravelResourcesTypescript\TypeGenerator;
public function test_type_generation()
{
$generator = new TypeGenerator();
$resource = new UserResource(new User());
$type = $generator->generate($resource);
$this->assertStringContainsString('interface UserResource', $type);
}
How can I help you explore Laravel packages today?