campelo/laravel-typescript-models
最新稳定版本:v1.7.0
Composer 安装命令:
composer require campelo/laravel-typescript-models
包简介
Automatically generate TypeScript interfaces from your Laravel Eloquent models via API endpoint
README 文档
README
Automatically generate TypeScript interfaces from your Laravel Eloquent models, API Resources, and Form Requests via an API endpoint or CLI command.
Features
- API Resources (Recommended): Generate interfaces from JsonResource classes as source of truth
- Models: Generate TypeScript interfaces from Eloquent models (opt-in)
- Form Requests: Generate interfaces from FormRequest validation rules
- Yup Schemas: Generate Yup validation schemas from Form Requests
- Zod Schemas: Generate Zod validation schemas from Form Requests
- Intelligent Type Inference: Eliminate
anytypes using PHPDoc, static analysis, and Model casts - Split by Domain: Generate separate files per module (
users.ts,orders.ts) - Union Literal Types: Enum-like types from
in:rules ('admin' | 'user' | 'guest') - Pagination Types: Auto-generated
PaginatedResponse<T>generic type - Array Types: Auto-generated array types (
Users,Posts, etc.) - Smart Detection: Multiple strategies for type detection (return type, PHPDoc, method body)
- Conflict Resolution: Automatic name prefixing when classes have the same name in different folders
- CLI Command:
php artisan typescript:generatefor CI/CD integration - Web Configurator: Interactive HTML page to customize and download types
- Security: Token authentication, IP whitelist, disabled by default
Installation
composer require campelo/laravel-typescript-models
The package will automatically register its service provider.
Configuration
Publish the configuration file:
php artisan vendor:publish --tag=typescript-models-config
Environment Variables
# Enable the endpoint (disable in production!) TYPESCRIPT_MODELS_ENABLED=true # Secret token for authentication TYPESCRIPT_MODELS_TOKEN=your-secret-token-here # Optional: Custom route path TYPESCRIPT_MODELS_ROUTE=/api/typescript-models # Optional: Allowed IPs (comma-separated) TYPESCRIPT_MODELS_ALLOWED_IPS=127.0.0.1,::1 # Optional: Properties mode (fillable, database, or both) TYPESCRIPT_MODELS_PROPERTIES_MODE=fillable # Optional: Include/exclude features TYPESCRIPT_MODELS_INCLUDE_MODELS=false # Models disabled by default (use Resources!) TYPESCRIPT_MODELS_INCLUDE_ACCESSORS=false TYPESCRIPT_MODELS_INCLUDE_RELATIONS=true TYPESCRIPT_MODELS_INCLUDE_RESOURCES=true TYPESCRIPT_MODELS_INCLUDE_REQUESTS=true TYPESCRIPT_MODELS_GENERATE_YUP_SCHEMAS=true TYPESCRIPT_MODELS_GENERATE_ZOD_SCHEMAS=false # Type inference for Resources (eliminates 'any' types) TYPESCRIPT_MODELS_INFER_TYPES=true TYPESCRIPT_MODELS_UNKNOWN_FALLBACK=unknown # 'unknown' | 'any' | 'never' # Split output by domain TYPESCRIPT_MODELS_SPLIT_BY_DOMAIN=false # 'subdirectory' | 'class' | false TYPESCRIPT_MODELS_DOMAIN_DETECTION=subdirectory
Usage
CLI Command (Recommended)
Generate TypeScript interfaces directly from the command line:
# Generate with default options (Resources + Requests, no Models) php artisan typescript:generate # Specify output file php artisan typescript:generate --output=resources/js/types/api.d.ts # Control what to generate with --only and --include php artisan typescript:generate --only=resources # Only resources php artisan typescript:generate --only=resources,requests # Resources + Requests php artisan typescript:generate --include=models # Add models to default # Legacy flags (still supported) php artisan typescript:generate --models # Only models php artisan typescript:generate --resources # Only resources php artisan typescript:generate --requests # Only requests # Choose validation schema library php artisan typescript:generate --yup # Generate Yup schemas php artisan typescript:generate --zod # Generate Zod schemas php artisan typescript:generate --yup --zod # Generate both # Split by domain (generates multiple files) php artisan typescript:generate --split-by=subdirectory --output=resources/types/ # Creates: users.ts, orders.ts, _shared.ts, index.ts # Exclude pagination/array types php artisan typescript:generate --no-paginated --no-array-types
Web Configurator
Access the interactive configurator page to customize and download your types:
http://localhost/api/typescript-models/configurator?token=your-secret-token
Features:
- Toggle models, resources, requests
- Enable/disable relations and accessors
- Choose between Yup and Zod schemas
- Preview before download
- Copy generated URL for CI/CD
API Endpoint
# Using token in header curl -H "X-TypeScript-Token: your-secret-token" http://localhost/api/typescript-models # Using Bearer token curl -H "Authorization: Bearer your-secret-token" http://localhost/api/typescript-models # Using query parameter curl "http://localhost/api/typescript-models?token=your-secret-token" # With custom options curl "http://localhost/api/typescript-models?token=your-token&resources=1&requests=1&yup=1&zod=0"
Integrating with Your Frontend
// package.json { "scripts": { "types:generate": "curl -H 'X-TypeScript-Token: your-token' http://localhost/api/typescript-models > src/types/models.d.ts" } }
Resources vs Models: Best Practices
Resources are the recommended source of truth for frontend types because:
- They represent the actual API response shape
- They hide internal fields that shouldn't be exposed
- Multiple Resources can exist for the same Model (summary, detail, admin)
By default, Models are disabled (include_models=false). Enable them only if needed:
# Default: Only Resources + Requests php artisan typescript:generate # Include Models for admin panels or internal tools php artisan typescript:generate --include=models # Or via config TYPESCRIPT_MODELS_INCLUDE_MODELS=true
Split by Domain
For large codebases, split generated types into separate files:
php artisan typescript:generate --split-by=subdirectory --output=resources/types/
Directory Structure
Given this Resource structure:
app/Http/Resources/
├── Users/
│ ├── UserResource.php
│ └── UserSummaryResource.php
├── Orders/
│ └── OrderResource.php
└── ProductResource.php (no subdirectory)
Generates:
resources/types/
├── _shared.ts # PaginatedResponse, PaginationLink
├── index.ts # Re-exports everything
├── users.ts # UserResource, UserSummaryResource
├── orders.ts # OrderResource
└── default.ts # ProductResource (no subdirectory)
Generated Files
_shared.ts
export interface PaginatedResponse<T> { ... } export interface PaginationLink { ... }
users.ts
import type { PaginatedResponse } from './_shared'; export interface UserResource { ... } export interface UserSummaryResource { ... } export type UserResources = UserResource[]; export type UserResourcesPaginated = PaginatedResponse<UserResource>;
index.ts
export * from './_shared'; export * from './users'; export * from './orders'; export * from './default';
Detection Modes
| Mode | Description | Example |
|---|---|---|
subdirectory |
Uses namespace subdirectory | Resources\Users\ → users.ts |
class |
Groups by matching Model names | UserResource, StoreUserRequest → user.ts |
Class Mode (Entity Matching)
The class mode intelligently groups interfaces by matching against your discovered Models:
php artisan typescript:generate --split-by=class --output=resources/types/
How it works:
- Discovers all your Eloquent Models (
User,Post,PostLike, etc.) - For each Resource/Request, removes suffixes (
Resource,Request) and HTTP verb prefixes (Store,Update,Delete,Index,Show,Create,Destroy) - Matches the clean name against Model names (exact match or plural match)
Examples:
| Class | Clean Name | Matched Model | Output File |
|---|---|---|---|
UserResource |
User |
User |
user.ts |
StoreUserRequest |
User |
User |
user.ts |
DeletePostLikeRequest |
PostLike |
PostLike |
post_like.ts |
PostLikesResource |
PostLikes |
PostLike (plural) |
post_like.ts |
TripUserResource |
TripUser |
TripUser |
trip_user.ts |
Note: The matching is exact to avoid conflicts. For example, with Models
User,Trip, andTripUser, the classTripUserRequestwill correctly matchTripUser(notUserorTrip).
1. Model Interfaces
Generate TypeScript interfaces from your Eloquent models.
Laravel Model
// app/Models/User.php class User extends Model { protected $fillable = ['name', 'email', 'birth_date']; protected $casts = [ 'birth_date' => 'date', ]; public function posts(): HasMany { return $this->hasMany(Post::class); } public function profile(): HasOne { return $this->hasOne(Profile::class); } }
Generated TypeScript
// Model Interfaces export interface User { id: number; name?: string; email?: string; birth_date?: Date; created_at?: Date; updated_at?: Date; posts?: Post[]; profile?: Profile; } export interface Post { id: number; title?: string; content?: string; user_id?: number; created_at?: Date; updated_at?: Date; user?: User; } // Model Array Types export type Users = User[]; export type Posts = Post[]; // Model Paginated Types export type UsersPaginated = PaginatedResponse<User>; export type PostsPaginated = PaginatedResponse<Post>;
Relationship Detection
The package uses 3 strategies to detect relationships:
- Return Type (Recommended)
public function posts(): HasMany { return $this->hasMany(Post::class); }
- PHPDoc Annotation
/** @return HasMany */ public function posts() { return $this->hasMany(Post::class); }
- Method Body Analysis
public function posts() { return $this->hasMany(Post::class); // Auto-detected! }
Supported Relationships
| Relationship | TypeScript Type |
|---|---|
HasOne, BelongsTo, MorphOne, MorphTo, HasOneThrough |
RelatedModel |
HasMany, BelongsToMany, MorphMany, MorphToMany, HasManyThrough |
RelatedModel[] |
2. API Resource Interfaces
Generate interfaces from your API Resources. Useful when your API response differs from the model structure.
Laravel Resources
// app/Http/Resources/UserResource.php class UserResource extends JsonResource { public function toArray($request) { return [ 'id' => $this->id, 'full_name' => $this->name, 'email' => $this->email, 'avatar_url' => $this->getAvatarUrl(), 'member_since' => $this->created_at->format('Y-m-d'), ]; } } // app/Http/Resources/UserSummaryResource.php class UserSummaryResource extends JsonResource { public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, ]; } } // app/Http/Resources/UserProfileResource.php class UserProfileResource extends JsonResource { public function toArray($request) { return [ 'id' => $this->id, 'full_name' => $this->name, 'email' => $this->email, 'bio' => $this->profile->bio, 'posts_count' => $this->posts->count(), ]; } }
Generated TypeScript (with Intelligent Type Inference)
// Resource Interfaces - No more 'any' types! export interface UserResource { avatar_url?: string; // Inferred from name pattern (*_url) email?: string; // Inferred from name pattern full_name?: string; // Inferred from name pattern id?: number; // Inferred from name pattern member_since?: string; // Inferred from ->format() call } export interface UserSummaryResource { id?: number; name?: string; } export interface UserProfileResource { bio?: string; email?: string; full_name?: string; id?: number; posts_count?: number; // Inferred from name pattern (*_count) } // Resource Array Types export type UserResources = UserResource[]; export type UserSummaryResources = UserSummaryResource[]; export type UserProfileResources = UserProfileResource[]; // Resource Paginated Types export type UserResourcesPaginated = PaginatedResponse<UserResource>; export type UserSummaryResourcesPaginated = PaginatedResponse<UserSummaryResource>; export type UserProfileResourcesPaginated = PaginatedResponse<UserProfileResource>;
PHPDoc Type Hints (Recommended)
For full control over types, use PHPDoc annotations:
/** * @property int $id * @property string $full_name * @property string $email * @property string $avatar_url * @property PostResource[] $posts */ class UserResource extends JsonResource { public function toArray($request) { return [ 'id' => $this->id, 'full_name' => $this->name, 'email' => $this->email, 'avatar_url' => $this->getAvatarUrl(), 'posts' => PostResource::collection($this->posts), ]; } }
How Type Inference Works
The package uses multiple strategies to determine types (in priority order):
- PHPDoc
@property: Highest priority, most reliable - Static Analysis: Detects patterns in
toArray():PostResource::collection(...)→PostResource[]new UserResource(...)→UserResource(bool) $value→boolean->format('Y-m-d')→string
- Model Casts: Inherits types from the underlying Model
- Name Patterns:
*_id,id→number*_at,*_date→stringis_*,has_*,can_*→boolean*_count→number*_url,email,name→string
- Fallback:
unknown(configurable viaunknown_type_fallback)
3. Form Request Interfaces
Generate interfaces from your Form Request validation rules. Perfect for typing your frontend forms.
Laravel Form Requests
// app/Http/Requests/StoreUserRequest.php class StoreUserRequest extends FormRequest { public function rules(): array { return [ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users', 'password' => 'required|string|min:8|confirmed', 'age' => 'nullable|integer|min:18', 'roles' => 'required|array', 'roles.*' => 'exists:roles,id', 'avatar' => 'nullable|image|max:2048', ]; } } // app/Http/Requests/UpdateUserRequest.php class UpdateUserRequest extends FormRequest { public function rules(): array { return [ 'name' => 'sometimes|string|max:255', 'email' => 'sometimes|email', 'bio' => 'nullable|string|max:1000', ]; } }
Generated TypeScript
// Form Request Interfaces export interface StoreUserRequest { age?: number; avatar?: File; email: string; name: string; password: string; roles: any[]; } export interface UpdateUserRequest { bio?: string; email?: string; name?: string; }
Type Mapping from Validation Rules
| Laravel Rule | TypeScript Type |
|---|---|
integer, numeric |
number |
boolean, accepted |
boolean |
array |
any[] |
file, image, mimes |
File |
json |
Record<string, any> |
string, email, url, uuid, date |
string |
required |
Required field (no ?) |
nullable, sometimes |
Optional field (?) |
Conflict Resolution
When you have requests with the same name in different folders, the package automatically adds a prefix:
app/Http/Requests/
├── StoreUserRequest.php → StoreUserRequest
├── UpdateUserRequest.php → UpdateUserRequest
└── Admin/
└── StoreUserRequest.php → AdminStoreUserRequest
└── Api/
└── V1/
└── StoreUserRequest.php → AdminApiV1StoreUserRequest
4. Yup Validation Schemas
Generate Yup schemas from your Form Requests for client-side validation.
Laravel Form Request
class StoreUserRequest extends FormRequest { public function rules(): array { return [ 'name' => 'required|string|min:2|max:255', 'email' => 'required|email', 'password' => 'required|string|min:8|confirmed', 'age' => 'nullable|integer|min:18|max:120', 'website' => 'nullable|url', 'role' => 'required|in:admin,user,guest', ]; } }
Generated Yup Schema
// Yup Validation Schemas // Usage: import * as yup from 'yup'; export const StoreUserRequestSchema = yup.object({ name: yup.string().required('This field is required').min(2, 'Must be at least 2 characters').max(255, 'Must be at most 255 characters'), email: yup.string().required('This field is required').email('Invalid email address'), password: yup.string().required('This field is required').min(8, 'Must be at least 8 characters').oneOf([yup.ref('password_confirmation')], 'Must match confirmation'), age: yup.number().nullable().min(18, 'Must be at least 18').max(120, 'Must be at most 120'), website: yup.string().nullable().url('Invalid URL'), role: yup.string().required('This field is required').oneOf(['admin', 'user', 'guest'], 'Invalid value'), });
Using in React/Vue
// React example with react-hook-form import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { StoreUserRequest, StoreUserRequestSchema } from '@/types/models'; function UserForm() { const { register, handleSubmit, formState: { errors } } = useForm<StoreUserRequest>({ resolver: yupResolver(StoreUserRequestSchema) }); const onSubmit = (data: StoreUserRequest) => { // data is typed! api.post('/users', data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('name')} /> {errors.name && <span>{errors.name.message}</span>} {/* ... */} </form> ); }
Supported Yup Validations
| Laravel Rule | Yup Method |
|---|---|
required |
.required() |
nullable |
.nullable() |
email |
.email() |
url |
.url() |
uuid |
.uuid() |
min:n |
.min(n) |
max:n |
.max(n) |
between:a,b |
.min(a).max(b) |
size:n |
.length(n) |
in:a,b,c |
.oneOf(['a','b','c']) |
confirmed |
.oneOf([yup.ref('field_confirmation')]) |
integer |
.integer() |
regex |
.matches() |
5. Zod Validation Schemas
Generate Zod schemas from your Form Requests. Zod is a TypeScript-first schema validation library.
Enable Zod Schemas
TYPESCRIPT_MODELS_GENERATE_ZOD_SCHEMAS=true
Laravel Form Request
class StoreUserRequest extends FormRequest { public function rules(): array { return [ 'name' => 'required|string|min:2|max:255', 'email' => 'required|email', 'role' => 'required|in:admin,user,guest', 'age' => 'nullable|integer|min:18', ]; } }
Generated Zod Schema
// Zod Validation Schemas // Usage: import { z } from 'zod'; export const StoreUserRequestSchema = z.object({ name: z.string().min(2, { message: 'Must be at least 2 characters' }).max(255, { message: 'Must be at most 255 characters' }), email: z.string().email({ message: 'Invalid email address' }), role: z.enum(['admin', 'user', 'guest']), age: z.number().min(18, { message: 'Must be at least 18' }).nullable().optional(), });
Using in React
import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { StoreUserRequestSchema } from '@/types/models'; type StoreUserRequest = z.infer<typeof StoreUserRequestSchema>; function UserForm() { const { register, handleSubmit, formState: { errors } } = useForm<StoreUserRequest>({ resolver: zodResolver(StoreUserRequestSchema) }); // ... }
6. Enum Union Literal Types
The package automatically generates TypeScript union literal types from in: validation rules and Rule::in():
Laravel Validation
// Using string rule 'role' => 'required|in:admin,user,guest', // Using Rule::in() 'status' => ['required', Rule::in(['pending', 'approved', 'rejected'])], // Using Enum rule (Laravel 9+) 'type' => ['required', Rule::enum(OrderType::class)],
Generated TypeScript
export interface StoreOrderRequest { role: 'admin' | 'user' | 'guest'; status: 'pending' | 'approved' | 'rejected'; type: 'delivery' | 'pickup' | 'dine_in'; }
This provides much stronger typing than generic string types!
7. Pagination Types
Auto-generated generic pagination interfaces:
export interface PaginationLink { url: string | null; label: string; active: boolean; } export interface PaginatedResponse<T> { data: T[]; current_page: number; first_page_url: string; from: number | null; last_page: number; last_page_url: string; links: PaginationLink[]; next_page_url: string | null; path: string; per_page: number; prev_page_url: string | null; to: number | null; total: number; }
Usage
// Fetch paginated users const response = await api.get<UsersPaginated>('/users'); console.log(response.data); // User[] console.log(response.total); // number console.log(response.current_page); // number
Configuration Options
Models
'include_models' => false, // Disabled by default (use Resources!) 'models_paths' => [ app_path('Models'), ], 'exclude_models' => [ // App\Models\SomeModel::class, ], 'properties_mode' => 'fillable', // fillable, database, or both 'include_accessors' => false, 'include_relations' => true,
Resources
'include_resources' => true, 'resources_paths' => [ app_path('Http/Resources'), ], 'exclude_resources' => [ // App\Http\Resources\SomeResource::class, ], // Type inference to eliminate 'any' types 'infer_resource_types' => true, // What to use when type cannot be inferred 'unknown_type_fallback' => 'unknown', // 'unknown' | 'any' | 'never'
Form Requests
'include_requests' => true, 'requests_paths' => [ app_path('Http/Requests'), ], 'exclude_requests' => [ // App\Http\Requests\SomeRequest::class, ], 'generate_yup_schemas' => true, 'generate_zod_schemas' => false,
Split by Domain
// Split output into multiple files 'split_by_domain' => false, // 'subdirectory' | 'class' | false // How to detect domain from class namespace 'domain_detection' => 'subdirectory', // 'subdirectory' | 'class_basename'
Complete Output Example
// ============================================================================= // Auto-generated TypeScript interfaces from Laravel Models // Generated at: 2024-01-15T10:30:00+00:00 // Do not edit this file manually - it will be overwritten // ============================================================================= // Pagination Interfaces export interface PaginationLink { url: string | null; label: string; active: boolean; } export interface PaginatedResponse<T> { data: T[]; current_page: number; // ... other pagination fields } // Resource Interfaces (with intelligent type inference - no 'any'!) export interface UserResource { id?: number; full_name?: string; avatar_url?: string; posts?: PostResource[]; } export interface UserSummaryResource { id?: number; name?: string; } // Resource Array Types export type UserResources = UserResource[]; export type UserSummaryResources = UserSummaryResource[]; // Resource Paginated Types export type UserResourcesPaginated = PaginatedResponse<UserResource>; export type UserSummaryResourcesPaginated = PaginatedResponse<UserSummaryResource>; // Form Request Interfaces export interface StoreUserRequest { email: string; name: string; password: string; age?: number; } export interface UpdateUserRequest { email?: string; name?: string; } // Yup Validation Schemas // Usage: import * as yup from 'yup'; export const StoreUserRequestSchema = yup.object({ email: yup.string().required('This field is required').email('Invalid email address'), name: yup.string().required('This field is required').max(255, 'Must be at most 255 characters'), password: yup.string().required('This field is required').min(8, 'Must be at least 8 characters'), age: yup.number().nullable().min(18, 'Must be at least 18'), }); export const UpdateUserRequestSchema = yup.object({ email: yup.string().optional().email('Invalid email address'), name: yup.string().optional().max(255, 'Must be at most 255 characters'), });
Security Recommendations
- Never enable in production - Set
TYPESCRIPT_MODELS_ENABLED=false - Use strong tokens - Generate a secure random token
- Restrict IPs - Limit to localhost or known development IPs
- Use HTTPS - Always use HTTPS when transmitting tokens
- Exclude sensitive models - Don't expose internal/admin models
Type Mapping Reference
PHP/Laravel to TypeScript
| PHP/Laravel Type | TypeScript Type |
|---|---|
int, integer |
number |
float, double, decimal |
number |
bool, boolean |
boolean |
array, json, collection |
any[] |
object |
Record<string, any> |
datetime, date, timestamp |
Date |
string (default) |
string |
Validation Rules to TypeScript
| Laravel Rule | TypeScript Type |
|---|---|
integer, numeric |
number |
boolean, accepted |
boolean |
array |
any[] |
file, image |
File |
json |
Record<string, any> |
| Other | string |
License
MIT License - see LICENSE file.
统计信息
- 总下载量: 8
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 2
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-02-12