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

laraschema

v0.0.3

Published

Generate Laravel migrations, models, enums, and TypeScript declarations from Prisma schemas

Readme

LaraSchema

Generate Laravel migrations, Eloquent models, PHP enums, and TypeScript declarations from Prisma schemas.

LaraSchema is designed for Laravel projects that use Prisma as the schema authoring layer, while still needing Laravel-native output files that can be reviewed, customized, committed, and safely regenerated.

It can generate:

  • Laravel migration files
  • Eloquent model classes
  • PHP enum classes
  • TypeScript model and enum declarations
  • Custom output using project-owned stubs
  • Merge-safe updates that preserve manual edits where possible

Contents


Overview

LaraSchema reads your Prisma schema and generates Laravel-friendly source files from it.

The package is useful when you want Prisma to remain the schema design source of truth, but your Laravel application still needs normal Laravel files such as migrations, Eloquent models, enum classes, and frontend TypeScript declarations.

LaraSchema supports three generation paths:

Prisma schema
  -> Laravel migrations
  -> Eloquent models + PHP enums
  -> TypeScript declarations

It also includes a CLI for initialization, generation, stub customization, listing generated files, and cleaning generated outputs.


LaraSchema-Specific Additions

The current LaraSchema package includes several changes from the older Prisma-Laravel package line.

| Area | Current LaraSchema behavior | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | Command identity | The package exposes laraschema and lsh as CLI commands. | | Prisma provider identity | Generator entrypoints are exposed as laraschema-migrations, laraschema-models, and laraschema-types. | | Config file | CLI commands default to laraschema.config.js. | | Root-aware paths | rootDir can point output and backup resolution at the Laravel app root. | | Model casts | modeler.castMaps can override generated $casts, including callback-based mappings. | | Custom model directives | modeler.directives can parse project-specific Prisma comment directives and attach metadata to generated model properties. | | Modeler hooks | modeler.hooks can run custom functions after model and enum definitions are built. | | Generated notices | Built-in stubs include LaraSchema auto-generated file warnings. | | TypeScript output controls | noEmitEnums, modelsFileName, and enumsFileName control enum emission and bundle filenames. |


Quick Start

Install the package:

npm install laraschema --save-dev

Initialize LaraSchema for a Prisma schema:

laraschema init --schema=prisma/schema.prisma

This injects generator blocks into your Prisma schema, creates a laraschema.config.js file beside the schema, and scaffolds default stubs under the schema directory.

Run generation:

laraschema gen

Or use the short alias:

lsh gen

The generated files are written to the configured Laravel output folders.


Installation

npm install laraschema --save-dev

LaraSchema expects Prisma to be available in your project because laraschema gen can run prisma generate before running the Laravel and TypeScript generators.

npm install prisma --save-dev

CLI Commands

LaraSchema exposes the primary command:

laraschema

And a short alias:

lsh

The package also exposes Prisma generator entrypoints:

laraschema-migrations
laraschema-models
laraschema-types

init

Injects LaraSchema generator blocks into schema.prisma, scaffolds default stubs, and creates laraschema.config.js if it does not already exist.

laraschema init --schema=prisma/schema.prisma

gen

Runs Prisma generation first, then runs LaraSchema's migrations, models/enums, and TypeScript generators.

laraschema gen

Use a custom config file:

laraschema gen --config=prisma/laraschema.config.js

Skip the prisma generate step and only run LaraSchema generators:

laraschema gen --skipGenerate

customize

Creates per-table, per-model, per-enum, or per-TypeScript stub files from the matching index.stub.

laraschema customize -t migration,model -n users,accounts

Create a PHP enum stub override:

laraschema customize -t enum -n UserStatus

Create a TypeScript stub override:

laraschema customize -t ts -n User

Supported types:

migration
model
enum
ts

enum cannot be combined with other types.

list

Lists generated files.

laraschema list

List only migrations:

laraschema list --migrations

List only models:

laraschema list --models

List only enums:

laraschema list --enums

For migrations, show backup order when available:

laraschema list --migrations --sorted

clean

Deletes generated files and their backup baselines, then optionally regenerates.

laraschema clean

Clean only selected output types:

laraschema clean --types=migration,model

Clean selected names:

laraschema clean --types=model --names=User,Account

Preview without deleting:

laraschema clean --dry-run

Clean without regenerating:

laraschema clean --skipGenerate

Prisma Generator Blocks

LaraSchema uses Prisma generator blocks to discover generator-specific options.

A minimal schema setup looks like this:

generator migrations {
  provider = "laraschema-migrations"
  stubDir  = "./prisma/stubs"
  outputDir = "database/migrations"
}

