Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Oi Laravel Ts Laravel Package

oi-lab/oi-laravel-ts

A Laravel package that automatically generates TypeScript interfaces from your Eloquent models, with relationship support, custom casts, DataObjects, PHPDoc types, watch mode, and namespace filters for customization.

View on GitHub
Deep Wiki
Context7

OI Laravel TypeScript Generator

Latest Version on Packagist Total Downloads Tests License

A Laravel package that automatically generates TypeScript interfaces from your Eloquent models, complete with relationships, custom casts, and DataObjects support.

Features

  • Automatic Interface Generation: Converts Eloquent models to TypeScript interfaces
  • Relationship Support: Handles all Laravel relationship types (HasOne, HasMany, BelongsTo, etc.)
  • Custom Casts: Supports Laravel custom casts and automatically detects DataObjects
  • DTO Support: Emits interfaces for spatie/laravel-data style DTOs (no dependency required), with enum literal unions and nested DTOs
  • PHPDoc Support: Reads PHPDoc annotations for complex types
  • Watch Mode: Monitor your models directory and regenerate on changes
  • Namespace Filters: Exclude third-party models entirely, or promote them to extension interfaces
  • Smart Primary Key Types: UUID / ULID keys are typed as string, integer keys as number
  • Configurable: Extensive configuration options for customization
  • JSON-LD Support: Optional support for JSON-LD data structures

Architecture

This package uses a modular architecture with clear separation of concerns, organized in two main pipelines:

Pipeline 1: Eloquent Analysis

  • Eloquent: Facade for model analysis and schema generation
  • ModelDiscovery: Discovers all Eloquent models in the application
  • TypeExtractor: Extracts type information from models
  • CastTypeResolver: Resolves custom Laravel casts to TypeScript types
  • RelationshipResolver: Detects and extracts relationship metadata
  • DataObjectAnalyzer: Analyzes PHP DataObject classes
  • PhpToTypeScriptConverter: Converts PHP types to TypeScript
  • SchemaBuilder: Orchestrates schema building for all models

Pipeline 2: TypeScript Generation

  • Convert: Main orchestrator coordinating the conversion process
  • TypeScriptTypeConverter: Handles schema to TypeScript type conversion
  • DataObjectProcessor: Processes PHP DataObjects and generates their interfaces
  • DataClassResolver / DataClassAnalyzer / DataClassProcessor: Discover, analyze and emit spatie/laravel-data style DTOs
  • ModelInterfaceGenerator: Generates TypeScript interfaces for Laravel models
  • ImportManager: Manages TypeScript import statements
  • JsonLdGenerator: Generates JSON-LD support interfaces

For detailed architecture documentation, see ARCHITECTURE.md.

Requirements

  • PHP 8.2+
  • Laravel 11.0+, 12.0+, or 13.0+

Installation

composer require oi-lab/oi-laravel-ts

Local Development

For local development, add this to your main project's composer.json:

{
    "repositories": [
        {
            "type": "path",
            "url": "./packages/oi-lab/oi-laravel-ts"
        }
    ]
}

Then:

composer require oi-lab/oi-laravel-ts

Configuration

Publish the configuration file:

php artisan vendor:publish --tag=oi-laravel-ts-config

This creates config/oi-laravel-ts.php with the following options:

