PHPackages                             vincentndegwa/eloquent-typegen - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. vincentndegwa/eloquent-typegen

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

vincentndegwa/eloquent-typegen
==============================

Generate TypeScript types from Eloquent models

v1.0.6(3w ago)262↓50%MITPHPPHP ^8.1CI passing

Since May 5Pushed 3w agoCompare

[ Source](https://github.com/VincentNdegwa/eloquent-typegen)[ Packagist](https://packagist.org/packages/vincentndegwa/eloquent-typegen)[ RSS](/packages/vincentndegwa-eloquent-typegen/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (11)Versions (8)Used By (0)

⚡ eloquent-typegen
==================

[](#-eloquent-typegen)

**Stop writing TypeScript types by hand. Generate them from your Laravel models.**

Reads your `$casts`, PHP enums, migrations (for nullability), and relationships — and produces accurate `.ts` files your Vue, React, or Svelte frontend can import immediately.

[**Installation**](#installation) · [**Configuration**](#configuration) · [**Usage**](#usage) · [**Type Mapping**](#type-mapping) · [**Casting Logic**](#casting-logic)

---

The Problem
-----------

[](#the-problem)

You add a column to your `users` table. You update the model. You run the migration.

Then you open your frontend and realise:

- Your TypeScript type still has the old fields
- You're not sure if `score` is `number | null` or just `number`
- Your `role` field is typed as `string` but it should be `'admin' | 'editor' | 'viewer'`
- Someone added a `deleted_at` field and now there's a runtime error in production

**You shouldn't have to maintain types in two places.**

---

The Solution
------------

[](#the-solution)

One command. Run it after any migration or model change.

```
php artisan typegen:generate
```

```
✓ user.ts
✓ post.ts
✓ blog-post.ts
✓ model-helpers.ts
✓ index.ts

✅  Done! Generated 5 type file(s) → resources/js/types/models

```

---

What It Generates
-----------------

[](#what-it-generates)

**Your Laravel model:**

```
// app/Models/User.php

class User extends Model
{
    use SoftDeletes;

    protected $fillable = ['name', 'email', 'active'];

    protected $hidden = ['password', 'remember_token'];

    protected $casts = [
        'active' => 'boolean',
        'role'   => UserRole::class,
        'score'  => 'decimal:2',
        'meta'   => 'array',
    ];
}

enum UserRole: string
{
    case Admin  = 'admin';
    case Editor = 'editor';
    case Viewer = 'viewer';
}
```

**Your migration:**

```
Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email');
    $table->boolean('active')->default(true);
    $table->string('role');
    $table->decimal('score', 8, 2)->nullable(); // ← detected automatically
    $table->json('meta')->nullable();
    $table->timestamps();
    $table->softDeletes();
});
```

**Generated `resources/js/types/models/user.ts`:**

```
// Auto-generated by eloquent-typegen.
// Run `php artisan typegen:generate` to refresh.

import type { Nullable } from './model-helpers';

export type UserRole = 'admin' | 'editor' | 'viewer';

export interface User {
  readonly id: number;
  name: string;
  email: string;
  active: boolean;
  role: UserRole;
  score?: Nullable;
  meta?: Nullable;
  created_at?: Nullable;
  updated_at?: Nullable;
  deleted_at?: Nullable;
}

/** Fields required to create a new User */
export type CreateUserPayload = Omit;

/** Fields allowed when updating a User */
export type UpdateUserPayload = Partial;
```

Notice what happened automatically:

- `password` and `remember_token` are **absent** — they're in `$hidden`
- `score` and `meta` are **optional and nullable** — read from your migration file
- `role` is `'admin' | 'editor' | 'viewer'`, not just `string` — derived from your PHP enum
- `deleted_at` is included because you use `SoftDeletes`
- `CreateUserPayload` and `UpdateUserPayload` are generated for free

---

Installation
------------

[](#installation)

> **Requirements:** PHP 8.1+, Laravel 10 / 11 / 12 / 13

Install as a dev dependency — this package has zero production footprint:

```
composer require vincentndegwa/eloquent-typegen --dev
```

Laravel's auto-discovery registers the package automatically. No manual config needed.

Optionally publish the config file:

```
php artisan vendor:publish --tag=typegen-config
```

---

Usage
-----

[](#usage)

### Generate all types

[](#generate-all-types)

```
php artisan typegen:generate
```

### Generate for specific models only

[](#generate-for-specific-models-only)

```
php artisan typegen:generate --model=User --model=Post
```

### Preview without writing files

[](#preview-without-writing-files)

```
php artisan typegen:generate --dry-run
```

### Custom output path

[](#custom-output-path)

```
php artisan typegen:generate --path=src/types/api
```

### Skip relationships

[](#skip-relationships)

```
php artisan typegen:generate --no-relations
```

---

Using the Generated Types
-------------------------

[](#using-the-generated-types)

### Vue 3

[](#vue-3)

```

import type { User, UpdateUserPayload } from '@/types/models'

const props = defineProps()

async function save(payload: UpdateUserPayload) {
  await $fetch(`/api/users/${props.user.id}`, {
    method: 'PATCH',
    body: payload,
  })
}

  {{ user.score ?? 'No score yet' }}

```

### React

[](#react)

```
import type { User, CreateUserPayload, Paginated } from '@/types/models'

async function getUsers(): Promise {
  const res = await fetch('/api/users')
  return res.json()
}

function UserCard({ user }: { user: User }) {
  return (

      {user.name}
      {/* 'superadmin' would be a compile error */}
      {user.role === 'admin' && }
      {/* TypeScript knows score is number | null */}
      Score: {user.score?.toFixed(2) ?? 'N/A'}

  )
}

function CreateUserForm() {
  const [form, setForm] = useState({
    name: '',
    email: '',
    active: true,
    role: 'viewer',
  })
  // ...
}
```

### Svelte 5

[](#svelte-5)

Svelte fully supports TypeScript — add `lang="ts"` to your `` block:

```

  import type { User } from '$lib/types/models'

  let { user }: { user: User } = $props()

{#if user.role === 'admin'}

{/if}

Score: {user.score ?? 'No score yet'}
```

### Inertia.js

[](#inertiajs)

```
// Inertia passes your model data as page props — type them directly:
import type { User, Paginated } from '@/types/models'

defineProps()
```

---

Configuration
-------------

[](#configuration)

`config/typegen.php` after publishing:

```
return [

    /*
    |--------------------------------------------------------------------------
    | Model Paths
    |--------------------------------------------------------------------------
    | Directories to scan for Eloquent models. Relative to app_path().
    */
    'model_paths' => ['Models'],

    /*
    |--------------------------------------------------------------------------
    | Output Directory
    |--------------------------------------------------------------------------
    | Where to write .ts files. Relative to base_path() or absolute.
    | The default works for Vite-based projects (Vue, React, Svelte, Inertia).
    */
    'output_path' => 'resources/js/types/models',

    /*
    |--------------------------------------------------------------------------
    | Index File
    |--------------------------------------------------------------------------
    | Generates an index.ts barrel — one import for all your model types.
    */
    'generate_index' => true,

    /*
    |--------------------------------------------------------------------------
    | Helpers File
    |--------------------------------------------------------------------------
    | Generates model-helpers.ts with Nullable, Paginated, ApiError, etc.
    */
    'generate_helpers' => true,

    /*
    |--------------------------------------------------------------------------
    | Date Type
    |--------------------------------------------------------------------------
    | How date/datetime columns are typed. 'string' is the safe default because
    | Laravel serialises dates as ISO strings over the wire.
    */
    'date_type' => 'string', // 'string' | 'Date'

    /*
    |--------------------------------------------------------------------------
    | Excluded Models
    |--------------------------------------------------------------------------
    | Models to skip. Accepts FQCNs or short class names.
    */
    'excluded_models' => [
        // 'App\Models\PersonalAccessToken',
    ],

    /*
    |--------------------------------------------------------------------------
    | Custom Type Map
    |--------------------------------------------------------------------------
    | Override the TypeScript type for specific cast classes.
    */
    'custom_type_map' => [
        // 'App\Casts\Money' => '{ amount: number; currency: string }',
    ],

    /*
    |--------------------------------------------------------------------------
    | Relationships
    |--------------------------------------------------------------------------
    | Include relationship methods as optional properties on generated types.
    */
    'include_relationships' => true,

    /*
    |--------------------------------------------------------------------------
    | Vendor Models
    |--------------------------------------------------------------------------
    | Include vendor models referenced by relations (e.g. notifications).
    */
    'include_vendor_models' => true,

    /*
    |--------------------------------------------------------------------------
    | Additional Models
    |--------------------------------------------------------------------------
    | Explicit model classes to always include in generation.
    */
    'additional_models' => [
      // 'Illuminate\Notifications\DatabaseNotification',
    ],

    /*
    |--------------------------------------------------------------------------
    | Read Migrations
    |--------------------------------------------------------------------------
    | Parse migration files to detect nullable columns accurately.
    | No database connection is required.
    */
    'read_migrations' => true,

];
```

---

Type Mapping
------------

[](#type-mapping)

PHP / Laravel castTypeScript type`int`, `integer`, `bigInteger``number``float`, `double`, `decimal`, `decimal:2``number``bool`, `boolean``boolean``string`, `char`, `text`, `uuid`, `ulid``string``date`, `datetime`, `timestamp``string` *(configurable to `Date`)*`immutable_date`, `immutable_datetime``string` *(configurable to `Date`)*`array`, `json`, `object``Record``collection``unknown[]``BackedEnum` (string-backed)Union of string literals`BackedEnum` (int-backed)Union of number literals`UnitEnum`Union of case name strings`AsCollection`, `AsArrayObject``unknown[]``AsStringable``string``AsEnumCollection:MyEnum``MyEnum[]`Custom cast (no `toTypeScript()`)`unknown`Casting Logic
-------------

[](#casting-logic)

The generator reads these sources in this order:

1. `$casts` for field type mapping
2. `$fillable` and `$dates` to discover fields
3. Migrations for `nullable()` columns
4. `$hidden` to exclude fields
5. Relationships (optional) to include related types

### Custom Casts

[](#custom-casts)

Custom casts default to `unknown`. You can override them in two ways:

1. **Config map** in `custom_type_map`:

```
// config/typegen.php
'custom_type_map' => [
  'App\Casts\Money' => '{ amount: number; currency: string }',
],
```

2. **Cast class method** by defining a `toTypeScript()` static method on the cast:

```
class MoneyCast
{
  public static function toTypeScript(): string
  {
    return '{ amount: number; currency: string }';
  }
}
```

Add a static `toTypeScript()` method to any custom cast class and the generator uses it automatically:

```
class MoneyCast implements CastsAttributes
{
    public static function toTypeScript(): string
    {
        return '{ amount: number; currency: string }';
    }

    // get() and set() ...
}
```

Or use the config map for third-party casts you can't modify:

```
'custom_type_map' => [
    'Brick\Money\Money' => '{ amount: number; currency: string }',
],
```

---

Generated Helpers
-----------------

[](#generated-helpers)

`model-helpers.ts` ships automatically with every project:

```
/** Marks a field as possibly null — mirrors Laravel's nullable() */
export type Nullable = T | null

/** A model primary key */
export type ModelId = number

/** Matches Laravel's LengthAwarePaginator JSON output */
export interface Paginated {
  data: T[]
  current_page: number
  last_page: number
  per_page: number
  total: number
  from: number | null
  to: number | null
  first_page_url: string
  last_page_url: string
  next_page_url: string | null
  prev_page_url: string | null
  path: string
  links: { url: string | null; label: string; active: boolean }[]
}

/** Standard Laravel validation error response */
export interface ApiError {
  message: string
  errors?: Record
}
```

---

Automating Generation
---------------------

[](#automating-generation)

### Before every Vite build

[](#before-every-vite-build)

```
{
  "scripts": {
    "typegen": "php artisan typegen:generate",
    "dev": "npm run typegen && vite",
    "build": "npm run typegen && vite build"
  }
}
```

### Pre-commit hook (Husky)

[](#pre-commit-hook-husky)

```
# .husky/pre-commit
php artisan typegen:generate
git add resources/js/types/models/
```

### CI — fail if types are stale

[](#ci--fail-if-types-are-stale)

```
- name: Generate TypeScript types
  run: php artisan typegen:generate

- name: Fail if types are out of sync
  run: git diff --exit-code resources/js/types/models/
```

This fails the pipeline if a developer changed a model and forgot to regenerate types, keeping your team honest without any manual process.

---

Roadmap
-------

[](#roadmap)

VersionFeature**v1.0**Model + migration scanning — everything documented here**v1.1**Zod schema output — generates `z.object({...})` alongside `.ts` types**v1.2**`--watch` mode — re-generates on model or migration file changes**v2.0****Laravel API Resource scanning** — reads `toArray()` in `JsonResource` classes to generate types that match exactly what your API *returns*, not just what the model *holds***v2.1**Spatie Laravel Data support**v3.0**Route + controller tracing — per-route types like Wayfinder> v1 is built for projects returning models directly or using simple arrays. v2 is for teams using full API Resource transformations — it's the correct source of truth for API-first Laravel.

---

FAQ
---

[](#faq)

**Does this require a database connection?**No. It reads your model classes and migration files from disk — no DB connection needed. Safe to run in CI without any environment setup.

**Does it work with Inertia.js?**Yes. Inertia passes Laravel model data as page props. Typed props are exactly what this package produces.

**What about models that use `$guarded = []` instead of `$fillable`?**The generator falls back to migration-detected columns. If you have neither `$fillable` nor migrations, it generates `id` and timestamps only. Running with migrations enabled is recommended for these cases.

**Can I exclude specific fields from output?**Add them to `$hidden` on the model. Hidden fields are never included in generated types.

**What if I use API Resources and only expose some fields?**v1 includes all non-hidden model fields. v2 (on the roadmap) fixes this by reading your `JsonResource::toArray()` directly.

**Does Svelte support TypeScript?**Yes, fully. Add `lang="ts"` to your `` tag. SvelteKit projects ship with TypeScript configured out of the box.

---

Contributing
------------

[](#contributing)

Contributions are welcome. To get started locally:

```
git clone https://github.com/VincentNdegwa/eloquent-typegen.git
cd eloquent-typegen

composer install

composer test       # run the test suite
composer analyse    # PHPStan static analysis
composer format     # Laravel Pint code style
```

Please write tests for any new behaviour. Open an issue before starting large changes so we can align on approach.

---

License
-------

[](#license)

MIT — see [LICENSE](LICENSE) for details.

---

Built by developers who were tired of TypeScript lying about their Laravel data.

**If this saves you time, give it a ⭐ — it helps others find it.**

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance95

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

7

Last Release

23d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/8fbb161baeebe9d09930a2d04bf9466d749174955fe974ac5da6d27b0d431f44?d=identicon)[VincentNdegwa](/maintainers/VincentNdegwa)

---

Top Contributors

[![VincentNdegwa](https://avatars.githubusercontent.com/u/116282941?v=4)](https://github.com/VincentNdegwa "VincentNdegwa (21 commits)")

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/vincentndegwa-eloquent-typegen/health.svg)

```
[![Health](https://phpackages.com/badges/vincentndegwa-eloquent-typegen/health.svg)](https://phpackages.com/packages/vincentndegwa-eloquent-typegen)
```

###  Alternatives

[laravel/ai

The official AI SDK for Laravel.

9782.1M153](/packages/laravel-ai)[illuminate/queue

The Illuminate Queue package.

20432.2M1.5k](/packages/illuminate-queue)[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[spatie/laravel-health

Monitor the health of a Laravel application

88011.3M149](/packages/spatie-laravel-health)[flarum/core

Delightfully simple forum software.

261.4M2.2k](/packages/flarum-core)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45344.0k1](/packages/pressbooks-pressbooks)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