generator models {
  provider = "laraschema-models"
  stubDir  = "./prisma/stubs"
  outputDir     = "app/Models"
  outputEnumDir = "app/Enums"
}

generator types {
  provider = "laraschema-types"
  outputDir = "resources/js/types"
}

The CLI init command can add these blocks for you.


Configuration

LaraSchema loads shared configuration from laraschema.config.js.

By default, CLI commands look for:

laraschema.config.js

When init is run with --schema=prisma/schema.prisma, it creates:

prisma/laraschema.config.js

You can pass a custom config path to commands that support --config:

laraschema gen --config=prisma/laraschema.config.js

Example laraschema.config.js

module.exports = {
  // Optional Laravel app root.
  // Useful when LaraSchema is run from a schema/package folder
  // but writes into a Laravel application elsewhere.
  rootDir: "../laravel-app",

  tablePrefix: "",
  tableSuffix: "",

  stubDir: "./prisma/stubs",

  output: {
    migrations: "database/migrations",
    models: "app/Models",
    enums: "app/Enums",
    ts: "resources/js/types",
  },

  migrate: {
    outputDir: "database/migrations",
    prettier: true,
    noEmit: false,
  },

  modeler: {
    outputDir: "app/Models",
    outputEnumDir: "app/Enums",
    prettier: true,
    overwriteExisting: true,

    // Use awobaz/compoships-style relations for composite keys.
    awobaz: false,

    // Extra fields allowed on pivot models.
    allowedPivotExtraFields: ["scope"],

    // Override Eloquent casts by Prisma type.
    castMaps: {
      Json: "Illuminate\\Database\\Eloquent\\Casts\\AsArrayObject::class",
      BigInt: ({ isList }) =>
        isList
          ? "Illuminate\\Database\\Eloquent\\Casts\\AsCollection::class"
          : "'string'",
    },

    // Optional project-specific directives parsed from Prisma comments.
    directives: {
      adminOnly: { style: "flag", targets: ["field"] },
      badge: { style: "parens", targets: ["field"] },
    },

    // Optional hooks run after model and enum definitions are built.
    hooks: ["./prisma/modeler-hooks.js"],
  },

  ts: {
    outputDir: "resources/js/types",
    declaration: false,
    noEmitEnums: false,
    shape: "interface",
    nullableAsOptional: false,
    readonlyArrays: false,
    modelsFileName: "index",
    enumsFileName: "enums",
    scalarMap: {
      BigInt: "bigint",
      Decimal: "string",
      Json: "unknown",
    },
  },
};

Main config keys

| Key | Purpose | | -------------------- | ------------------------------------------------------------------------ | | rootDir | Optional Laravel app root used to resolve generated outputs and backups. | | schemaPath | Optional Prisma schema path override used by laraschema gen. | | tablePrefix | Prefix added to generated physical table names. | | tableSuffix | Suffix added to generated physical table names. | | stubDir | Root folder for project-owned stubs. | | noEmit | Global dry-run switch. | | output.migrations | Default migration output folder. | | output.models | Default Eloquent model output folder. | | output.enums | Default PHP enum output folder. | | output.ts | Default TypeScript output folder. | | migrate | Migration generator overrides. | | modeler | Model and PHP enum generator overrides. | | modeler.castMaps | Custom Prisma-type to Eloquent-cast mappings. | | modeler.directives | Project-specific comment directive registry for model generation. | | modeler.hooks | Hook files or functions run after model/enums are built. | | ts | TypeScript generator overrides. |

export interface LaravelSharedConfig {
  rootDir?: string;
  schemaPath?: string;

  tablePrefix?: string;
  tableSuffix?: string;

  stubDir?: string;
  noEmit?: boolean;

  output?: {
    migrations?: string;
    models?: string;
    enums?: string;
    ts?: string;
  };

  migrate?: Partial<MigratorConfigOverride>;
  modeler?: Partial<ModelConfigOverride>;
  ts?: Partial<TypesConfigOverride>;
}

export type CastMapContext = {
  prismaType: string;
  isList?: boolean;
  ignore?: string[];
};

export type CastMapValue =
  | string
  | ((ctx: CastMapContext) => string);

export interface MigratorConfigOverride {
  tablePrefix?: string;
  tableSuffix?: string;
  stubDir?: string;
  outputDir?: string;
  overwriteExisting?: boolean;
  prettier?: boolean;
  groups?: string | StubGroupConfig[];
  noEmit?: boolean;
  namespace?: "App\\";
  rules?: string | Rule[];
  stubPath?: string;
  allowUnsigned?: boolean;
  defaultMaps?: DefaultMaps;
}

