npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

laravel-ts-sync

v1.1.0

Published

Sync Laravel and TypeScript models bidirectionally. Generate TypeScript interfaces from Laravel models and Laravel DTOs/stubs from TypeScript definitions.

Readme

laravel-ts-sync 🚀

npm version License: MIT Node.js Version PRs Welcome

Sync Laravel and TypeScript models bidirectionally 🔄

Generate TypeScript interfaces from Laravel models and Laravel DTOs, form requests, policies, factories, and API resources from TypeScript definitions. Keep your frontend and backend types in perfect harmony! 🤝


Table of Contents 📑


Features 🌟

  • 🔀 Bidirectional Sync — Convert Laravel PHP models → TypeScript interfaces AND TypeScript interfaces → Laravel PHP files
  • 📋 Full Laravel Support — Parses $fillable, $guarded, $hidden, $casts, relationships (hasOne, hasMany, belongsTo, belongsToMany, morphTo, morphMany, morphToMany), accessors, mutators, scopes, and enums
  • 🔧 Generator Options — Generate models, form requests, DTOs, policies, factories, and API resources from TypeScript
  • 👀 Watch Mode — Automatically regenerate TypeScript interfaces when Laravel model files change using chokidar
  • ⚙️ Highly Configurable — Custom type mappings, field overrides, ignore lists, multiple config file formats (.js, .json, .ts, .rc files)
  • 🎨 Interface or Type — Choose between export interface or export type style for TypeScript output
  • 📁 Per-Model & Aggregated — Generate individual files per model OR a single aggregated models.ts file (or both!)
  • 🧪 Utility Types — Auto-generates CreateInput, UpdateInput, and Filter utility types for each model
  • 🏷️ Enum Support — Extracts PHP enums from Laravel models and generates TypeScript enums
  • 🔗 Relationship Types — Generates TypeScript types for Eloquent relationships with proper nullability and array handling
  • 🧹 Dry Run Mode — Preview what files would be generated without writing anything to disk
  • 📝 Validation Rules — Automatically generates Laravel form request validation rules from TypeScript property types
  • ✅ Config Validation — Built-in validate command to verify your configuration

Architecture 🏗️

┌─────────────────────────────────────────────────────────────┐
│                   laravel-ts-sync CLI                       │
│                    (Commander-based)                        │
└──────────────────┬──────────────────────────┬──────────────┘
                   │                          │
         ┌─────────▼──────────┐    ┌──────────▼─────────┐
         │  Laravel Parser    │    │ TypeScript Parser   │
         │  (php-parser AST)  │    │  (TS Compiler API)  │
         └─────────┬──────────┘    └──────────┬─────────┘
                   │                          │
         ┌─────────▼──────────┐    ┌──────────▼─────────┐
         │ TypeScript Generator│    │  Laravel Generator  │
         │  (.ts interfaces,   │    │  (.php models,      │
         │   types, utilities) │    │   requests, DTOs,   │
         └─────────────────────┘    │   policies, etc.)   │
                                    └─────────────────────┘

Parsers 🧠

| Parser | Source | Technology | Output | |--------|--------|------------|--------| | LaravelParser 🐘 | PHP model files | php-parser (AST) | LaravelModel[] | | TypeScriptParser 📝 | .ts interface files | TypeScript Compiler API | TypeScriptInterface[] |

Generators 🏭

| Generator | Input | Output | |-----------|-------|--------| | TypeScriptGenerator 💙 | LaravelModel[] | .ts interfaces, types, enums, utility types | | LaravelGenerator 🟠 | TypeScriptInterface[] | .php models, requests, DTOs, policies, factories, API resources |


Installation 📦

Global Installation 🌍

npm install -g laravel-ts-sync

Local Installation (recommended) 📁

npm install --save-dev laravel-ts-sync

Run with npx (no install) ⚡

npx laravel-ts-sync --help

Requirements ✅

  • Node.js >= 16.0.0
  • Laravel project (for Laravel → TypeScript direction)
  • TypeScript project (for TypeScript → Laravel direction)

Quick Start 🚀

1️⃣ Initialize Configuration

npx laravel-ts-sync init

This creates a laravel-ts-sync.config.js file in your project root with sensible defaults.

2️⃣ Generate TypeScript from Laravel

npx laravel-ts-sync laravel-to-ts

This parses your Laravel models in ./app/Models and generates TypeScript interfaces in ./generated/.

3️⃣ Generate Laravel from TypeScript

npx laravel-ts-sync ts-to-laravel

This parses your TypeScript interfaces in ./src/types and generates Laravel PHP files in ./generated/.


Configuration ⚙️

Edit laravel-ts-sync.config.js in your project root:

module.exports = {
  // 📁 Path to Laravel models directory
  laravelPath: './app/Models',

  // 📁 Path to TypeScript types directory
  typescriptPath: './src/types',

  // 📁 Output directory for generated files
  outputDir: './generated',

  // 📄 Generate one file per model
  generatePerModel: true,

  // 📄 Generate aggregated types file
  generateAggregated: true,

  // 📄 Aggregated file name
  aggregatedFileName: 'models.ts',

  // 🎨 Use 'interface' or 'type' for TypeScript output
  interfaceStyle: 'interface',

  // 🚫 Models to ignore (by name)
  ignoreModels: [],

  // 🚫 Fields to ignore (timestamps, etc.)
  ignoreFields: ['id', 'created_at', 'updated_at', 'deleted_at'],

  // 🔄 Custom type mappings (Laravel type -> TS type)
  typeOverrides: {},

  // 🎨 Custom field-to-type mappings
  customMappings: {},

  // ✨ Use Prettier for formatting (requires prettier)
  usePrettier: false,

  // 👀 Enable watch mode
  watchMode: false,

  // 🧪 Dry run (no files written)
  dryRun: false,

  // 📣 Verbose output
  verbose: false,

  // 🏷️ Generate TypeScript enums from PHP enums
  generateEnums: true,

  // 🔧 Generate Laravel accessors in model
  generateAccessors: false,

  // 🔧 Generate Laravel mutators in model
  generateMutators: false,

  // 🔍 Generate Laravel query scopes
  generateScopes: false,

  // 🛡️ Generate Laravel policies
  generatePolicies: false,

  // 🏭 Generate Laravel factories
  generateFactories: false,

  // 📡 Generate Laravel API resources
  generateApiResources: false,
};

Config File Formats 📄

The config loader supports multiple formats (searched in order):

| Format | File Name | |--------|-----------| | JavaScript | laravel-ts-sync.config.js | | JSON | laravel-ts-sync.config.json | | TypeScript | laravel-ts-sync.config.ts | | RC (JSON) | .laravel-ts-syncrc | | RC (JSON5) | .laravel-ts-syncrc.json | | Package.json | laravelTsSync key in package.json |


CLI Commands 🖥️

laravel-ts-sync generate 📝

Generate files based on direction:

# Laravel to TypeScript 🔀
npx laravel-ts-sync generate --direction laravel-to-ts

# TypeScript to Laravel 🔀
npx laravel-ts-sync generate --direction ts-to-laravel

laravel-ts-sync laravel-to-ts (alias: l2ts) 💙

Generate TypeScript interfaces from Laravel models:

npx laravel-ts-sync laravel-to-ts

# With options
npx laravel-ts-sync laravel-to-ts --output ./src/types --verbose --dry-run

# Ignore specific models
npx laravel-ts-sync laravel-to-ts --ignore User,OldModel

laravel-ts-sync ts-to-laravel (alias: ts2l) 🟠

Generate Laravel files from TypeScript interfaces:

npx laravel-ts-sync ts-to-laravel

# With options
npx laravel-ts-sync ts-to-laravel --output ./app/Models --dry-run --verbose

laravel-ts-sync init 🏁

Create a configuration file:

npx laravel-ts-sync init

laravel-ts-sync watch 👀

Watch for changes and regenerate:

npx laravel-ts-sync watch

# With custom config
npx laravel-ts-sync watch --config ./config/laravel-ts-sync.config.js

laravel-ts-sync validate

Validate configuration file:

npx laravel-ts-sync validate

# Validate a specific config
npx laravel-ts-sync validate --config ./custom-config.js

