abetwothree / laravel-ts-publish
Create TypeScript declaration types from your PHP models, enums, and other cast classes
Fund package maintenance!
Requires
- php: ^8.4
- composer/class-map-generator: ^1.7
- illuminate/contracts: ^13.0||^12.0||^11.0
- nikic/php-parser: ^5.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- archtechx/enums: ^1.1
- awobaz/compoships: ^2.5
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^11.0||^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- dev-main
- v1.3.0
- v1.2.1
- v1.2.0
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.1
- v1.0.0
- v0.0.11
- v0.0.10
- v0.0.9
- v0.0.8
- v0.0.7
- v0.0.6
- v0.0.5
- v0.0.4
- V0.0.3
- v0.0.2
- v0.0.1
- v0.0.0
- dev-v2-routing
- dev-model-less-resource-support
- dev-appends-to-model-resource-extends
- dev-dependabot/github_actions/dependabot/fetch-metadata-3.0.0
- dev-dependabot/github_actions/ramsey/composer-install-4
This package is auto-updated.
Last update: 2026-04-02 03:27:57 UTC
README
This is an extremely flexible package that allows you to transform Laravel PHP models, enums, API resources, and other cast classes into TypeScript declaration types.
Enums are treated as functional objects with support for PHP-like enum functions and the the inclusion of your custom methods.
Every Laravel application is different, and this package aims to provide the tools to tailor TypeScript types to your specific needs while providing powerful backend & frontend tooling to keep your frontend types in sync with your backend PHP code.
For examples of the generated TypeScript output, see these output examples.
Table of Contents
- 📦 Installation
- 🚀 Usage
- 🏷️ Enums
- 🗃️ Models
- 📡 API Resources
- 🧬 Extending Interfaces
- ❌ Excluding Content
- 🔤 Casing Configurations
- 🌐 Enum API Resource
- 📂 Modular Publishing
- 🔧 Customizing the Pipeline
- ⚡ Pre-Command Hook
- ⚙️ Configuration Reference
Installation
Requires PHP 8.4+ and supports Laravel 13, 12 or 11
You can install the package via composer:
composer require abetwothree/laravel-ts-publish
You can publish the config file with:
php artisan vendor:publish --tag="ts-publish-config"
Optionally, you can publish the views using:
php artisan vendor:publish --tag="laravel-ts-publish-views"
Usage
Publishing Types
You can publish your TypeScript declaration types using the ts:publish Artisan command:
php artisan ts:publish
By default, the generated TypeScript declaration types will be saved to the resources/js/types/data/ directory and follow default configuration settings.
Additionally, by default, the package will look for models in the app/Models directory, enums in the app/Enums directory, and API resources in the app/Http/Resources directory. You can customize these settings in the published configuration file.
For a full installation and setup guide, see the Installation & Setup documentation.
Preview Mode
You can preview the generated TypeScript output in the console without writing any files by using the --preview flag:
php artisan ts:publish --preview
This is useful for debugging or reviewing what will be generated before committing to file output.
Single-File Republishing
You can republish a single enum, model, or resource instead of the entire set by using the --source option with a fully-qualified class name or file path:
php artisan ts:publish --source="App\Enums\Status" php artisan ts:publish --source="app/Enums/Status.php" php artisan ts:publish --source="App\Http\Resources\UserResource"
This is significantly faster than a full publish on large projects and is used automatically by the Vite plugin to republish only the file that changed during development.
Automatic Publishing After Migrations
By default, this package will automatically re-publish your TypeScript declaration types after running migrations. This ensures your TypeScript types stay in sync with your database schema changes.
You can disable this behavior in the config file or via environment variable:
// config/ts-publish.php 'run_after_migrate' => false,
TS_PUBLISH_RUN_AFTER_MIGRATE=false
Filtering Models, Enums & Resources
You can fully customize which models, enums, and resources are included or excluded, and add additional directories to search in. By default, all models in app/Models, all enums in app/Enums, and all resources in app/Http/Resources are included.
// config/ts-publish.php // Only publish these specific models (leave empty to include all) 'included_models' => [ App\Models\User::class, App\Models\Post::class, ], // Exclude specific models from publishing 'excluded_models' => [ App\Models\Pivot::class, ], // Search additional directories for models 'additional_model_directories' => [ 'modules/Blog/Models', ],
The same options are available for enums with included_enums, excluded_enums, and additional_enum_directories, and for resources with included_resources, excluded_resources, and additional_resource_directories.
Tip
Include and exclude settings accept both fully-qualified class names and directory paths. When a directory is provided, all matching classes within it will be discovered automatically.
Conditional Publishing
You can choose to publish only enums, only models, or only resources, either through configuration or command flags.
Via Configuration
Disable enum, model, or resource publishing entirely in the config file:
// config/ts-publish.php 'publish_enums' => true, 'publish_models' => true, 'publish_resources' => true,
Setting any to false will skip that type on every run, including automatic post-migration publishing.
Via Command Flags
Use the --only-enums, --only-models, or --only-resources flags to limit a single run:
php artisan ts:publish --only-enums php artisan ts:publish --only-models php artisan ts:publish --only-resources
These flags cannot be combined — passing any two together will return an error.
Config & Flag Conflicts
When a command flag requests a type that is disabled in config (e.g. --only-enums while publish_enums is false), the command will prompt you to confirm whether to override the config setting. In non-interactive environments (CI, queued jobs, post-migration hooks), the config value is respected and the command exits gracefully.
If all types end up disabled (all config values are false and no override flag is given), the command prints a warning and exits with a success status.
Verbosity Levels
The ts:publish command supports three verbosity levels using the standard Artisan verbosity flags:
| Flag | Output |
|---|---|
--quiet / -q |
No output at all — only the exit code indicates success or failure. Ideal for automated tooling like the Vite plugin. |
| (default) | A compact summary showing the output directory, file counts, and any extra files generated (barrels, globals, JSON). |
--verbose / -v |
Detailed tables listing every generated file with per-file metadata (cases, methods, columns, mutators, relations). |
# Compact summary (default) php artisan ts:publish # Detailed tables php artisan ts:publish -v # Silent — for scripts, CI, or the Vite plugin php artisan ts:publish --quiet
In quiet mode, files are still generated normally — only console output is suppressed. The Vite plugin passes --quiet by default since it only needs the exit code.
Enums
This package, like others before it (spatie/typescript-transformer and modeltyper), can convert enums from PHP to TypeScript for each enum case.
However, PHP enums do not solely consist of enum cases, but can also have methods and static methods that have valuable data to use on the frontend. This package allows you to use these features of PHP enums and publish the return values of these methods in TypeScript as well.
By default, this package will only publish the enum cases and their values to TypeScript, but you can use the provided attributes to specify that you want to call certain methods or static methods and publish their return values in TypeScript as well. See below.
Alternatively, you can enable the auto_include_enum_methods and auto_include_enum_static_methods config options to automatically include all public methods without needing to add attributes. See Auto-Including All Enum Methods for details.
Tip
This package also provides an EnumResource JSON resource that lets you return a flattened, instance-specific representation of any enum case from your API routes. See JSON Enum HTTP API Resource for details.
Note
Whether you use the attributes or the global config options, only public methods are ever included. Private and protected methods are always excluded.
Enum Attributes
To use the more advanced transforming features provided by this package for enums, you'll need to use the PHP Attributes described below.
All attributes can be found at this link and are under the AbeTwoThree\LaravelTsPublish\Attributes namespace.
List of enum attributes & descriptions:
| Attribute | Target | Description |
|---|---|---|
#[TsEnumMethod] |
Method | Include a method's return values in the TypeScript output. Called per enum case, creates a key/value pair object. |
#[TsEnumStaticMethod] |
Static Method | Include a static method's return value in the TypeScript output. Called once, added as a property on the enum. |
#[TsEnum] |
Enum Class | Rename the enum or add a description when converting to TypeScript. Useful to avoid naming conflicts across namespaces. |
#[TsCase] |
Enum Case | Rename, change the frontend value, or add a description to an enum case. |
#[TsExclude] |
Class, Method | Exclude an entire enum or specific enum methods from the TypeScript output. See Excluding with TsExclude. |
Enum Method #[TsEnumMethod]
Using the TsEnumMethod attribute to specify that the label() method should be called for each enum case value and the return value should be used as the value for the enum case in TypeScript:
use AbeTwoThree\LaravelTsPublish\Attributes\TsEnumMethod; enum Status: string { case Active = 'active'; case Inactive = 'inactive'; #[TsEnumMethod] public function label(): string { return match($this) { self::Active => 'Active User', self::Inactive => 'Inactive User', }; } }
Generated TypeScript declaration type:
export const Status = { Active: 'Active User', Inactive: 'Inactive User', label: { Active: 'Active User', Inactive: 'Inactive User', } } as const;
The #[TsEnumMethod] attribute accepts optional name, description, and params parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
string |
Method name | Customize the key name used in the TypeScript output |
description |
string |
'' |
Added as a JSDoc comment above the method output |
params |
array |
[] |
Named arguments to pass when invoking the method (see example below) |
#[TsEnumMethod(name: 'statusLabel', description: 'Human-readable label for this status')] public function label(): string { return match($this) { self::Active => 'Active User', self::Inactive => 'Inactive User', }; }
Methods with Required Parameters
Methods that require parameters are skipped by default — they will not appear in the generated TypeScript output. This prevents producing misleading null values for methods that can't be called without arguments.
To include a method that requires parameters, use the params property on the attribute to provide named arguments:
enum Priority: int { case Low = 0; case Medium = 1; case High = 2; #[TsEnumMethod(description: 'Compare with threshold', params: ['threshold' => 1])] public function isAboveThreshold(int $threshold): bool { return $this->value > $threshold; } }
Generated TypeScript:
export const Priority = { Low: 0, Medium: 1, High: 2, /** Compare with threshold */ isAboveThreshold: { Low: false, Medium: false, High: true, }, } as const;
The params values must be constant expressions (scalars, arrays of scalars) since they are defined inside a PHP attribute. The values are spread as named arguments when the method is invoked for each enum case.
Note
Methods with only optional parameters (parameters with default values) are still included without needing to set params, since they can be called without arguments.
Enum Static Method #[TsEnumStaticMethod]
Using the TsEnumStaticMethod attribute to specify that the options() static method should be called and the return value should be published in TypeScript:
use AbeTwoThree\LaravelTsPublish\Attributes\TsEnumStaticMethod; enum Status: string { case Active = 'active'; case Inactive = 'inactive'; #[TsEnumStaticMethod] public static function options(): array { return array_map(fn(self $status) => [ 'value' => $status->value, 'label' => $status->name, ], self::cases()); } }
Generated TypeScript declaration type:
export const Status = { Active: 'active', Inactive: 'inactive', options: [ { value: 'active', label: 'Active' }, { value: 'inactive', label: 'Inactive' }, ], } as const;
The #[TsEnumStaticMethod] attribute accepts the same optional name, description, and params parameters as #[TsEnumMethod]:
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
string |
Method name | Customize the key name used in the TypeScript output |
description |
string |
'' |
Added as a JSDoc comment above the method output |
params |
array |
[] |
Named arguments to pass when invoking the method |
#[TsEnumStaticMethod(name: 'allOptions', description: 'Array of all status options')] public static function options(): array { return array_map(fn(self $status) => [ 'value' => $status->value, 'label' => $status->name, ], self::cases()); }
Like #[TsEnumMethod], static methods with required parameters are skipped by default unless params is provided:
#[TsEnumStaticMethod(description: 'Filter by minimum priority', params: ['minimum' => 1])] public static function filterByMinimum(int $minimum): array { return array_filter(self::cases(), fn (self $case) => $case->value >= $minimum); }
Enum Class Name #[TsEnum]
Renaming an enum or adding a description using the TsEnum attribute:
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
string |
Enum class name | Override the TypeScript const name |
description |
string |
'' |
Added as a JSDoc comment above the enum. Takes priority over any PHPDoc description. |
use AbeTwoThree\LaravelTsPublish\Attributes\TsEnum; #[TsEnum('UserStatus', description: 'All possible user account statuses')] enum Status: string { case Active = 'active'; case Inactive = 'inactive'; }
Generated TypeScript declaration type:
/** All possible user account statuses */ export const UserStatus = { Active: 'active', Inactive: 'inactive', } as const;
Enum Case Typings #[TsCase]
Renaming an enum case, changing the frontend value, and adding a description using the TsCase attribute:
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
string |
Case name | Override the case key name in the TypeScript output |
value |
string|int |
Case value | Override the case value in the TypeScript output |
description |
string |
'' |
Added as a JSDoc comment above the case |
use AbeTwoThree\LaravelTsPublish\Attributes\TsCase; enum Status: int { #[TsCase(name: 'active_status', value: true, description: 'The user is active')] case Active = 1; #[TsCase(name: 'inactive_status', value: false, description: 'The user is inactive')] case Inactive = 0; }
Generated TypeScript declaration type:
export const Status = { /** The user is active */ active_status: true, /** The user is inactive */ inactive_status: false, } as const;
Enum Value & Key Types
As shown above, the enum generated in TypeScript is a JavaScript object with the as const assertion to prevent modification.
However, there are times when you need to validate that a value is a valid enum value or a valid enum case key. For this purpose, this package also generates TypeScript types for the enum values and case keys if the enum is a PHP backed enum.
For every enum, a Type alias is generated from the case values. For backed enums, a Kind alias is also generated from the case names:
| Generated Type | Source | Example |
|---|---|---|
StatusType |
Case values | 'active' | 'inactive' |
StatusKind |
Case names | 'Active' | 'Inactive' |
Note
The Kind type alias is only generated for backed enums, since unit enums already use case names as their values.
Example:
export const Status = { Active: 'active', Inactive: 'inactive', } as const; export type StatusType = 'active' | 'inactive'; export type StatusKind = 'Active' | 'Inactive'; // Only published if the enum is a backed enum
With those types, you can now validate that a value is a valid enum value or case key:
import { StatusType, StatusKind } from '@js/types/data/enums'; function setStatus(status: StatusType) { // status will only accept 'active' or 'inactive' } function setStatusByKey(status: StatusKind) { // status will only accept 'Active' or 'Inactive' }
Enum Metadata & Tolki Enum Package
By default, this package will publish three metadata properties on the enum in TypeScript for the cases, methods, and static methods that are published. These properties are _cases, _methods, and _static.
The purpose of these metadata properties is to be able to create an "instance" of the enum from a case value like you'd get on the PHP side. To accomplish this, you need to use the @tolki/enum npm package.
By default, this package configures the usage of the @tolki/enum package when enums are published.
This is what a published enum looks like when using the @tolki/enum package on the frontend:
import { defineEnum } from '@tolki/enum'; export const Status = defineEnum({ _cases: ['Active', 'Inactive'], _methods: ['label'], _static: ['options'], Active: 'active', Inactive: 'inactive', label: { Active: 'Active User', Inactive: 'Inactive User', }, options: [ { value: 'active', label: 'Active' }, { value: 'inactive', label: 'Inactive' }, ], } as const);
The defineEnum function from the @tolki/enum package is a factory function that will bind PHP-like methods to the enum object.
See more details about defineEnum here.
With the @tolki/enum package, you can now create an "instance" of the enum from a case value like you'd get on the PHP side using the from function:
import { Status } from '@js/types/data/enums'; // Using example status from the previous example import { User } from '@js/types/data/models'; // Assuming you have a User model published as well const user: User = { id: 1, name: 'John Doe', status: 'active', } const userStatus = Status.from(user.status); // userStatus will now have the following structure: { // cases become just value with the matching value to the model value: 'active', // methods become just the key/value pair for the matching case label: 'Active User', // static methods stay as is on the enum options: [ { value: 'active', label: 'Active' }, { value: 'inactive', label: 'Inactive' }, ], } // Then use the userStatus object in your frontend similarly to how you would use an instance of the enum in PHP: userStatus.value // 'active' userStatus.label // 'Active User' userStatus.options // [ // { value: 'active', label: 'Active' }, // { value: 'inactive', label: 'Inactive' }, // ]
The defineEnum function currently also binds the tryFrom and cases functions to the enum.
Enum Metadata Vite Plugin
The @tolki/enum package also provides a Vite plugin that can call the artisan publish command for you and watch for changes to your enums & models to automatically update the generated TypeScript declaration types on the frontend.
For documentation on how to set up the Vite plugin, see this link.
Disabling Enum Metadata or Tolki Enum Package
If you don't plan to use the @tolki/enum package or don't need the metadata properties for your use case, you can disable the generation of these metadata properties in the config file by setting enum_metadata_enabled to false:
// config/ts-publish.php 'enum_metadata_enabled' => false,
If you would like to use the metadata but don't want the @tolki/enum package, you can disable the usage of that package in the config file by setting enums_use_tolki_package to false. This will still generate the metadata properties on the enum, but will not wrap the enum in the defineEnum function from the @tolki/enum package:
// config/ts-publish.php 'enum_metadata_enabled' => true, 'enums_use_tolki_package' => false,
Auto-Including All Enum Methods
By default, only public methods decorated with the #[TsEnumMethod] or #[TsEnumStaticMethod] attributes are included in the TypeScript output. If you'd prefer to include all public methods without needing to add the attribute to every method, you can enable automatic inclusion in your config file:
// config/ts-publish.php 'auto_include_enum_methods' => true, // Include all public non-static methods 'auto_include_enum_static_methods' => true, // Include all public static methods
When enabled, every public method declared on the enum will be included in the TypeScript output — you no longer need to add #[TsEnumMethod] or #[TsEnumStaticMethod] to each method. Built-in enum methods like cases(), from(), and tryFrom() are always excluded automatically.
Note
Methods with required parameters are automatically skipped in auto-include mode since there is no attribute to provide params on. To include a method that requires parameters, add the #[TsEnumMethod] or #[TsEnumStaticMethod] attribute with the params property set.
You can still use #[TsEnumMethod] and #[TsEnumStaticMethod] to customize the name, description, or params of individual methods when auto-inclusion is enabled:
enum Status: string { case Active = 'active'; case Inactive = 'inactive'; // Included automatically with defaults (name: 'label', description: '') public function label(): string { return match($this) { self::Active => 'Active User', self::Inactive => 'Inactive User', }; } // Included automatically, but with a custom description from the attribute #[TsEnumMethod(description: 'Get the icon name for the status')] public function icon(): string { return match($this) { self::Active => 'check', self::Inactive => 'x', }; } }
Caution
These settings are disabled by default for security reasons — enabling them will expose the return values of all public methods on your enums. Make sure you're comfortable with that before enabling them.
PHPDoc Descriptions for Enums
This package automatically reads PHPDoc doc blocks and outputs them as JSDoc comments in the generated TypeScript. Descriptions are read from the following locations:
| Location | Source | JSDoc Placement |
|---|---|---|
| Enum class | Doc block above the enum class | Above the export const declaration |
| Enum cases | Doc block above each case | Above the case property |
| Instance methods | Doc block above the method | Above the method property |
| Static methods | Doc block above the static method | Above the static method property |
Lines starting with @ (such as @param, @return, @phpstan-type, etc.) are automatically filtered out — only the human-readable description text is included.
Priority: If an element has both a PHPDoc doc block and an attribute with a description parameter (e.g., #[TsEnum(description: ...)], #[TsCase(description: ...)], #[TsEnumMethod(description: ...)]), the attribute description always takes priority over the doc block.
/** * Represents the priority level of a task. * * @phpstan-type PriorityValue = int */ enum Priority: int { /** Lowest priority level */ case Low = 0; /** Standard priority */ case Medium = 1; /** Highest priority level */ case High = 2; /** Human-readable label for the priority */ #[TsEnumMethod] public function label(): string { return match($this) { self::Low => 'Low Priority', self::Medium => 'Medium Priority', self::High => 'High Priority', }; } }
Generated TypeScript:
/** Represents the priority level of a task. */ export const Priority = { /** Lowest priority level */ Low: 0, /** Standard priority */ Medium: 1, /** Highest priority level */ High: 2, /** Human-readable label for the priority */ label: { Low: 'Low Priority', Medium: 'Medium Priority', High: 'High Priority', }, } as const;
Models
This package can also convert your Laravel Eloquent models to TypeScript declaration types. This package will go through your models' properties, mutators, and relations to create TypeScript interfaces that match the structure of your model.
Model Templates & Publishing
By default, this package purposely breaks the model into three separate interfaces for the properties, mutators, and relations to give you more flexibility on which properties you need to use in a concrete situation on your frontend projects. It also generates a fourth interface that extends all three interfaces for when you do want to use all the properties, mutators, and relations together, see below.
Note
Any mutator added to the $appends property on the model will be included in the model properties interface in the split template since those are always included when the model is serialized to JSON.
If that's still not ideal for your situation, you can change the template used to generate the model types. This package comes with two templates for generating model types:
| Template | Description |
|---|---|
laravel-ts-publish::model-split |
(Default) Splits into separate interfaces for properties, mutators, and relations |
laravel-ts-publish::model-full |
Combines all properties, mutators, and relations into a single interface |
Just change the model_template in the config file to use the template that best fits your needs:
// config/ts-publish.php 'model_template' => 'laravel-ts-publish::model-full',
You are also free to publish the views to modify them or create your own custom template if you want to change the structure of the generated types even more. Just make sure to update the model_template in the config file to point to your new custom template.
Example using the default model-split template with a model that has properties, mutators, and relations
use App\Enums\Status; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; /** * Example database structure for this model: * @property int $id * @property string $name * @property int $is_super_admin * @property Status $status * @property Post[] $posts */ class User extends Model { public function casts(): array { return [ 'status' => Status::class, ]; } public function posts(): HasMany { return $this->hasMany(Post::class); } protected function admin(): Attribute { return Attribute::get(fn(): bool => $this->is_super_admin === 1 ? true : false); } }
Default generated TypeScript declaration type user.ts:
import { StatusType } from '../enums'; import { Profile, Post } from './'; export interface User { id: number; name: string; is_super_admin: number; status: StatusType; } export interface UserMutators { admin: boolean; // From the accessor method } export interface UserRelations { profile: Profile | null; posts: Post[]; profile_count: number; posts_count: number; profile_exists: boolean; posts_exists: boolean; } export interface UserAll extends User, UserMutators, UserRelations {}
Example Inertia form where we use the entire User interface for the form data, but only need the profile & profile_exists properties from the UserRelations interface for this specific page:
<script setup> import { useForm } from '@inertiajs/vue3' import { User, UserRelations } from '@js/types/data/models'; interface UserForm extends User, Pick<UserRelations, 'profile_exists'>{ profile: UserRelations['profile'] | null; } const { user } = defineProps<{ user: UserForm; }>() const form = useForm<UserForm>({ ...user, }) form.profile // Is Profile or null form.posts // TS error because posts is not part of the UserForm interface </script>
Example using the model-full template with a model that has all properties in one interface
import { StatusType } from '../enums'; import { Profile, Post } from './'; export interface User { // Columns id: number; name: string; is_super_admin: number; status: StatusType; // Mutators admin: boolean; // From the accessor method // Relations profile: Profile | null; posts: Post[]; // Counts profile_count: number; posts_count: number; // Exists profile_exists: boolean; posts_exists: boolean; }
The same Inertia form example as above would work with this model-full template as well since all properties, mutators, and relations are in the same interface.
You will notice the need to call Omit with more properties to exclude the relation properties that are not needed for this specific page, but that's the tradeoff with using a single interface for the model instead of splitting it into separate interfaces for the properties, mutators, and relations.
<script setup> import { useForm } from '@inertiajs/vue3' import { User } from '@js/types/data/models'; interface UserForm extends Omit<User, 'admin' | 'profile' | 'posts' | 'profile_count' | 'posts_count' | 'posts_exists'> { profile: User['profile'] | null; } const { user } = defineProps<{ user: UserForm; }>() const form = useForm<UserForm>({ ...user, }) form.profile // Is Profile or null form.posts // TS error because posts is not part of the UserForm interface </script>
Nullable Relations
By default, this package detects whether singular relations should be typed as nullable (| null) based on the relation type and database schema:
| Relation Type | Strategy | Behavior |
|---|---|---|
HasOne |
nullable |
Always add null — the related record may not exist |
MorphOne |
nullable |
Always add null |
HasOneThrough |
nullable |
Always add null |
BelongsTo |
fk |
Add null only when the foreign key column is nullable in the database |
MorphTo |
morph |
Add null when either the morph type or morph id column is nullable |
HasMany |
never |
Never nullable (returns an empty array, not null) |
BelongsToMany |
never |
Never nullable |
MorphMany |
never |
Never nullable |
MorphToMany |
never |
Never nullable |
For example, a User model with a HasOne profile and a HasMany posts relation generates:
export interface UserRelations { profile: Profile | null; // HasOne — always nullable posts: Post[]; // HasMany — never nullable }
A Post model with a non-nullable user_id FK and a nullable category_id FK generates:
export interface PostRelations { author: User; // BelongsTo — user_id is NOT NULL category_rel: Category | null; // BelongsTo — category_id is nullable }
Disabling Nullable Relations
To disable nullable relation detection entirely and keep all singular relations non-nullable:
// config/ts-publish.php 'nullable_relations' => false,
Overriding the Nullability Strategy
You can override the default strategy for any relation type using the relation_nullability_map config. Keys are fully qualified class names — use the ::class syntax for safety and IDE autocompletion:
// config/ts-publish.php use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasOne; 'relation_nullability_map' => [ BelongsTo::class => 'nullable', // Make all BelongsTo always nullable HasOne::class => 'never', // Make HasOne never nullable ],
This also supports custom relation types from third-party packages:
use SomePackage\Relations\BelongsToTenant; 'relation_nullability_map' => [ BelongsToTenant::class => 'fk', ],
Available strategies: 'nullable' (always), 'never' (never), 'fk' (check FK column), 'morph' (check morph columns).
See AbeTwoThree\LaravelTsPublish\RelationMap for the full default map.
Model Attributes
Like with enums, this package provides a few PHP attributes that you can use to further customize the generated TypeScript declaration types for your models. All attributes can be found at this link and are under the AbeTwoThree\LaravelTsPublish\Attributes namespace.
| Attribute | Target | Description |
|---|---|---|
#[TsCasts] |
casts() method, $casts property, or model class |
Specify TypeScript types for model columns. Works similarly to Laravel's casts but for TypeScript. |
#[TsType] |
Custom cast class | Specify the TypeScript type for any model property that uses this custom cast class. |
#[TsExclude] |
Model class, accessor method, or relation method | Exclude an entire model, specific accessors, or relations from the TypeScript output. See Excluding with TsExclude. |
Examples using #[TsCasts] attribute
Using #[TsCasts] attribute on casts() method
use AbeTwoThree\LaravelTsPublish\Attributes\TsCasts; class User extends Model { #[TsCasts([ 'metadata' => '{label: string, value: string}[]', 'is_super_admin' => 'boolean', ])] public function casts(): array { return [ 'status' => Status::class, 'metadata' => 'array', 'is_super_admin' => 'number', ]; } }
Generated TypeScript declaration type:
import { StatusType } from '../enums'; export interface User { status: StatusType; metadata: {label: string, value: string}[]; is_super_admin: boolean; }
Using #[TsCasts] attribute on $casts property & model class name
Similarly, you can use the TsCasts attribute on the $casts property or on the model class itself with the same syntax as above to specify TypeScript types for model properties.
On the $casts property:
use AbeTwoThree\LaravelTsPublish\Attributes\TsCasts; class User extends Model { #[TsCasts([ 'metadata' => '{label: string, value: string}[]', 'is_super_admin' => 'boolean', ])] protected $casts = [ 'status' => Status::class, 'metadata' => 'array', 'is_super_admin' => 'number', ]; }
On the model class itself:
use AbeTwoThree\LaravelTsPublish\Attributes\TsCasts; #[TsCasts([ 'metadata' => '{label: string, value: string}[]', 'is_super_admin' => 'boolean', ])] class User extends Model { protected $casts = [ 'status' => Status::class, 'metadata' => 'array', 'is_super_admin' => 'number', ]; }
Tip
It is recommended to place the TsCasts attribute either on the casts() method or the $casts property instead of the model class itself to keep the TypeScript type definitions close to where you are defining the casts for the model properties in PHP. However, the TsCasts attribute can also be used to define the types of mutators and relations, at which point it may make more sense to place the attribute on the model class itself instead of the casts() method or $casts property since those only define types for the model properties.
Custom types using #[TsCasts] attribute
The TsCasts attribute can also receive an array as the value for a property to specify a custom type and where that type should be imported from.
This allows you to define a custom TypeScript type that you can reuse across multiple model properties or across multiple models without having to redefine the type for each property on each model.
use AbeTwoThree\LaravelTsPublish\Attributes\TsCasts; class User extends Model { #[TsCasts([ 'settings' => 'Record<string, unknown>', 'metadata' => ['type' => 'MetadataType | null', 'import' => '@js/types/custom'], 'dimensions' => ['type' => 'ProductDimensions', 'import' => '@js/types/product'], ])] public function casts(): array { return [ 'settings' => 'array', 'metadata' => 'array', 'dimensions' => 'array', 'created_at' => 'datetime', 'updated_at' => 'datetime', ]; } }
Generated TypeScript declaration type:
import { MetadataType } from '@js/types/custom'; import { ProductDimensions } from '@js/types/product'; export interface User { id: number; name: string; settings: Record<string, unknown>; metadata: MetadataType | null; dimensions: ProductDimensions; created_at: string; updated_at: string; }
Examples using #[TsType] attribute
When you have a custom cast class that you use on one or more model properties, you can use the TsType attribute on that custom cast class to specify what TypeScript type should be used for any model property that uses that custom cast.
Custom cast class with TsType attribute:
use AbeTwoThree\LaravelTsPublish\Attributes\TsType; #[TsType('{width: number, height: number, depth: number}')] class ProductDimensionsCast implements CastsAttributes { public function get($model, string $key, $value, array $attributes) { // Custom logic to cast the value to the ProductDimensions type } }
Model using the custom cast class:
class Product extends Model { public function casts(): array { return [ 'dimensions' => ProductDimensionsCast::class, ]; } }
Generated TypeScript declaration type:
export interface Product { id: number; name: string; dimensions: {width: number, height: number, depth: number}; }
Using #[TsType] attribute with custom type and import
Similarly to the TsCasts attribute, you can also specify the type and import for a custom cast class using the TsType attribute:
use AbeTwoThree\LaravelTsPublish\Attributes\TsType; #[TsType(['type' => 'ProductDimensions', 'import' => '@js/types/product'])] class ProductDimensionsCast implements CastsAttributes { public function get($model, string $key, $value, array $attributes) { // Custom logic to cast the value to the ProductDimensions type } }
Generated TypeScript declaration type:
import { ProductDimensions } from '@js/types/product'; export interface Product { id: number; name: string; dimensions: ProductDimensions; }
PHPDoc Descriptions for Models
Similar to enums, this package automatically reads PHPDoc doc blocks from your model classes and outputs them as JSDoc comments in the generated TypeScript interfaces. Descriptions are read from the following locations:
| Location | Source | JSDoc Placement |
|---|---|---|
| Model class | Doc block above the model class | Above the export interface declaration |
| Columns | Doc block above the column's Attribute accessor method | Above the column property |
| Mutators | Doc block above the mutator's Attribute accessor method | Above the mutator property |
| Relations | Doc block above the relation method | Above the relation property |
For columns and mutators, the package looks for a doc block on the corresponding accessor method — either new-style (protected function name(): Attribute) or old-style (public function getNameAttribute()). The new-style accessor is checked first.
Lines starting with @ (such as @param, @return, @phpstan-type, etc.) are automatically filtered out.
/** Application user account */ class User extends Model { /** User name formatted with first letter capitalized */ protected function name(): Attribute { return Attribute::make( get: fn ($value): string => ucfirst((string) $value), ); } /** User initials (e.g. "JD" for "John Doe") */ protected function initials(): Attribute { return Attribute::make( get: fn (): string => collect(explode(' ', $this->name)) ->map(fn (string $part) => strtoupper(substr($part, 0, 1))) ->implode(''), ); } /** Polymorphic images (avatar gallery, etc.) */ public function images(): MorphMany { return $this->morphMany(Image::class, 'imageable'); } }
Generated TypeScript using the model-full template:
import type { Image } from './'; /** Application user account */ export interface User { // Columns id: number; /** User name formatted with first letter capitalized */ name: string; email: string; // Mutators /** User initials (e.g. "JD" for "John Doe") */ initials: string; // Relations /** Polymorphic images (avatar gallery, etc.) */ images: Image[]; images_count: number; images_exists: boolean; }
Timestamps as Date Objects
By default, timestamp columns (date, datetime, timestamp, and their immutable variants) are mapped to string in TypeScript. If your frontend works with Date objects instead, you can enable date mapping:
// config/ts-publish.php 'timestamps_as_date' => true,
| Config Value | Generated TypeScript Type |
|---|---|
false |
created_at: string |
true |
created_at: Date |
Custom TypeScript Type Mappings
This package ships with a comprehensive set of PHP-to-TypeScript type mappings (e.g., integer → number, boolean → boolean, json → object). You can override existing mappings or add new ones using the custom_ts_mappings config option:
// config/ts-publish.php 'custom_ts_mappings' => [ 'binary' => 'Blob', 'json' => 'Record<string, unknown>', // Override the default 'object' mapping 'money' => 'number', // Add a custom type ],
Tip
Custom mappings are merged with the built-in map and take precedence. Type keys are case-insensitive. For per-model type overrides, use the #[TsCasts] attribute instead.
Output Options
This package provides several output formats that can be enabled independently:
| Config Key | Default | Description |
|---|---|---|
output_to_files |
true |
Write individual .ts files with barrel index.ts exports |
output_globals_file |
false |
Generate a global.d.ts file with a global TypeScript namespace |
output_json_file |
false |
Output all generated definitions as a JSON file |
output_collected_files_json |
true |
Output a JSON list of collected PHP file paths (useful for file watchers) |
When output_globals_file is enabled, a global declaration file is created that makes all your types available without explicit imports:
// config/ts-publish.php 'output_globals_file' => true, 'global_filename' => 'laravel-ts-global.d.ts', 'models_namespace' => 'models', 'enums_namespace' => 'enums',
The JSON output from output_collected_files_json is designed to work with build tools and file watchers (like the @tolki/enum Vite plugin) that need to know which PHP source files were collected so they can trigger a re-publish when those files change.
API Resources
This package can generate TypeScript interfaces from your Laravel API Resources (JsonResource classes). It statically analyzes the toArray() method to extract property names, types, and optionality — producing a TypeScript interface that matches the shape of your API responses.
By default, the package will look for resources in the app/Http/Resources directory. You can customize this with the additional_resource_directories, included_resources, and excluded_resources config options (see Filtering Resources).
How It Works
The package uses PHP Parser to statically analyze each resource's toArray() method. It resolves property types by inspecting the backing Eloquent model's database schema and cast definitions. The backing model is determined from (in priority order):
- The
#[TsResource(model:)]attribute - The
@mixinPHPDoc tag (resolved via use statements) - Convention-based guess — reverses Laravel's naming convention (
App\Http\Resources\UserResource→App\Models\User) #[UseResource]attribute scan — checks all collected models for a#[UseResource(ResourceClass::class)]attribute pointing to this resource (Laravel 12+ only)
Most resources only need @mixin or the naming convention. The #[TsResource(model:)] attribute is useful when the resource name doesn't match the model, and #[UseResource] handles cases where the resource lives outside the standard Http\Resources namespace.
Supported Patterns
The analyzer recognizes the following patterns inside toArray():
Direct Property Access
'id' => $this->id, 'name' => $this->name, 'status' => $this->status, // Enum cast → generates enum type
Types are resolved from the model's database columns and cast definitions.
Conditional Methods
All conditional methods produce optional properties (with ? in TypeScript):
| Method | Description | Generated Type |
|---|---|---|
$this->when(cond, value) |
Include when condition is true | Inferred from value |
$this->whenHas('attr') |
Include when attribute is present | From model column type |
$this->whenNotNull($this->attr) |
Include when not null | From model column type |
$this->whenLoaded('relation') |
Include when relation is loaded | From model relation type |
$this->whenCounted('relation') |
Include when count is loaded | number |
$this->whenAggregated('rel', 'col', 'fn') |
Include when aggregate is loaded | number |
$this->whenPivotLoaded('table') |
Include when pivot is loaded | unknown |
See Nullable Relations for whenLoaded nullability handling.
Enum Properties with EnumResource
Use EnumResource::make() to expose enum-cast properties as rich enum objects:
'status' => EnumResource::make($this->status), 'currency' => EnumResource::make($this->currency),
When enums_use_tolki_package is enabled (the default), these generate AsEnum<typeof EnumName> types with automatic imports. When disabled, they generate the enum's Type alias (e.g., StatusType).
Nested Resources
Reference other resources using ::make(), ::collection(), or new:
// Single nested resource (optional when inside whenLoaded) 'author' => UserResource::make($this->whenLoaded('user')), // Using new instead of ::make() — works identically 'author' => new UserResource($this->whenLoaded('user')), // Collection of nested resources 'tags' => TagResource::collection($this->whenLoaded('tags')), // Non-conditional nested resource 'owner' => UserResource::make($this->user),
Both SomeResource::make(...) and new SomeResource(...) are fully supported and behave identically — the analyzer resolves the resource type, tracks the FQCN for imports, and detects conditional arguments for optionality.
Self-referencing resources are also supported:
'parent' => CategoryResource::make($this->whenLoaded('parent')), 'children' => CategoryResource::collection($this->whenLoaded('children')),
Merge Operations
Use merge and mergeWhen to spread additional properties into the response:
// Unconditional merge — properties are required (not optional) $this->merge([ 'full_name' => $this->first_name . ' ' . $this->last_name, 'total_display' => $this->total, ]), // Conditional merge — properties are optional $this->mergeWhen($this->is_featured, [ 'weight' => $this->weight, 'dimensions' => $this->dimensions, ]),
Both merge and mergeWhen also accept closures and arrow functions instead of array literals:
// merge with closure $this->merge(fn () => [ 'currency_label' => $this->currency, ]), // mergeWhen with closure $this->mergeWhen($this->paid_at !== null, fn () => [ 'shipped_at' => $this->shipped_at, 'tracking' => $this->tracking_number, ]),
| Method | Optionality | Description |
|---|---|---|
$this->merge([...]) |
Required | Properties are always present |
$this->mergeWhen(cond, [...]) |
Optional (?) |
Properties included conditionally |
Closure & Arrow Function Values
The analyzer resolves closures and arrow functions used as value arguments. Simple closures that return a single expression are analyzed recursively:
// Arrow function — return expression analyzed directly 'status' => $this->when(true, fn () => $this->status), // Arrow function returning a nested resource 'user' => $this->when(true, fn () => UserResource::make($this->user)), // Full closure — first return statement is analyzed 'notes' => $this->when(true, function () { return $this->notes; }),
This works anywhere a value expression is expected — including when, whenLoaded, whenNotNull, merge, and mergeWhen.
Parent toArray() Spread
Extend a parent resource using ...parent::toArray($request). Parent properties appear first, and the child can override any key:
class PostResource extends JsonResource { public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, 'status' => EnumResource::make($this->status), ]; } } class ApiPostResource extends PostResource { public function toArray(Request $request): array { return [ ...parent::toArray($request), 'status' => $this->status, // Overrides parent's EnumResource type ]; } }
The child ApiPostResource inherits all parent properties (id, title, status), with status overridden to use the plain enum value instead of EnumResource::make().
If the parent itself extends JsonResource (the base class), the spread automatically delegates to the model's database attributes — see JsonResource Base Delegation.
Trait Method Spread
Spread trait method return values into toArray() with ...$this->traitMethod(). The analyzer reads @return array{key: type} PHPDoc annotations to resolve property types:
trait IncludesMorphValue { /** * @return array{morphValue: string} */ protected function includeMorphValue(): array { return ['morphValue' => $this->resource->getMorphClass()]; } } class PostResource extends JsonResource { use IncludesMorphValue; public function toArray(Request $request): array { return [ ...$this->includeMorphValue(), 'id' => $this->id, 'title' => $this->title, ]; } }
Generates:
export interface Post { morphValue: string; // From trait PHPDoc id: number; title: string; }
Multiline @return shapes are also supported:
/** * @return array{ * firstName: string, * lastName: string, * isActive: bool, * } */ protected function includeProfile(): array { // ... }
Another option for defining the return types of a trait method is to use the #[TsResourceCasts] attribute on the trait method itself with the same syntax as the #[TsCasts] attribute for models:
use AbeTwoThree\LaravelTsPublish\Attributes\TsResourceCasts; trait IncludesExtras { #[TsResourceCasts([ 'location' => ['type' => 'GeoPoint', 'import' => '@/types/geo'], 'flag' => ['type' => 'string | null', 'optional' => true], 'extra' => 'Record<string, unknown>', ])] protected function includeCastedExtras(): array { return [ 'location' => strtoupper('x'), 'flag' => strtolower('y'), ]; } }
Tip
Trait spreads also flow through parent inheritance. If a parent resource spreads a trait method and a child extends it with ...parent::toArray($request), the child inherits the trait-contributed properties.
Note
When a trait method has no @return array{...} PHPDoc or #[TsResourceCasts] attribute, its properties will be typed as unknown.
JsonResource Base Delegation
Resources that have no toArray() method or whose toArray() simply returns parent::toArray($request) automatically generate properties from the backing model's database schema:
/** * @mixin User */ class UserResource extends JsonResource { // No toArray() — properties auto-generated from User model }
You can also spread the base properties and add computed keys:
/** * @mixin User */ class UserResource extends JsonResource { public function toArray(Request $request): array { return [ ...parent::toArray($request), 'full_name' => strtoupper($this->name), ]; } }
The model is resolved from #[TsResource(model:)], @mixin PHPDoc, or use statements. When no model can be detected, the resource produces an empty interface.
Attribute Filters (only / except)
Resources that use $this->only([...]) or $this->except([...]) to filter model attributes are supported — both as a direct return value and as a spread:
// As the return value public function toArray(Request $request): array { return $this->only(['id', 'name', 'email']); } // As a spread in a return array public function toArray(Request $request): array { return [ ...$this->except(['password', 'remember_token']), 'role' => EnumResource::make($this->role), ]; }
Both methods delegate to the backing model's full database schema and filter by the listed keys. Properties retain their original types from the model.
Note
Currently only only and except are supported as attribute filter methods. Other collection-style methods are not analyzed. If you find you need additional methods, open and issue, or better yet, submit a PR with the added functionality! See FiltersModelAttributes
Resource Collections
ResourceCollection subclasses are supported. The analyzer resolves $this->collection to the singular resource type as an array:
use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection { public function toArray(Request $request): array { return [ 'data' => $this->collection, 'has_admin' => true, ]; } }
Generates:
import type { UserResource } from './'; export interface UserCollection { data: UserResource[]; has_admin: unknown; }
The singular resource is resolved from:
- Explicit
$collectsproperty — if defined on the collection class - Naming convention —
UserCollection→UserResource(strips "Collection", appends "Resource")
class OrderCollection extends ResourceCollection { // Explicit: use OrderResource as the singular resource public $collects = OrderResource::class; public function toArray(Request $request): array { return [ 'data' => $this->collection, ]; } }
When the singular resource cannot be resolved (e.g., MiscCollection with no matching MiscResource), $this->collection falls back to unknown.
Larger support for ResourceCollection features (e.g., pagination metadata, additional() method, etc.) may be added in a future release.
Example
Given this resource:
use AbeTwoThree\LaravelTsPublish\EnumResource; use Illuminate\Http\Resources\Json\JsonResource; use App\Models\User; /** * User account resource. * * @mixin User */ class UserResource extends JsonResource { public function toArray(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'role' => EnumResource::make($this->role), 'profile' => $this->whenLoaded('profile'), 'posts' => PostResource::collection($this->whenLoaded('posts')), 'phone' => $this->whenHas('phone'), 'avatar' => $this->whenNotNull($this->avatar), 'posts_count' => $this->whenCounted('posts'), ]; } }
The package generates the following TypeScript interface:
import { type AsEnum } from '@tolki/enum'; import { Role } from '../enums'; import type { Profile } from '../models'; import type { PostResource } from './'; /** User account resource. */ export interface UserResource { id: number; name: string; email: string; role: AsEnum<typeof Role>; profile?: Profile | null; posts?: PostResource[]; phone?: string | null; avatar?: string | null; posts_count?: number; }
Notice how:
- Direct properties (
id,name,email) are required whenLoaded,whenHas,whenNotNull, andwhenCountedproperties are optional (?)EnumResource::make()generatesAsEnum<typeof Role>with the proper importsPostResource::collection()is typed asPostResource[]- Bare
whenLoaded('profile')resolves to the model relation type (Profile | null) - PHPDoc class descriptions are preserved as JSDoc comments
Resource Attributes
Three attributes are available for configuring resource TypeScript generation:
| Attribute | Target | Description |
|---|---|---|
#[TsResource] |
Resource class | Override the interface name, specify the backing model, or add a description |
#[TsResourceCasts] |
Resource class or method | Override or add property types with custom TypeScript types |
#[TsExclude] |
Resource class | Exclude the entire resource from the TypeScript output. |
#[TsResource] — Configure Resource Generation
Use this attribute to override the generated interface name, explicitly specify the backing model, or add a description:
use AbeTwoThree\LaravelTsPublish\Attributes\TsResource; use App\Models\User; #[TsResource(name: 'UserData', model: User::class, description: 'User API response')] class UserResource extends JsonResource { // ... }
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
?string |
Class name | Override the TypeScript interface name |
model |
?class-string |
Auto-detected | Explicitly specify the backing Eloquent model |
description |
string |
'' |
Added as a JSDoc comment above the interface |
Tip
When name is set, it also affects the output filename. For example, #[TsResource(name: 'Address')] generates address.ts instead of address-resource.ts.
#[TsResourceCasts] — Override Property Types
Use this attribute to override inferred types or add virtual properties with custom TypeScript types:
use AbeTwoThree\LaravelTsPublish\Attributes\TsResourceCasts; #[TsResourceCasts([ 'metadata' => 'Record<string, unknown>', 'coordinates' => ['type' => 'GeoPoint', 'import' => '@/types/geo'], 'flagged_at' => ['type' => 'string | null', 'optional' => true], ])] class CommentResource extends JsonResource { // ... }
Each entry can be:
| Format | Example | Description |
|---|---|---|
| Plain string | 'Record<string, unknown>' |
Override the type only |
Array with import |
['type' => 'GeoPoint', 'import' => '@/types/geo'] |
Custom type with an import statement |
Array with optional |
['type' => 'string', 'optional' => true] |
Override the type and mark as optional |
Properties defined in #[TsResourceCasts] that don't exist in toArray() are appended to the generated interface. Properties that do exist have their types overridden.
Generated TypeScript with the coordinates example:
import type { GeoPoint } from '@/types/geo'; export interface CommentResource { id: number; content: string; is_flagged: boolean; flagged_at?: string | null; metadata: Record<string, unknown>; author?: UserResource; post?: PostResource; coordinates: GeoPoint; }
On Trait Methods
#[TsResourceCasts] can also be applied to trait methods that are spread into toArray(). This lets you control types for trait-contributed properties without modifying the resource class:
use AbeTwoThree\LaravelTsPublish\Attributes\TsResourceCasts; trait IncludesLocation { #[TsResourceCasts([ 'location' => ['type' => 'GeoPoint', 'import' => '@/types/geo'], 'flag' => ['type' => 'string | null', 'optional' => true], 'extra' => 'Record<string, unknown>', ])] protected function includeLocation(): array { return [ 'location' => $this->coordinates, 'flag' => $this->flag, ]; } }
The attribute works identically to the class-level version — overriding types, marking properties optional, adding imports, and appending new properties. Properties defined in the attribute that don't exist in the method's return array (like extra above) are appended.
Nullable Relations
When whenLoaded('relation') resolves a relation type, the package determines whether it should include | null based on the relation kind and the database schema.
This is controlled by the nullable_relations config option (enabled by default). The strategy for each relation type is:
| Relation Type | Strategy | Description |
|---|---|---|
HasOne, MorphOne, HasOneThrough |
nullable |
Always nullable — the related record may not exist |
BelongsTo |
fk |
Checks the foreign key column's DB-level nullability |
MorphTo |
morph |
Checks both the morph type and FK column nullability |
HasMany, BelongsToMany, etc. |
never |
Collection relations — typed as arrays, never null |
For example, a BelongsTo relation with a nullable foreign key:
// Migration: $table->foreignId('user_id')->nullable(); // Resource: 'user' => UserResource::make($this->whenLoaded('user')),
Generates user?: UserResource | null — optional (from whenLoaded) and nullable (from the nullable FK).
You can disable nullable relation detection globally:
// config/ts-publish.php 'nullable_relations' => false,
Or override the strategy for specific relation types using relation_nullability_map:
// config/ts-publish.php 'relation_nullability_map' => [ \Illuminate\Database\Eloquent\Relations\HasOne::class => 'never', ],
Valid strategies are 'nullable', 'never', 'fk', and 'morph'.
Filtering Resources
You can customize which resources are discovered using the same include/exclude pattern as models and enums:
// config/ts-publish.php // Only publish these specific resources (leave empty to include all) 'included_resources' => [ App\Http\Resources\UserResource::class, App\Http\Resources\PostResource::class, ], // Exclude specific resources from publishing 'excluded_resources' => [ App\Http\Resources\InternalResource::class, ], // Search additional directories for resources 'additional_resource_directories' => [ 'modules/Blog/Http/Resources', ],
Tip
Like models and enums, include and exclude settings accept both fully-qualified class names and directory paths.
Conditional Resource Publishing
You can disable resource publishing entirely in the config file:
// config/ts-publish.php 'publish_resources' => false,
Or publish only resources using the command flag:
php artisan ts:publish --only-resources
The --only-resources flag cannot be combined with --only-enums or --only-models.
Extending Interfaces with #[TsExtends] & Configs
The #[TsExtends] attribute allows you to specify that a generated TypeScript interface should extend one or more other interfaces. This is useful when this package's limitations doesn't include properties on your interfaces. Another use is for sharing common properties across multiple models or resources without duplication.
You can place the #[TsExtends] attribute on any model or resource class, their parent classes, or even on traits used by those classes. The specified interfaces will be included in the generated TypeScript extends clause for any class that has the attribute directly or inherits it from a parent class or trait.
The #[TsExtends] attribute can be place multiple times on the same class or trait to define multiple interfaces that should be extended. The interfaces specified in all #[TsExtends] attributes on the class and its parents/traits will be combined into a single extends clause.
The #[TsExtends] attribute accepts the following parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
extends |
string |
required |
The interface to use or the way it should be used. |
import |
?string |
null |
The import path for the extended interfaces. |
types |
string[] |
[] |
The names of the interfaces to import from the specified module. |
Example usage of #[TsExtends]
use AbeTwoThree\LaravelTsPublish\Attributes\TsExtends; #[TsExtends('ExampleInterface', '@js/types/models')] class User extends Model { // This model's generated TypeScript interface will extend ExampleInterface from @js/types/models }
The above will generate the following TypeScript interface for the User model:
import type { ExampleInterface } from '@js/types/models'; export interface User extends ExampleInterface { // ... model properties }
You can also specify the interface extension with TypeScript helpers like Partial, Pick, or Omit. Use the third argument to list the interfaces used in the extension for proper importing and the first argument will be output as-is in the generated TypeScript:
use AbeTwoThree\LaravelTsPublish\Attributes\TsExtends; #[TsExtends('Partial<ExampleInterface>', '@js/types/resources', ['ExampleInterface'])] #[TsExtends('Pick<ModularInterface, "id" | "name">', '@js/types/resources', ['ModularInterface'])] class UserResource extends JsonResource { // This resource's generated TypeScript interface will extend Partial<ExampleInterface> and Pick<ModularInterface, "id" | "name"> }
The generated TypeScript interface for UserResource will look like this:
import type { ExampleInterface, ModularInterface } from '@js/types/resources'; export interface UserResource extends Partial<ExampleInterface>, Pick<ModularInterface, "id" | "name"> { // ... resource properties }
Global Interface Extensions
In some cases, you may want all your models or resources to extend a common interface without having to add #[TsExtends] to each class. You can achieve this with the ts_extends.models and ts_extends.resources config options:
// config/ts-publish.php 'ts_extends' => [ 'models' => [ 'HasTimestamps', ['extends' => 'BaseFields', 'import' => '@/types/base'], ['extends' => 'Pick<Auditable, "created_by">', 'import' => '@/types/audit', 'types' => ['Auditable']], ], 'resources' => [ ['extends' => 'BaseResource', 'import' => '@/types/base'], ], ],
With the above config, all generated model interfaces will extend HasTimestamps, BaseFields, and Pick<Auditable, "created_by">, while all resource interfaces will extend BaseResource. The necessary imports will be included automatically.
Example model:
import type { BaseFields } from '@/types/base'; import type { Auditable } from '@/types/audit'; export interface User extends HasTimestamps, BaseFields, Pick<Auditable, "created_by"> { // ... model properties }
Example resource:
import type { BaseResource } from '@/types/base'; export interface UserResource extends BaseResource { // ... resource properties }
Excluding with #[TsExclude]
The #[TsExclude] attribute lets you exclude specific items from the TypeScript output. This is especially useful when auto_include_enum_methods or auto_include_enum_static_methods is enabled and you want to opt out individual enum methods.
#[TsExclude] can be applied to:
| Target | Effect |
|---|---|
| Enum class | Entire enum is excluded from collection and publishing |
| Enum method | Method is excluded from the TypeScript output |
| Model class | Entire model is excluded from collection and publishing |
| Model accessor | Mutator/accessor is excluded from the TypeScript output |
| Model relation | Relation is excluded from the TypeScript output |
| Resource class | Entire resource is excluded from collection and publishing |
Note
#[TsExclude] always takes priority — even if you use attributes like #[TsEnumMethod] or #[TsEnumStaticMethod] on enum methods, the methods will be excluded.
Excluding an entire enum, model, or resource
use AbeTwoThree\LaravelTsPublish\Attributes\TsExclude; #[TsExclude] enum InternalStatus: string { case Pending = 'pending'; case Processing = 'processing'; } #[TsExclude] class AuditLog extends Model { // This model will not be published to TypeScript } #[TsExclude] class InternalResource extends JsonResource { // This resource will not be published to TypeScript }
Excluding specific enum methods
use AbeTwoThree\LaravelTsPublish\Attributes\TsExclude; enum Status: string { case Active = 'active'; case Inactive = 'inactive'; // Included in TypeScript output public function label(): string { return match($this) { self::Active => 'Active', self::Inactive => 'Inactive', }; } // Excluded from TypeScript output #[TsExclude] public function internalCode(): int { return match($this) { self::Active => 100, self::Inactive => 200, }; } }
Excluding model accessors and relations
use AbeTwoThree\LaravelTsPublish\Attributes\TsExclude; class User extends Model { // Excluded from TypeScript output #[TsExclude] protected function secretToken(): Attribute { return Attribute::make( get: fn (): string => 'hidden', ); } // Excluded from TypeScript output #[TsExclude] public function auditLogs(): HasMany { return $this->hasMany(AuditLog::class); } }
Casing Configurations
This package provides two independent config options to control the casing of generated property and method names:
| Config Key | Applies To | Default |
|---|---|---|
relationship_case |
Model relationship names, _count, _exists |
'snake' |
enum_method_case |
Enum method and static method names | 'camel' |
Both accept 'snake', 'camel', or 'pascal'.
Relationship Case Style
Controls relationship names in the generated model TypeScript interfaces:
// config/ts-publish.php 'relationship_case' => 'snake', // default
| Config Value | Relationship hasMany(Post::class) |
Count | Exists |
|---|---|---|---|
'snake' |
posts: Post[] |
posts_count |
posts_exists |
'camel' |
posts: Post[] |
postsCount |
postsExists |
'pascal' |
Posts: Post[] |
PostsCount |
PostsExists |
Note
For each relationship defined on a model, this package automatically generates _count and _exists properties alongside the relation itself. These correspond to Laravel's withCount and withExists features and are included in every generated model interface.
Enum Method Case Style
Controls the casing of enum method and static method names in the generated TypeScript output:
// config/ts-publish.php 'enum_method_case' => 'camel', // default
| Config Value | Method getLabel() |
Static Method AllLabels() |
|---|---|---|
'snake' |
get_label |
all_labels |
'camel' |
getLabel |
allLabels |
'pascal' |
GetLabel |
AllLabels |
Tip
This setting applies to all enum methods — both instance methods (via #[TsEnumMethod] or auto_include_enum_methods) and static methods (via #[TsEnumStaticMethod] or auto_include_enum_static_methods). You can still override individual method names using the name parameter on the attribute.
JSON Enum HTTP API Resource
This package ships with an EnumResource — a Laravel JSON resource that transforms any PHP enum case into a flat, API-friendly array. It runs the enum through the same transformer pipeline used for TypeScript publishing, so every #[TsEnumMethod] or #[TsEnumStaticMethod] you've configured is automatically included.
The EnumResource class is useful when you need to send a single enum instance (e.g., a model's status) to the frontend as a rich object with resolved method values, rather than just the raw string or integer value.
Basic Usage
In a controller or route:
use AbeTwoThree\LaravelTsPublish\EnumResource; use App\Enums\Status; return new EnumResource(Status::Published);
From another HTTP API resource to automatically transform an enum property on a model or collection of models:
namespace App\Http\Resources; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; use AbeTwoThree\LaravelTsPublish\EnumResource; use App\Enums\Status; use App\Enums\MembershipLevel; class UserResource extends JsonResource { public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, // Assuming "status" is a model property cast to the Status enum 'status' => new EnumResource($this->status), // Can also create enum resources from any enum case, not just model properties 'membership_level' => new EnumResource(MembershipLevel::Free), ]; } }
Enum Case Instance Response
Response:
{
"name": "Published",
"value": 1,
"backed": true,
"icon": "check",
"color": "green",
}
Response Shape
Every response includes these base keys:
| Key | Type | Description |
|---|---|---|
name |
string |
The enum case name |
value |
string | int |
The backed value, or the case name for unit enums |
backed |
bool |
Whether the enum is a backed enum |
Instance methods (decorated with #[TsEnumMethod] or via the auto-include config setting) are flattened as top-level keys with the resolved value for the specific case passed to the resource. Static methods (decorated with #[TsEnumStaticMethod] or via the auto-include config setting) are included as top-level keys with the resolved value from the static method.
This allows the EnumResource to provide the same data as the published TypeScript enum when you call the from method from the @tolki/enum package on the enum with the matching case value.
Unit Enums
Unit enums (enums without a backed type) are also supported. Since they have no backed value, the value key will equal the case name and backed will be false:
return new EnumResource(Role::Admin);
{
"name": "Admin",
"value": "Admin",
"backed": false
}
Relationship to TypeScript Publishing
The EnumResource uses the same EnumTransformer pipeline as the ts:publish command. This means:
- Only methods marked with
#[TsEnumMethod](or all public methods when auto-include is enabled) are included. - Methods with required parameters but no
paramson the attribute are excluded. - The
enum_method_caseconfig setting applies to the method key names in the response.
This ensures the JSON response shape is consistent with the TypeScript types generated by this package.
Typing API Responses with AsEnum
The @tolki/enum package exports an AsEnum utility type that resolves the EnumResource JSON response shape for any published enum. This gives you full type safety when consuming enum API responses on the frontend.
import type { AsEnum } from '@tolki/enum'; import type { Status } from '@/types/enums'; // Full discriminated union of all cases type StatusResponse = AsEnum<typeof Status>; // { name: 'Draft'; value: 0; backed: true; icon: 'pencil'; color: 'gray'; ... } // | { name: 'Published'; value: 1; backed: true; icon: 'check'; color: 'green'; ... }
The optional second type parameter lets you pre-narrow to a specific case by value:
// Narrowed to a single case type DraftResponse = AsEnum<typeof Status, 0>; // { name: 'Draft'; value: 0; backed: true; icon: 'pencil'; color: 'gray'; ... }
Use it to type your API responses:
const response = await fetch(`/api/articles/${id}`); const article: { id: number; status: AsEnum<typeof Status> } = await response.json(); if (article.status.value === 0) { // TypeScript knows this is the Draft case console.log(article.status.icon); // 'pencil' }
Auto-Generated Resource Model Interface
When enums_use_tolki_package is enabled (the default), any model with enum-cast columns automatically gets a {Model}Resource companion set of interfaces. These interfaces replace each enum-backed property with AsEnum<typeof EnumName>, so you don't have to compose Omit + AsEnum manually on model properties or mutators that are cast to enums.
For a Post model that casts the database columns status, visibility, and priority to enums, the publisher will generate a PostResource interface that looks like this:
export interface Post { id: number; title: string; content: string; status: StatusType; // Original enum type visibility: VisibilityType | null; // Original enum type priority: PriorityType | null; // Original enum type } // Auto-generated — no manual typing needed export interface PostResource extends Omit<Post, 'status' | 'visibility' | 'priority'> { status: AsEnum<typeof Status>; visibility: AsEnum<typeof Visibility> | null; priority: AsEnum<typeof Priority> | null; }
Use it to type API responses that use the EnumResource class:
import type { PostResource } from '@js/types/data/models'; const response = await fetch('/api/posts/1'); const post: PostResource = await response.json(); post.status.value; // 0 | 1 post.status.icon; // 'pencil' | 'check'
The interfaces are generated for both the model-full and model-split templates. In split mode, the template will create a PostResource interface for the properties interface and a PostMutatorsResource interface for the mutators interface, since mutators can also be enum-cast properties:
export interface PostResource extends Omit<Post, 'status' | 'visibility' | 'priority'> { status: AsEnum<typeof Status>; // ... } export interface PostMutators { due_notice: DueAtNoticeType; } export interface PostMutatorsResource extends Omit<PostMutators, 'due_notice'> { due_notice: AsEnum<typeof DueAtNotice>; }
Naming conflicts are handled automatically — if two enum FQCNs share the same base name, namespace-prefixed aliases are used for both the type and const imports (e.g., AppStatus, CrmStatus).
Modular Publishing
By default, this package outputs all generated TypeScript files into flat enums/, models/, and resources/ directories:
resources/js/types/data/
├── enums/
│ ├── article-status.ts
│ ├── invoice-status.ts
│ ├── role.ts
│ └── index.ts
├── models/
│ ├── user.ts
│ ├── invoice.ts
│ ├── shipment.ts
│ └── index.ts
├── resources/
│ ├── user-resource.ts
│ ├── order-resource.ts
│ └── index.ts
└── global.d.ts
For applications that use a modular architecture (e.g., InterNACHI/modular or a custom module structure), you can enable modular publishing to organize TypeScript output into namespace-derived directory trees that mirror your PHP namespace structure.
Enabling Modular Publishing
Set modular_publishing to true in your config file:
// config/ts-publish.php 'modular_publishing' => true,
With modular publishing enabled, the output structure changes to reflect your PHP namespaces:
resources/js/types/data/
├── app/
│ ├── enums/
│ │ ├── role.ts
│ │ ├── membership-level.ts
│ │ └── index.ts
│ ├── models/
│ │ ├── user.ts
│ │ ├── order.ts
│ │ └── index.ts
│ └── http/
│ └── resources/
│ ├── user-resource.ts
│ ├── order-resource.ts
│ └── index.ts
├── accounting/
│ ├── enums/
│ │ ├── invoice-status.ts
│ │ └── index.ts
│ ├── models/
│ │ ├── invoice.ts
│ │ └── index.ts
│ └── http/
│ └── resources/
│ ├── invoice-resource.ts
│ └── index.ts
├── shipping/
│ ├── enums/
│ │ ├── shipment-status.ts
│ │ └── index.ts
│ └── models/
│ ├── shipment.ts
│ └── index.ts
└── global.d.ts
Each namespace directory gets its own barrel index.ts file that exports all types within that directory.
How It Works
Modular publishing converts each class's PHP namespace into a kebab-cased directory path. For example:
| PHP Class | Output File |
|---|---|
App\Models\User |
app/models/user.ts |
App\Enums\Role |
app/enums/role.ts |
Accounting\Models\Invoice |
accounting/models/invoice.ts |
Shipping\Enums\ShipmentStatus |
shipping/enums/shipment-status.ts |
App\Domain\Billing\Models\Invoice |
app/domain/billing/models/invoice.ts |
Import paths between generated files are automatically computed as relative paths based on the namespace directory structure:
// accounting/models/invoice.ts import { Payment } from '.'; // Same namespace (accounting/models) import { User } from '../../app/models'; // Cross-module import import { InvoiceStatusType } from '../enums'; // Sibling namespace (accounting/enums) export interface Invoice { id: number; user_id: number; number: string; status: InvoiceStatusType; subtotal: number; tax: number; total: number; // ... } export interface InvoiceRelations { user: User; payments: Payment[]; // ... } export interface InvoiceAll extends Invoice, InvoiceRelations {}
Stripping a Namespace Prefix
If your modules live under a common namespace prefix (e.g., Modules\), you can strip it from the output path using the namespace_strip_prefix config option:
// config/ts-publish.php 'namespace_strip_prefix' => 'Modules\\',
| PHP Class | Without Strip Prefix | With 'Modules\\' Strip Prefix |
|---|---|---|
Modules\Blog\Models\Article |
modules/blog/models/article.ts |
blog/models/article.ts |
Modules\Shipping\Enums\Carrier |
modules/shipping/enums/carrier.ts |
shipping/enums/carrier.ts |
This keeps the output directory structure clean by removing the redundant prefix.
Barrel Files
In modular mode, each namespace directory receives its own barrel index.ts file. For example, accounting/models/index.ts:
export * from './invoice';
And app/models/index.ts:
export * from './address'; export * from './order'; export * from './product'; export * from './user'; // ... all models in this namespace
This allows you to import types from any namespace barrel:
import { User, Order } from '@js/types/data/app/models'; import { Invoice } from '@js/types/data/accounting/models'; import { InvoiceStatusType } from '@js/types/data/accounting/enums';
Extending & Customizing the Pipeline
This package uses a Collector → Generator → Transformer → Writer → Template pipeline. Each stage is fully configurable via the config file, allowing you to extend or replace any component without modifying the package itself:
| Pipeline Stage | Config Key | Default Class | Responsibility |
|---|---|---|---|
| Collector | model_collector_class |
ModelsCollector |
Discovers PHP model classes |
| Collector | enum_collector_class |
EnumsCollector |
Discovers PHP enum classes |
| Collector | resource_collector_class |
ResourcesCollector |
Discovers PHP resource classes |
| Generator | model_generator_class |
ModelGenerator |
Orchestrates transforming and writing |
| Generator | enum_generator_class |
EnumGenerator |
Orchestrates transforming and writing |
| Generator | resource_generator_class |
ResourceGenerator |
Orchestrates transforming and writing |
| Transformer | model_transformer_class |
ModelTransformer |
Converts PHP class into TypeScript data |
| Transformer | enum_transformer_class |
EnumTransformer |
Converts PHP enum into TypeScript data |
| Transformer | resource_transformer_class |
ResourceTransformer |
Converts PHP resource into TypeScript data |
| Writer | model_writer_class |
ModelWriter |
Writes TypeScript model files |
| Writer | enum_writer_class |
EnumWriter |
Writes TypeScript enum files |
| Writer | resource_writer_class |
ResourceWriter |
Writes TypeScript resource files |
| Writer | barrel_writer_class |
BarrelWriter |
Writes barrel index.ts files |
| Writer | globals_writer_class |
GlobalsWriter |
Writes global declaration file |
| Writer | json_writer_class |
JsonWriter |
Writes JSON definitions file |
| Writer | watcher_json_writer_class |
WatcherJsonWriter |
Writes collected files JSON for watchers |
| Template | model_template |
model-split |
Blade template for model output |
| Template | enum_template |
enum |
Blade template for enum output |
| Template | resource_template |
resource |
Blade template for resource output |
To swap a component, create a class that extends the default and override the config key:
// config/ts-publish.php 'model_transformer_class' => App\TypeScript\CustomModelTransformer::class,
Tip
You can also publish and customize the Blade templates directly with php artisan vendor:publish --tag="laravel-ts-publish-views" if you only need to change the output format without modifying the pipeline logic.
Pre-Command Hook
If you need to run custom logic right before the ts:publish command executes — such as dynamically configuring directories, adjusting included/excluded models, or performing any setup that requires processing — you can register a pre-command hook using callCommandUsing.
This is useful because the closure is only executed when the ts:publish command actually runs, not at service provider boot time. This keeps your boot process fast and avoids unnecessary overhead on every request.
Basic Usage
In your AppServiceProvider (or any service provider), register a closure in the boot method:
use AbeTwoThree\LaravelTsPublish\LaravelTsPublish; public function boot(): void { LaravelTsPublish::callCommandUsing(function () { // This only runs when `php artisan ts:publish` is executed config()->set('ts-publish.additional_model_directories', [ 'modules/Blog/Models', 'modules/Shop/Models', ]); config()->set('ts-publish.additional_resource_directories', [ 'modules/Blog/Http/Resources', 'modules/Shop/Http/Resources', ]); }); }
Dynamic Directory Discovery
A common use case is using Symfony Finder to automatically discover module directories:
use AbeTwoThree\LaravelTsPublish\LaravelTsPublish; use Symfony\Component\Finder\Finder; public function boot(): void { LaravelTsPublish::callCommandUsing(function () { $modelDirs = collect(Finder::create()->directories()->in(base_path('modules'))->name('Models')->depth(1)) ->map(fn ($dir) => $dir->getRelativePathname()) ->values() ->all(); $enumDirs = collect(Finder::create()->directories()->in(base_path('modules'))->name('Enums')->depth(1)) ->map(fn ($dir) => $dir->getRelativePathname()) ->values() ->all(); $resourceDirs = collect(Finder::create()->directories()->in(base_path('modules'))->name('Resources')->depth(2)) ->map(fn ($dir) => $dir->getRelativePathname()) ->values() ->all(); config()->set('ts-publish.additional_model_directories', $modelDirs); config()->set('ts-publish.additional_enum_directories', $enumDirs); config()->set('ts-publish.additional_resource_directories', $resourceDirs); }); }
Note
Only one closure can be registered at a time. Calling callCommandUsing again will replace the previous closure.
Configuration Reference
Below is a quick reference of all available configuration options:
| Config Key | Type | Default | Description |
|---|---|---|---|
run_after_migrate |
bool |
true |
Re-publish types after running migrations |
output_to_files |
bool |
true |
Write generated TypeScript to .ts files |
output_directory |
string |
resources/js/types/data |
Directory where TypeScript files are written |
publish_enums |
bool |
true |
Enable or disable enum publishing |
publish_models |
bool |
true |
Enable or disable model publishing |
publish_resources |
bool |
true |
Enable or disable resource publishing |
modular_publishing |
bool |
false |
Organize output into namespace-derived directory trees |
namespace_strip_prefix |
string |
'' |
Strip this prefix from namespaces in modular mode |
relationship_case |
string |
'snake' |
Case style for relationships: snake, camel, or pascal |
nullable_relations |
bool |
true |
Append | null to singular relation types based on smart detection |
relation_nullability_map |
array |
[] |
Override nullability strategy per relation type |
enum_method_case |
string |
'camel' |
Case style for enum methods: snake, camel, or pascal |
timestamps_as_date |
bool |
false |
Map date/datetime/timestamp to Date instead of string |
custom_ts_mappings |
array |
[] |
Override or extend PHP-to-TypeScript type mappings |
auto_include_enum_methods |
bool |
false |
Include all public non-static enum methods without attributes |
auto_include_enum_static_methods |
bool |
false |
Include all public static enum methods without attributes |
enum_metadata_enabled |
bool |
true |
Include _cases, _methods, _static metadata on enums |
enums_use_tolki_package |
bool |
true |
Wrap enums in defineEnum() from @tolki/enum |
output_globals_file |
bool |
false |
Generate a global.d.ts namespace file |
global_directory |
?string |
null | Directory for the global declaration file |
global_filename |
string |
laravel-ts-global.d.ts |
Filename for the global declaration file |
models_namespace |
string |
'models' |
Namespace label used in the global declaration file |
enums_namespace |
string |
'enums' |
Namespace label used in the global declaration file |
resources_namespace |
string |
'resources' |
Namespace label used in the global declaration file |
output_json_file |
bool |
false |
Output all definitions as a JSON file |
json_filename |
string |
laravel-ts-definitions.json |
Filename for the JSON output |
json_output_directory |
?string |
null | Directory for the JSON output |
output_collected_files_json |
bool |
true |
Output collected PHP file paths as JSON (for file watchers) |
collected_files_json_filename |
string |
laravel-ts-collected-files.json |
Filename for the collected files JSON |
collected_files_json_output_directory |
?string |
null | Directory for the collected files JSON |
model_template |
string |
laravel-ts-publish::model-split |
Blade template for model TypeScript output |
enum_template |
string |
laravel-ts-publish::enum |
Blade template for enum TypeScript output |
resource_template |
string |
laravel-ts-publish::resource |
Blade template for resource TypeScript output |
globals_template |
string |
laravel-ts-publish::globals |
Blade template for global declaration output |
included_models |
array |
[] |
Only publish these models (empty = all) |
excluded_models |
array |
[] |
Exclude these models from publishing |
additional_model_directories |
array |
[] |
Extra directories to search for models |
included_enums |
array |
[] |
Only publish these enums (empty = all) |
excluded_enums |
array |
[] |
Exclude these enums from publishing |
additional_enum_directories |
array |
[] |
Extra directories to search for enums |
included_resources |
array |
[] |
Only publish these resources (empty = all) |
excluded_resources |
array |
[] |
Exclude these resources from publishing |
additional_resource_directories |
array |
[] |
Extra directories to search for resources |
Note
The 20 pipeline class config keys (e.g. model_collector_class, enum_writer_class, resource_transformer_class, etc.) are listed in the Extending & Customizing the Pipeline section above.
See the full configuration file for detailed comments on each option.
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.