export interface ModelConfigOverride {
  tablePrefix?: string;
  tableSuffix?: string;
  stubDir?: string;
  outputDir?: string;
  overwriteExisting?: boolean;
  prettier?: boolean;
  groups?: string | StubGroupConfig[];
  noEmit?: boolean;
  namespace?: "App\\";
  modelStubPath?: string;
  enumStubPath?: string;
  outputEnumDir?: string;
  awobaz?: boolean;
  allowedPivotExtraFields?: string[];
  castMaps?: Record<string, CastMapValue>;
  modelNamespace?: string;
  enumNamespace?: string;
  directives?: CustomDirectiveRegistry;
  hooks?: Array<string | ModelerHook>;
}

export interface TypesConfigOverride {
  tablePrefix?: string;
  tableSuffix?: string;
  stubDir?: string;
  outputDir?: string;
  overwriteExisting?: boolean;
  prettier?: boolean;
  groups?: string | StubGroupConfig[];
  noEmit?: boolean;
  namespace?: "App\\";
  declaration?: boolean;
  noEmitEnums?: boolean;
  shape?: "interface" | "type";
  scalarMap?: Record<string, string>;
  nullableAsOptional?: boolean;
  readonlyArrays?: boolean;
  namePrefix?: string;
  nameSuffix?: string;
  moduleName?: string;
  modelsFileName?: string;
  enumsFileName?: string;
}

Output Paths and rootDir

By default, relative output paths are resolved from the current working directory.

If your Prisma schema package is not the Laravel app root, configure rootDir:

module.exports = {
  rootDir: "../laravel-app",

  output: {
    migrations: "database/migrations",
    models: "app/Models",
    enums: "app/Enums",
    ts: "resources/js/types",
  },
};

With this setup, LaraSchema writes to:

../laravel-app/database/migrations
../laravel-app/app/Models
../laravel-app/app/Enums
../laravel-app/resources/js/types

Backup baselines are also stored under the resolved root:

<rootDir>/.laraschema/backups

This makes generation predictable when your schema and Laravel application live in different folders.


Stub Customization

LaraSchema ships default stubs in the package root stubs/ folder.

The built-in stubs include an auto-generated notice so generated files are easy to recognize during review. The notice warns that the file was generated by LaraSchema and that direct edits may be overwritten by future generator runs.

Project-owned stubs are usually placed under the Prisma schema folder:

prisma/
  schema.prisma
  laraschema.config.js
  stubs/
    migration/
      index.stub
    model/
      index.stub
    enum/
      index.stub
    ts/
      index.stub

The actual package stub templates remain outside src/.

Stub resolution order

For a generated item, LaraSchema resolves stubs in this order:

1. Direct item stub
   stubs/<type>/<name>.stub

2. First matching group stub
   stubs/<type>/<group-stub>.stub

3. Default stub
   stubs/<type>/index.stub

Examples:

prisma/stubs/migration/users.stub
prisma/stubs/model/User.stub
prisma/stubs/enum/UserStatus.stub
prisma/stubs/ts/User.stub

Create stub overrides with the CLI

laraschema customize -t migration -n users
laraschema customize -t model -n User
laraschema customize -t enum -n UserStatus
laraschema customize -t ts -n User

Stub Groups

Stub groups allow many tables, models, enums, or TS outputs to share a custom stub.

module.exports = {
  migrate: {
    groups: [
      {
        stubFile: "auth.stub",
        tables: ["users", "accounts", "password_resets"],
      },
      {
        stubFile: "audit.stub",
        pattern: /^audit_/,
        exclude: ["audit_archive"],
      },
      {
        stubFile: "catch-all.stub",
        include: "*",
        exclude: ["failed_jobs", "migrations"],
      },
    ],
  },
};

Supported selectors:

| Key | Meaning | | --------- | ------------------------------------------------------ | | tables | Exact list of names. | | include | Glob list or "*". | | exclude | Names or globs removed after include/pattern matching. | | pattern | RegExp, glob string, or array of either. |


Merge-Safe Writing

LaraSchema does not blindly overwrite generated files.

It writes with a git-style 3-way merge:

base   = last generated baseline stored in .laraschema/backups
yours  = current file on disk
theirs = newly generated file

When there are no conflicts, changes are merged automatically.

When there is a real conflict, the file is written with conflict markers:

<<<<<<<
current file changes
=======
new generator output
>>>>>>>

After a successful write, LaraSchema updates the backup baseline.

This applies to:

  • migrations
  • models
  • PHP enums
  • TypeScript outputs

Migrations Generator

The migrations generator converts Prisma models into Laravel migration files.