CLI Options 🎛️

| Option | Alias | Description | |--------|-------|-------------| | --config <path> | -c | Path to config file | | --output <path> | -o | Output directory | | --dry-run | | Run without writing files | | --verbose | -v | Enable verbose output | | --ignore <models> | | Models to ignore (comma-separated, e.g. User,Post) | | --direction <direction> | -d | Generation direction: laravel-to-ts or ts-to-laravel | | --help | -h | Display help information | | --version | -V | Display version number |


Type Mapping 🔄

Laravel to TypeScript 🐘 → 💙

| Laravel Type | TypeScript Type | Notes | |-------------|----------------|-------| | int | number | | | integer | number | | | bigint | number | | | float | number | | | double | number | | | decimal | number | | | string | string | | | varchar | string | | | char | string | | | text | string | | | longtext | string | | | mediumtext | string | | | tinytext | string | | | boolean | boolean | | | bool | boolean | | | date | string | ⚠️ Use string for serialized dates | | datetime | string | ⚠️ Use string for serialized dates | | timestamp | string | ⚠️ Use string for serialized dates | | time | string | | | json | Record<string, unknown> | | | array | unknown[] | | | object | Record<string, unknown> | | | binary | Buffer | | | uuid | string | | | enum | string | ⚠️ Use generateEnums for typed enums | | file | File | | | collection | unknown[] | |

TypeScript to Laravel 💙 → 🐘

| TypeScript Type | Laravel Type | Notes | |----------------|-------------|-------| | number | integer | | | string | string | | | boolean | boolean | | | Date | datetime | | | Buffer | binary | | | Record<string, unknown> | json | | | unknown[] | json | | | File | file | |

Relationship Type Mapping 🔗

| Laravel Relation | TypeScript Type | Nullable | |-----------------|----------------|----------| | hasOne | RelatedModel \| null | ✅ | | hasMany | RelatedModel[] | ❌ | | belongsTo | RelatedModel \| null | ✅ | | belongsToMany | RelatedModel[] | ❌ | | morphTo | RelatedModel \| null | ✅ | | morphMany | RelatedModel[] | ❌ | | morphToMany | RelatedModel[] | ❌ |


Custom Mappings 🎨

Add custom mappings in your config to handle project-specific types:

module.exports = {
  // 🎨 Custom field name -> Laravel cast type mappings
  customMappings: {
    'Money': 'decimal:10,2',
    'Status': 'enum:active,inactive',
    'UUID': 'uuid',
    'SocialLinks': 'json',
    'Coordinates': 'object',
  },

  // 🔄 Override specific field types
  typeOverrides: {
    'amount': 'number',
    'currency': 'string',
    'metadata': 'Record<string, unknown>',
    'tags': 'string[]',
  },

  // 🚫 Ignore specific fields globally
  ignoreFields: [
    'id',
    'created_at',
    'updated_at',
    'deleted_at',
    'laravel_through_key',
    'remember_token',
  ],
};

Generated Files 📄

TypeScript Output 💙

When running laravel-to-ts, each model generates:

Main Interface

// models/user.ts
export interface User {
  id: number;
  name: string;
  email: string;
  password?: string;
  is_active: boolean;
  role: 'admin' | 'user' | 'guest';
  avatar_url?: string;
  bio?: string | null;
  date_of_birth?: string;
  email_verified_at?: string;
  created_at: string;
  updated_at: string;
}

Utility Types (auto-generated) 🛠️

// 📝 Create input — all fillable fields required
export type UserCreateInput = {
  name: string;
  email: string;
  password: string;
  role?: string;
  is_active?: boolean;
  avatar_url?: string;
  bio?: string | null;
};

// ✏️ Update input — all fields optional
export type UserUpdateInput = {
  name?: string;
  email?: string;
  password?: string;
  role?: string;
  is_active?: boolean;
  avatar_url?: string;
  bio?: string | null;
  date_of_birth?: string;
};

// 🔍 Filter type — all fields optional for query filtering
export type UserFilter = {
  id?: number;
  name?: string;
  email?: string;
  role?: string;
  is_active?: boolean;
  created_at?: string;
  updated_at?: string;
};

Generated Enums 🏷️

// Enums for User
export enum UserRole {
  ADMIN = 'admin',
  USER = 'user',
  GUEST = 'guest',
}

Relation Types 🔗

import type { Post } from './post';
import type { Profile } from './profile';

export interface User {
  // ...fields...
  posts?: Post[];
  profile?: Profile | null;
}

// Relation types
export type UserPostsRelation = Post;
export type UserProfileRelation = Profile;

Laravel Output 🟠

When running ts-to-laravel, each interface generates:

Eloquent Model 🐘

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use HasFactory;

    protected $table = 'users';

    protected $fillable = [
        'name', 'email', 'password', 'role',
        'is_active', 'avatar_url', 'bio', 'date_of_birth'
    ];

    protected $casts = [
        'is_active' => 'boolean',
        'email_verified_at' => 'datetime',
        'date_of_birth' => 'datetime',
        'settings' => 'array',
    ];

    protected $hidden = ['password'];
}

Form Request ✅

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255'],
            'password' => ['required', 'string', 'min:8'],
            'role' => ['nullable', 'string'],
            'is_active' => ['nullable', 'boolean'],
            'avatar_url' => ['nullable', 'string', 'url'],
            'bio' => ['nullable', 'string', 'max:1000'],
            'date_of_birth' => ['nullable', 'date'],
        ];
    }

    public function messages(): array
    {
        return [
            // Add custom validation messages here
        ];
    }
}

DTO 📦

<?php

namespace App\Dtos;

class UserDto
{
    private string $name;
    private string $email;
    private string $password;
    private ?string $role;
    private ?bool $is_active;
    private ?string $avatar_url;
    private ?string $bio;
    private ?string $date_of_birth;

    public function __construct(
        string $name,
        string $email,
        string $password,
        ?string $role = null,
        ?bool $is_active = null,
        ?string $avatar_url = null,
        ?string $bio = null,
        ?string $date_of_birth = null
    ) {
        $this->name = $name;
        $this->email = $email;
        $this->password = $password;
        $this->role = $role;
        $this->is_active = $is_active;
        $this->avatar_url = $avatar_url;
        $this->bio = $bio;
        $this->date_of_birth = $date_of_birth;
    }

    // Getters...
    public function getName(): string { return $this->name; }

    // toArray()...
    public function toArray(): array { /* ... */ }

    // fromArray()...
    public static function fromArray(array $data): self { /* ... */ }
}

Policy 🛡️

Generated when generatePolicies: true

<?php

namespace App\Policies;

use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class UserPolicy
{
    use HandlesAuthorization;

    public function viewAny(User $user): bool { return true; }
    public function view(User $user, User $model): bool { return true; }
    public function create(User $user): bool { return true; }
    public function update(User $user, User $model): bool { return $user->id === $model->id; }
    public function delete(User $user, User $model): bool { return $user->id === $model->id; }
    public function restore(User $user, User $model): bool { return true; }
    public function forceDelete(User $user, User $model): bool { return true; }
}

Factory 🏭

Generated when generateFactories: true

<?php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
    protected $model = User::class;

    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'password' => bcrypt('password'),
            'role' => fake()->randomElement(['admin', 'user', 'guest']),
            'is_active' => fake()->boolean(),
            'bio' => fake()->optional()->text(200),
        ];
    }
}

API Resource 📡

Generated when generateApiResources: true

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'role' => $this->role,
            'is_active' => $this->is_active,
            'avatar_url' => $this->avatar_url,
            'bio' => $this->bio,
            'date_of_birth' => $this->date_of_birth,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}

Watch Mode 👀

Enable automatic regeneration on file changes:

npx laravel-ts-sync watch

This will watch the laravelPath directory and regenerate TypeScript interfaces whenever a model file is created, modified, or deleted. 🪄

# With verbose output
npx laravel-ts-sync watch --verbose

# With custom config
npx laravel-ts-sync watch --config ./my-config.js

The watcher uses chokidar with these defaults:

  • Ignores dotfiles
  • Watches all PHP files in the configured laravelPath directory
  • Automatically debounces rapid successive changes
  • Press Ctrl+C to stop watching