return [
    // Output path for generated TypeScript file
    'output_path' => resource_path('js/types/interfaces.ts'),

    // Include _count fields for relationships
    'with_counts' => true,

    // Enable JSON-LD support
    'with_json_ld' => false,

    // Follow relationships to generate interfaces for referenced models
    // (incl. models attached through traits, e.g. spatie/laravel-permission)
    'discover_related_models' => true,

    // Add every Eloquent model of these namespaces to the schema, as if in app/Models
    'included_model_namespaces' => [],

    // Exclude models in these namespaces entirely (incl. relation fields pointing to them)
    'excluded_namespaces' => [],

    // Models in these namespaces emit I{Name}Extended extends I{Name} interfaces
    'extended_namespaces' => [],

    // Save intermediate schema.json for debugging
    'save_schema' => false,

    // Namespaces holding spatie/laravel-data style DTOs to emit as I{ClassName}
    'data_namespaces' => [],

    // Give a DTO a distinct interface name when two share a short class name
    'data_aliases' => [],

    // 'null': `?` means "key may be absent", `| null` means "value may be null"
    // 'optional': legacy — nullable or defaulted properties all render as `?`
    'data_nullable_style' => 'null',

    // When true, a model mapped to a DTO no longer emits its own Eloquent interface
    'data_replaces_model' => false,

    // Explicit model => DTO map (otherwise inferred from the DTO's fromModel() factory)
    'data_for_model' => [],

    // Define specific types for model properties
    'props_with_types' => [],

    // Add custom properties to models
    'custom_props' => [
        'Organization' => [
            'uuid' => 'string',
        ],
    ],
];

Usage

Basic Generation

Generate TypeScript interfaces from your models:

php artisan oi:gen-ts

This will scan all models in app/Models and generate a TypeScript file at the configured output path.

Watch Mode

Automatically regenerate when models change:

php artisan oi:gen-ts --watch

Example Output

Given a Laravel model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $fillable = ['name', 'email', 'bio'];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

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

The package generates:

export interface IUser {
    id: number;
    name: string;
    email: string;
    bio: string;
    email_verified_at?: string;
    created_at: string;
    updated_at: string;
    posts?: IPost[];
    posts_count?: number;
}

Advanced Features

Custom Properties

Add properties that aren't in your database schema:

// config/oi-laravel-ts.php
'custom_props' => [
    'User' => [
        'full_name' => 'string',
        'avatar_url' => 'string',
    ],
],

DataObject Support

The package automatically detects and converts custom DataObjects:

// Your model
class Page extends Model
{
    protected $casts = [
        'metadata' => MetadataCast::class,
    ];
}

// Your Cast
class MetadataCast implements CastsAttributes
{
    public function get($model, string $key, $value, array $attributes): Metadata
    {
        return Metadata::fromArray(json_decode($value, true));
    }
}

// Generated TypeScript
export interface IMetadata {
    title: string;
    description?: string;
}

export interface IPage {
    id: number;
    metadata?: IMetadata | null;
}

DTO Support (spatie/laravel-data)

Register the namespaces holding your Data Transfer Objects and every DTO is emitted as an I{ClassName} interface. Detection is structural — no dependency on spatie/laravel-data is required:

'data_namespaces' => [
    'App\\Data',
],
// App\Data\Knowledge\KnowledgeData
class KnowledgeData extends \Spatie\LaravelData\Data
{
    public function __construct(
        public readonly string $id,
        public readonly KnowledgeState $state,   // backed enum
        public readonly ?KnowledgeSourceData $source,   // nested DTO
        /** @var KnowledgeTagData[]|null */
        public readonly ?array $tags,
        public readonly string|Optional $slug,   // may be absent from the payload
        public readonly bool $isActive = true,
    ) {}

    public static function fromModel(Knowledge $knowledge): self { /* ... */ }
}
export interface IKnowledgeData {
    id: string;
    state: 'draft' | 'published' | 'archived';
    source: IKnowledgeSourceData | null;
    tags: IKnowledgeTagData[] | null;
    slug?: string;
    isActive: boolean;
}

Property names are kept verbatim (camelCase), backed enums become literal unions, nested DTOs become I{Name}, and typed arrays declared via a property @var Foo[] annotation become IFoo[].

The interface describes the JSON the DTO produces, so ? and | null mean different things: ? marks a key that may be absent (an Optional / Lazy property), | null a key that is always present but may hold null. A default value makes nothing optional. See data_nullable_style to fall back to the legacy rendering.