It supports:

  • scalar column mapping
  • native type mapping
  • nullable fields
  • defaults
  • enums
  • UUID and ULID detection
  • autoincrement IDs
  • unsigned integer inference
  • relations and foreign keys
  • referential actions
  • composite indexes
  • composite unique keys
  • fulltext indexes
  • custom migration rules
  • stub customization
  • merge-safe updates

Example generator block:

generator migrations {
  provider = "laraschema-migrations"
  stubDir  = "./prisma/stubs"
  outputDir = "database/migrations"
}

Example output:

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamps();
});

Unsigned handling

LaraSchema infers unsigned integer columns for IDs, generated integer keys, native unsigned integer types, @unsigned documentation directives, and foreign keys pointing to unsigned integer references.

Use allowUnsigned when needed:

module.exports = {
  migrate: {
    allowUnsigned: true,
  },
};

Silent models

Use @silent(migrator) or @silent on a model when you want the model parsed but no migration emitted.

/// @silent(migrator)
model ExternalAuditLog {
  id Int @id @default(autoincrement())
  message String
}

Models and Enums Generator

The model generator creates Eloquent model classes and PHP enum classes.

It supports:

  • $table
  • $fillable
  • $hidden
  • $guarded
  • $casts
  • enum casts
  • custom cast maps
  • custom model directives
  • modeler hooks
  • $with
  • $touches
  • $appends
  • traits
  • parent class extension
  • implemented interfaces
  • observers
  • factories
  • belongsTo
  • hasOne
  • hasMany
  • belongsToMany
  • morphTo
  • morphOne
  • morphMany
  • morphToMany
  • morphedByMany
  • explicit pivot metadata
  • constrained pivot detection with @entity
  • Compoships-aware relation arguments

Example generator block:

generator models {
  provider = "laraschema-models"
  stubDir  = "./prisma/stubs"
  outputDir     = "app/Models"
  outputEnumDir = "app/Enums"
}

Explicit many-to-many pivot detection

LaraSchema has automatic explicit many-to-many detection for models that look like pivot tables.

This is different from Prisma's implicit many-to-many relation. It exists for cases where your Prisma schema has a real model between two other models, and LaraSchema can recognize that model as the pivot table behind a Laravel belongsToMany(...) relation.

For example:

model User {
  id          Int          @id @default(autoincrement())
  memberships Membership[]
}

model Team {
  id          Int          @id @default(autoincrement())
  memberships Membership[]
}

model Membership {
  user   User @relation(fields: [userId], references: [id])
  userId Int

  team   Team @relation(fields: [teamId], references: [id])
  teamId Int

  role String?

  @@unique([userId, teamId])
}

When LaraSchema is generating relations for User, it sees that User.memberships points to Membership. It then asks: “Is Membership actually a pivot between User and one other model?”

A model is accepted as an explicit pivot candidate only when all of these are true:

  1. The candidate model has relation fields that own foreign keys.
  2. Exactly one owned relation points back to the model currently being resolved.
  3. There is exactly one other relation endpoint.
  4. The foreign keys for both sides do not overlap.
  5. The union of both foreign-key sets is unique on the candidate model, usually through @@unique([...]) or a primary key.

If those checks pass, LaraSchema treats the candidate as an explicit pivot and generates a belongsToMany relation to the other endpoint instead of treating the candidate model as the final related model.

So, from User, the generator may treat Membership as a pivot and generate a relation to Team.

Why @entity exists

Sometimes a model looks like a pivot table structurally, but it is actually a real domain model.

For example, Membership may have its own lifecycle, status, permissions, billing rules, invitation flow, approval state, or admin page. In that case, you may not want User.memberships to turn into a direct belongsToMany(Team::class, ...) relationship. You may want it to stay as a normal relation to the Membership model.

Use @entity to tell LaraSchema:

This model is a real entity. Do not collapse it into a many-to-many pivot relation.
/// @entity
model Membership {
  id Int @id @default(autoincrement())

  user   User @relation(fields: [userId], references: [id])
  userId Int

  team   Team @relation(fields: [teamId], references: [id])
  teamId Int

  role String?

  @@unique([userId, teamId])
}

With plain @entity, LaraSchema will not use Membership as an explicit pivot candidate for any owner model. It will behave as a real related model.

Scoped @entity(ModelName)

@entity can also be scoped to specific owner models.

/// @entity(User)
model Membership {
  id Int @id @default(autoincrement())

  user   User @relation(fields: [userId], references: [id])
  userId Int

  team   Team @relation(fields: [teamId], references: [id])
  teamId Int

  @@unique([userId, teamId])
}

This means:

When resolving relations for User, do not treat Membership as a pivot.
For other models, LaraSchema may still treat Membership as a pivot if it passes the pivot checks.

So the behavior is:

| Directive | Meaning | | -------------------- | ------------------------------------------------------------------------------------------ | | No @entity | Candidate can be collapsed into an explicit belongsToMany if it passes the pivot checks. | | @entity | Candidate is never treated as an explicit pivot. | | @entity(User) | Candidate is not treated as a pivot while resolving User relations. | | @entity(User,Team) | Candidate is not treated as a pivot while resolving User or Team relations. |

Syntax forms

@entity uses the shared list parser, so these scoped forms are equivalent:

/// @entity(User,Team)
/// @entity{User,Team}
/// @entity: User,Team

Use plain @entity when the model should always stay a real entity.

Use scoped @entity(ModelName) when you only want to block pivot collapsing from one side.

Important notes

@entity only affects explicit pivot detection in the model relationship generator.

It does not:

  • remove the model from migrations
  • remove the model file
  • remove TypeScript types
  • act like @silent
  • act like @local
  • change the database schema

It only changes whether a relation that points at the candidate model is interpreted as:

normal relation to the candidate model

or collapsed into:

belongsToMany relation through the candidate model

Use @entity when the model is conceptually important on its own, even if its foreign keys make it look like a pivot.

PHP enum output

Prisma enums can be emitted as PHP enum classes.

enum UserStatus {
  active
  inactive
  blocked
}

Example generated PHP enum:

<?php

namespace App\Enums;

enum UserStatus: string
{
    case active = 'active';
    case inactive = 'inactive';
    case blocked = 'blocked';
}

Cast maps

Use modeler.castMaps to override casts by Prisma type.

module.exports = {
  modeler: {
    castMaps: {
      Json: "Illuminate\\Database\\Eloquent\\Casts\\AsArrayObject::class",
      JsonB: "Illuminate\\Database\\Eloquent\\Casts\\AsArrayObject::class",
      BigInt: ({ isList }) =>
        isList
          ? "Illuminate\\Database\\Eloquent\\Casts\\AsCollection::class"
          : "'string'",
    },
  },
};

The callback receives runtime context:

type CastMapContext = {
  prismaType: string;
  isList?: boolean;
  ignore?: string[];
};

Custom Model Directives

LaraSchema has built-in directives such as @fillable, @hidden, @cast, @local, and @entity.

For project-specific metadata, use modeler.directives.

Custom directives do not replace the built-in LaraSchema directives. They run beside them, are parsed from Prisma documentation comments, and are attached to generated model/property metadata so your hooks or custom stubs can consume them.

Define custom directives

module.exports = {
  modeler: {
    directives: {
      adminOnly: {
        style: "flag",
        targets: ["field"],
      },

      badge: {
        style: "parens",
        targets: ["field"],
      },

      ui: {
        style: "braces",
        targets: ["field"],
      },

      panel: {
        style: "colon",
        targets: ["model"],
      },
    },
  },
};

Use custom directives in Prisma comments

/// @panel: admin
model User {
  id Int @id @default(autoincrement())

  /// @adminOnly
  email String

  /// @badge(primary,verified)
  status String

  /// @ui{color,icon}
  role String
}

Supported custom directive styles

| Style | Example | Parsed value by default | | -------- | -------------------------- | ------------------------------- | | flag | @adminOnly | true | | parens | @badge(primary,verified) | ['primary', 'verified'] | | braces | @ui{color,icon} | ['color', 'icon'] | | colon | @panel: admin | 'admin' | | auto | Any supported shape | Inferred from the syntax used. | | custom | User-defined | Whatever your resolver returns. |

Custom directive definitions can restrict valid targets with targets:

model
field
relation
enum
unknown

Custom resolver

Use resolve(ctx) when the default value is not enough.

module.exports = {
  modeler: {
    directives: {
      ui: {
        style: "braces",
        targets: ["field"],
        resolve(ctx) {
          return {
            name: ctx.name,
            style: ctx.style,
            target: ctx.target,
            raw: ctx.raw,
            body: ctx.body,
          };
        },
      },
    },
  },
};

Custom directive metadata is intended for advanced stubs and hooks. Use built-in directives for built-in LaraSchema behavior, and custom directives for project-specific metadata that LaraSchema should preserve for your own extension layer.


Modeler Hooks

modeler.hooks lets you run custom code after LaraSchema has built model and enum definitions.

Hooks are useful when you want side outputs or post-processing without changing the core generator behavior.

module.exports = {
  modeler: {
    hooks: ["./prisma/modeler-hooks.js"],
  },
};

A hook file should export a function as the default export, as hook, or as the module itself.

export default async function hook(ctx) {
  await ctx.writeJson("storage/laraschema/models.json", {
    models: ctx.models.map((model) => model.className),
    enums: ctx.enums.map((item) => item.name),
  });
}

The hook context includes the generated model and enum definitions, the active modeler config, and helper methods:

| Helper | Purpose | | -------------------------------- | -------------------------------------------------- | | writeFile(targetPath, content) | Writes a text file, creating parent folders first. | | writeJson(targetPath, value) | Writes formatted JSON with a trailing newline. |

Hook paths are resolved from the current working directory.

Use hooks for side outputs such as JSON manifests, documentation data, UI metadata, or package-specific helper files. Avoid using hooks to mutate generated PHP output unless you are intentionally building a higher-level extension around LaraSchema.


TypeScript Generator

The TypeScript generator creates frontend-friendly model and enum declarations from your Prisma schema.

Example generator block:

generator types {
  provider = "laraschema-types"
  outputDir = "resources/js/types"
}

Example config:

module.exports = {
  ts: {
    outputDir: "resources/js/types",
    declaration: false,
    noEmitEnums: false,
    shape: "interface",
    nullableAsOptional: false,
    readonlyArrays: false,
    modelsFileName: "index",
    enumsFileName: "enums",
    scalarMap: {
      BigInt: "bigint",
      Decimal: "string",
      Json: "unknown",
    },
  },
};

Supported options:

| Key | Purpose | | -------------------- | -------------------------------------------------------------------------------------------------------------- | | outputDir | Output directory for generated TS files. | | declaration | Controls the enum bundle extension: .d.ts when true, .ts when false. Models are always emitted as .d.ts. | | noEmitEnums | Skip only the enum bundle; model declarations are still generated unless noEmit is true. | | shape | Use interface or type for model shapes. | | scalarMap | Override Prisma scalar to TS type mapping. | | nullableAsOptional | Emit nullable fields as optional properties. | | readonlyArrays | Emit lists as ReadonlyArray<T>. | | namePrefix | Prefix generated model names. | | nameSuffix | Suffix generated model names. | | moduleName | Optional module wrapper name. | | modelsFileName | Base filename for the main model declaration bundle. Defaults to index, producing index.d.ts. | | enumsFileName | Base filename for the enum bundle. Defaults to enums, producing enums.ts or enums.d.ts. |


Prisma Comment Directives

LaraSchema uses Prisma documentation comments to customize generated Laravel and TypeScript output without changing database semantics.

Directives can be written above a field, inline beside a field, or at model/enum level depending on the directive.

model User {
  /// @fillable
  name String

  email String /// @fillable @hidden
}

Directive syntax styles

Some directives support more than one list syntax. This is intentional: the shared directive list parser accepts braces, parentheses, and colon form for supported list-style directives.

These three forms are equivalent for list-style directives:

/// @fillable{name,email}
/// @fillable(name,email)
/// @fillable: name,email

The same applies to supported list-style directives such as:

@fillable
@hidden
@guarded
@with
@touch
@pivot
@pivotAlias
@entity
@local
@silent

For example, all of these are valid ways to declare eager-loaded relations:

/// @with(posts,roles)
/// @with{posts,roles}
/// @with: posts,roles
model User {
  id Int @id @default(autoincrement())
}

And all of these are valid ways to declare appended attributes:

/// @appends(full_name,avatar_url)
/// @appends{full_name,avatar_url}
/// @appends: full_name,avatar_url
model User {
  id Int @id @default(autoincrement())
}

@appends also supports optional TypeScript types per entry:

/// @appends(full_name:string, meta:Record<string, unknown>)
model User {
  id Int @id @default(autoincrement())
}

Syntax groups

| Syntax group | Directives | Supported forms | | --------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | Boolean flags | @fillable, @hidden, @guarded, @with, @pivot, @withTimestamps | Plain presence, for example /// @fillable. | | Flexible lists | @fillable, @hidden, @guarded, @with, @touch, @pivot, @pivotAlias, @entity, @local, @silent | @tag{a,b}, @tag(a,b), @tag: a,b. | | Appended attributes | @appends | @appends{a,b}, @appends(a,b), @appends: a,b; entries may be name or name:type. | | Structured field type | @type | Brace object syntax only: @type{ import:'...', type:'...' }. | | Field cast | @cast | Brace syntax only: @cast{datetime} or @cast{decimal:2}. | | Class references | @trait, @use, @implements, @observer, @factory, @extend | Colon syntax only: @trait:Foo\Bar, optionally as Alias where supported. | | Morph relations | @morph | Parentheses syntax: @morph(name: commentable, type: many, model: Comment). |

Directive summary

| Directive | Scope | Purpose | | ------------------ | ---------------------------- | ----------------------------------------------------------------------- | | @fillable | Field or model | Adds field(s) to $fillable. | | @hidden | Field or model | Adds field(s) to $hidden. | | @guarded | Field or model | Adds field(s) to $guarded. | | @cast{...} | Field | Adds a cast entry to $casts. | | @type{...} | Field | Provides PHP/interface/TS type metadata. | | @with | Field or model | Adds relation(s) to $with; TS relation becomes non-optional. | | @trait:... | Model | Adds a trait import/use. | | @use:... | Model | Adds a raw import/use line. | | @extend:... | Model | Changes parent class. | | @implements:... | Model | Adds implemented interface. | | @observer:... | Model | Adds observer metadata. | | @factory:... | Model | Adds factory metadata. | | @touch{...} | Model | Adds $touches. | | @appends{...} | Model | Adds $appends and TS appended properties. | | @local | Relation field | Skips a specific relation method and/or FK generation. | | @silent | Model or enum | Parses but does not emit selected output. | | @morph(...) | Model | Declares owner-side polymorphic relations. | | @pivot | Pivot model or pivot field | Includes extra pivot columns in withPivot(...). | | @withTimestamps | Pivot model | Adds withTimestamps() to relation chain. | | @pivotAlias(...) | Pivot model | Adds as('name') to relation chain. | | @entity | Potential pivot/entity model | Prevents or constrains automatic explicit many-to-many pivot detection. |

These model-level directives use the shared list parser:

@fillable
@hidden
@guarded
@with
@touch

So each one can use braces, parentheses, or colon form:

/// @fillable{name,email}
/// @hidden(password,remember_token)
/// @guarded: id,created_at,updated_at
/// @with(posts,roles)
/// @touch{company,profile}
model User {
  id Int @id @default(autoincrement())
}

Field-level boolean forms are still valid:

model User {
  name String /// @fillable
  email String /// @hidden
}

@cast is currently parsed with brace syntax:

model User {
  emailVerifiedAt DateTime? /// @cast{datetime}
  balance Decimal /// @cast{decimal:2}
}

@type is currently parsed with structured brace syntax:

model User {
  meta Json? /// @type{ import:'App\\Data\\UserMeta', type:'UserMeta' }
}

Use braces for these two directives. Do not write them as colon or parentheses unless the parser is intentionally expanded later.

Class/reference directives use colon syntax:

/// @trait:Illuminate\\Notifications\\Notifiable
/// @use:App\\Support\\SomeHelper
/// @implements:Illuminate\\Contracts\\Auth\\Authenticatable as AuthenticatableContract
/// @observer:App\\Observers\\UserObserver
/// @factory:Database\\Factories\\UserFactory
/// @extend:Illuminate\\Foundation\\Auth\\User as Authenticatable
model User {
  id Int @id @default(autoincrement())
}

Where supported, as Alias controls the imported short name used in the generated model.

Use @local on a relation field to skip generation for that relation.

model Account {
  id     Int   @id @default(autoincrement())
  user   User? @relation(fields: [userId], references: [id]) /// @local
  userId Int?
}

Scoped forms can use parentheses, braces, or colon form:

/// @local(model)
/// @local{migrator}
/// @local: model,migrator

Supported scope values:

model
models
modeler
migrator
migration
migrations
both
all
*

Use @silent to parse a model or enum but suppress file emission.

/// @silent
model AuditTrail {
  id Int @id @default(autoincrement())
  note String
}

Scoped forms can use parentheses, braces, or colon form:

/// @silent(model)
/// @silent{migrator}
/// @silent: model,migrator

Supported scope values:

model
models
modeler
migrator
migration
migrations
both
all
*

Use @appends on a model to add appended attributes.

/// @appends(full_name,avatar_url)
model User {
  id Int @id @default(autoincrement())
}

The parser accepts parentheses, braces, and colon form:

/// @appends(full_name,avatar_url)
/// @appends{full_name,avatar_url}
/// @appends: full_name,avatar_url

Each entry may include a TypeScript type after the first colon:

/// @appends(full_name:string, stats:Record<string, unknown>)
model User {
  id Int @id @default(autoincrement())
}

Child-side morphTo is auto-detected from scalar pairs:

model Comment {
  id Int @id @default(autoincrement())
  commentable_id Int
  commentable_type String
}

This can generate:

public function commentable()
{
    return $this->morphTo('commentable');
}

Owner-side relations can be declared with @morph(...):

/// @morph(name: commentable, type: many, model: Comment)
model Post {
  id Int @id @default(autoincrement())
}

Supported type values:

one      -> morphOne
many     -> morphMany
to many  -> morphToMany
by many  -> morphedByMany

Use @pivot to include extra pivot columns in generated withPivot(...) chains.

Because model-level @pivot uses the shared list parser, these forms are accepted:

/// @pivot(role,meta)
/// @pivot{role,meta}
/// @pivot: role,meta
model TeamUser {
  teamId Int
  userId Int
  role   String
  meta   Json?
}

Field-level flag form is also supported:

role String /// @pivot
meta Json?  /// @pivot

@pivotAlias also uses the shared list parser; the first parsed value is used:

/// @pivotAlias(membership)
/// @pivotAlias{membership}
/// @pivotAlias: membership
model TeamUser {
  teamId Int
  userId Int
}

@withTimestamps is a plain flag:

/// @withTimestamps
model TeamUser {
  teamId Int
  userId Int
}

Custom Migration Rules

You can extend migration rendering with custom rules.

module.exports = {
  migrate: {
    rules: "./prisma/custom-rules.js",
  },
};

Example prisma/custom-rules.js:

module.exports = [
  {
    test(def) {
      return def.name === "archived" && def.migrationType === "boolean";
    },
    render() {
      return {
        column: "archived",
        snippet: ["$table->boolean('archived')->default(false);"],
      };
    },
  },
];

Rule execution order:

1. Built-in rules
2. Custom rules

Advanced Examples

/// @trait:Illuminate\\Notifications\\Notifiable
/// @implements:Illuminate\\Contracts\\Auth\\Authenticatable as AuthenticatableContract
/// @observer:App\\Observers\\UserObserver
/// @factory:UserFactory
/// @with(posts,roles)
/// @touch{company,profile}
/// @appends{full_name,avatar_url}
model User {
  id Int @id @default(autoincrement())

  /// @fillable
  name String

  /// @fillable @hidden
  email String

  /// @guarded
  password String

  /// @cast{datetime}
  emailVerifiedAt DateTime?
}
module.exports = {
    ts: {
        scalarMap: {
            BigInt: "bigint",
            Decimal: "string",
            Json: "Record<string, unknown>",
            DateTime: "string",
        },
    },
};
${imports}

${content(() => ({
    append: `export type ${model.name}Id = ${model.name}["id"];`,
}))}
<?php

namespace App\Models;

${model.imports}

use Illuminate\Database\Eloquent\Model;

class ${model.className} extends Model
{
    protected $table = '${model.tableName}';

    protected $fillable = [
${model.properties.filter(p => p.fillable).map(p => `        '${p.name}',`).join('\n')}
    ];

    protected $casts = [
${model.properties.filter(p => p.cast).map(p => `        '${p.name}' => ${p.cast},`).join('\n')}
${model.properties.filter(p => p.enumRef).map(p => `        '${p.name}' => ${p.enumRef}::class,`).join('\n')}
    ];

${relationships}

    ${content}
}

Troubleshooting

Config file not found

By default, CLI commands look for:

laraschema.config.js

Pass the config path explicitly if your config is inside prisma/:

laraschema gen --config=prisma/laraschema.config.js

Generated files are written to the wrong folder

Set rootDir:

module.exports = {
    rootDir: "../laravel-app",
};

Prisma cannot find the generator provider

Make sure the package is installed and the generator block uses the current provider names:

provider = "laraschema-migrations"
provider = "laraschema-models"
provider = "laraschema-types"

Stubs are not being used

Check that stubDir points to the folder containing migration/, model/, enum/, and ts/ folders.

prisma/stubs/migration/index.stub
prisma/stubs/model/index.stub
prisma/stubs/enum/index.stub
prisma/stubs/ts/index.stub

Merge conflicts appear in generated files

LaraSchema detected real divergence between your edits and the new generated output.

Open the file, resolve:

<<<<<<<
=======
>>>>>>>

Then run generation again.

TypeScript files are not generated

Check that the types generator block exists:

generator types {
  provider = "laraschema-types"
  outputDir = "resources/js/types"
}

And confirm ts.noEmit is not enabled.


Package Notes

The package keeps source and templates separate:

src/      TypeScript source only
stubs/    Published stub templates
types/    Global type declarations

The published package should include:

dist/
stubs/
README.md
LICENSE

The root stubs/ folder must remain outside src/ so compiled package code can resolve templates from the package root.


Migrating from the older Prisma-Laravel package

Older documentation and examples may mention names such as:

prisma-laravel-migrate
prisma-laravel-cli
prisma/laravel.config.js
prisma-laravel-migrations
prisma-laravel-models
prisma-laravel-types
.prisma-laravel/backups

Current LaraSchema examples should use:

laraschema
lsh
laraschema.config.js
laraschema-migrations
laraschema-models
laraschema-types
.laraschema/backups

The goal of LaraSchema is the same core workflow: generate Laravel-native files from Prisma schemas while keeping stubs, config, and merge behavior customizable.