Programmatic API 🧩

You can also use laravel-ts-sync programmatically in your own scripts:

import { LaravelParser, TypeScriptGenerator, loadConfig, resolvePaths } from 'laravel-ts-sync';

async function generate() {
  // 1️⃣ Load configuration
  const config = loadConfig('./laravel-ts-sync.config.js');
  const paths = resolvePaths(config);

  // 2️⃣ Parse Laravel models
  const parser = new LaravelParser(config);
  const models = await parser.parseDirectory(paths.laravelPath);

  // 3️⃣ Generate TypeScript interfaces
  const generator = new TypeScriptGenerator(config);
  const result = await generator.generateFromModels(models, paths.outputDir);

  console.log(`✅ Generated ${result.filesGenerated.length} files`);
  console.log(`⏱️  Duration: ${result.duration}ms`);
}

Available Classes 📚

| Class | Import | Description | |-------|--------|-------------| | LaravelParser | laravel-ts-sync | Parses PHP Laravel model files into LaravelModel[] | | TypeScriptParser | laravel-ts-sync | Parses TypeScript interface files into TypeScriptInterface[] | | TypeScriptGenerator | laravel-ts-sync | Generates .ts files from LaravelModel[] | | LaravelGenerator | laravel-ts-sync | Generates .php files from TypeScriptInterface[] |

Available Utilities 🔧

| Function | Description | |----------|-------------| | loadConfig(path?) | Load and merge config from file with defaults | | saveConfig(config) | Save config to default file path | | validateConfig(config) | Validate config, returns string[] of errors | | resolvePaths(config) | Resolve relative paths to absolute | | createDefaultConfig() | Create config with all default values |


Advanced Usage 🔧

Example: Full Laravel Model with Relationships

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Post extends Model
{
    protected $fillable = ['title', 'content', 'user_id', 'status', 'published_at'];

    protected $casts = [
        'published_at' => 'datetime',
        'is_pinned' => 'boolean',
        'metadata' => 'array',
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}

Generated TypeScript

import type { User } from './user';
import type { Comment } from './comment';

export interface Post {
  id: number;
  title: string;
  content: string;
  user_id: number;
  status: string;
  published_at?: string;
  is_pinned: boolean;
  metadata: Record<string, unknown>;
  created_at: string;
  updated_at: string;
  user?: User | null;
  comments?: Comment[];
}

export type PostCreateInput = {
  title: string;
  content: string;
  user_id: number;
  status: string;
  published_at?: string;
  is_pinned?: boolean;
  metadata?: Record<string, unknown>;
};

export type PostFilter = {
  id?: number;
  title?: string;
  user_id?: number;
  status?: string;
  is_pinned?: boolean;
};

Using interfaceStyle: 'type' 🎨

module.exports = {
  interfaceStyle: 'type',  // Use 'type' instead of 'interface'
};

This generates:

export type User = {
  id: number;
  name: string;
  email: string;
  // ...
};

Testing 🧪

# Run tests 🏃
npm test

# Run tests in watch mode 👀
npm run test:watch

# Run tests with coverage 📊
npm run test:coverage

# Lint source code 🔍
npm run lint

# TypeScript type checking ✅
npm run typecheck

Contributing 🤝

We welcome contributions! Here's how you can help:

  1. 🍴 Fork the repository
  2. 🌿 Create your feature branch (git checkout -b feature/amazing-feature)
  3. 💻 Commit your changes (git commit -m 'Add some amazing feature')
  4. 📤 Push to the branch (git push origin feature/amazing-feature)
  5. 🔁 Open a Pull Request

Development Setup 🛠️

# Clone the repository
git clone https://github.com/yourusername/laravel-ts-sync.git

# Install dependencies
npm install

# Build the project
npm run build

# Run tests
npm test

Code Style Guidelines 📐

  • Follow the existing code style and conventions
  • Write tests for new features
  • Update documentation as needed
  • Use meaningful commit messages
  • Keep pull requests focused on a single change

License 📄

This project is licensed under the MIT License — see the LICENSE file for details.


Support 💬