By default DTO interfaces coexist with the Eloquent model interfaces. To make a DTO the single source of truth for its model — suppressing the model's own I{Model} interface — enable data_replaces_model. The model is inferred from the DTO's fromModel() factory, or set explicitly via data_for_model:

'data_replaces_model' => true,
'data_for_model' => [
    App\Models\Knowledge::class => App\Data\Knowledge\KnowledgeData::class,
],

Note: with data_replaces_model enabled, a relationship on another model that points to a replaced model will reference an interface that is no longer generated.

Namespace Filters

Exclude third-party or package models from the schema entirely:

'excluded_namespaces' => [
    'OiLab\\Prestashop\\Models',
],

Models in these namespaces are dropped — including when reached through a relationship — and any relationship field pointing to them is stripped from other interfaces.

Alternatively, turn package model variants into extension interfaces:

'extended_namespaces' => [
    'OiLab\\Prestashop\\Extended\\Models',
],

For each extended model whose short name matches a base model in the schema, the generator emits:

export interface IUserExtended extends IUser {
    prestashop_id: number;
}

UUID / ULID Primary Keys

Primary key types are resolved automatically. Models using HasUuids, HasUlids, or declaring $keyType = 'string' generate id: string instead of id: number:

// Standard auto-increment model
export interface IPost {
    id: number;
    // ...
}

// UUID model (uses HasUuids trait or $keyType = 'string')
export interface IOrder {
    id: string;
    // ...
}

Import External Types

Reference external TypeScript types:

'custom_props' => [
    'User' => [
        'settings' => '@/types/settings|UserSettings',
    ],
],

Generates:

import { UserSettings } from '@/types/settings';

export interface IUser {
    settings: UserSettings;
}

Examples

Complete Workflow

  1. Define your models with relationships and casts
  2. Configure custom properties if needed
  3. Run the generator:
php artisan oi:gen-ts
  1. Use the generated interfaces in your TypeScript code:
import { IUser, IPost } from '@/types/interfaces';

const user: IUser = await fetchUser();
const posts: IPost[] = user.posts || [];

Integration with Inertia.js

import { PageProps } from '@inertiajs/core';
import { IUser } from '@/types/interfaces';

interface Props extends PageProps {
    user: IUser;
}

export default function Dashboard({ user }: Props) {
    // TypeScript knows all User properties
    console.log(user.email);
}

Testing

This package includes comprehensive test coverage with 142 tests and 344 assertions.

Run Tests

# Run all tests
vendor/bin/pest

# Run specific test suite
vendor/bin/pest tests/Unit
vendor/bin/pest tests/Feature

# Run with coverage
vendor/bin/pest --coverage

Test Coverage

  • ✅ Type conversion (PHP → TypeScript)
  • ✅ Model analysis and schema building
  • ✅ Relationship detection (HasMany, BelongsTo, etc.)
  • ✅ Custom cast resolution
  • ✅ DataObject handling
  • ✅ DTO (spatie/laravel-data) generation, enums and nested DTOs
  • ✅ TypeScript interface generation
  • ✅ Namespace exclusion and extension interfaces
  • ✅ UUID / ULID primary key type resolution
  • ✅ Integration tests for full pipeline

For detailed testing documentation, see TESTING.md.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

When contributing:

  1. Write tests for new features
  2. Ensure all tests pass: vendor/bin/pest
  3. Follow existing code style
  4. Update documentation as needed

License

This package is open-source software licensed under the MIT license.

Credits

Olivier Lacombe - Creator and maintainer

Olivier is a Product & Technology Director based in Montpellier, France, with over 20 years of experience innovating in UX/UI and emerging technologies. He specializes in guiding enterprises toward cutting-edge digital solutions, combining user-centered design with continuous optimization and artificial intelligence integration.

Projects & Resources:

  • OI Dev Docs - Documentation for all Open Source OI Lab packages
  • OnAI - Training courses and masterclasses on generative AI for businesses
  • Promptr - Prompt engineering Management Platform

Support

For support, please open an issue on the GitHub repository.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai