Enums
The Laravel TypeScript Publisher turns every PHP enum into a TypeScript object that behaves like the PHP enum, not only a union of its values. .from(), .tryFrom() and .cases() work on it through the @tolki/ts package, and you can publish your enum's own methods and static methods alongside the cases.
For example, this enum publishes its cases and its label() method:
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',
};
}
}On the frontend, you use the published enum much like the PHP one:
import { Status } from "@js/types/data/app/enums";
Status.Active; // 'active'
Status.label.Active; // 'Active User'
Status.from("active").label; // 'Active User', a PHP-like enum "instance"@tolki/ts works together with the files this package generates and isn't meant to be used on its own. See Installing @tolki/ts to add it to your project. To send one enum case to the frontend as a full object, return it through EnumResource.
How Enums Are Generated
The ts:publish command publishes each enum as follows:
- One file per enum:
App\Enums\Statusis written toapp/enums/status.ts, following the enum's namespace. Each namespace directory also gets anindex.tsbarrel that re-exports its files (export * from './status'). - Backed and unit enums: enums backed by
intorstringand unit enums are all supported. A unit enum uses each case name as its value. - Metadata: with
enums.metadata_enabledon (the default), each enum has abackedflag and a_caseslist. It also gets a_methodslist when it publishes instance methods, and a_staticlist when it publishes static methods. - The
defineEnum()wrapper: withenums.use_tolki_packagealso on (the default), the enum is wrapped indefineEnum()from@tolki/ts.
Anatomy of a Generated Enum
For a Color enum with case descriptions and a hex() method, the published file looks like this:
import { defineEnum } from "@tolki/ts";
/**
* Colors a label can use.
*
* @see App\Enums\Color
*/
export const Color = defineEnum({
/** Primary red color */
Red: "red",
/** Primary green color */
Green: "green",
Blue: "blue",
backed: true,
/** Get the hex code for the color */
hex: {
Red: "#EF4444",
Green: "#22C55E",
Blue: "#3B82F6",
},
_cases: ["Red", "Green", "Blue"],
_methods: ["hex"],
} as const);
export type ColorType = "red" | "green" | "blue";
export type ColorKind = "Red" | "Green" | "Blue";Each part of the file does one job:
- JSDoc comments: the PHPDoc descriptions of the enum and its cases. See PHPDoc Descriptions.
backed: whether the PHP enum is backed.EnumResourceresponses include the same key, andAsEnumtypes it.hex: an instance method published with#[TsEnumMethod], with one value per case._cases,_methodsand_static: the lists the@tolki/tsfunctions read to build a PHP-like instance. See Runtime Utilities.ColorTypeandColorKind: type aliases for the case values and the case names. See Value & Key Types.
Enum Attributes
All attributes live in the AbeTwoThree\LaravelTsPublish\Attributes namespace.
| Attribute | Target | Description |
|---|---|---|
#[TsEnumMethod] | Method | Publish a method's return value. Called once per case, producing an object keyed by case. |
#[TsEnumStaticMethod] | Static method | Publish a static method's return value. Called once and added as a top-level property. |
#[TsEnum] | Enum class | Rename the enum or add a description. |
#[TsCase] | Enum case | Rename a case, change its frontend value, or add a description. |
#[TsExclude] | Class, method | Exclude a whole enum or one method. |
#[TsEnumMethod]
Mark an instance method with #[TsEnumMethod] to publish its return value for every case. Here the attribute also renames the key and adds a description:
use AbeTwoThree\LaravelTsPublish\Attributes\TsEnumMethod;
enum Status: string
{
case Active = 'active';
case Inactive = 'inactive';
#[TsEnumMethod(name: 'statusLabel', description: 'Human-readable label')]
public function label(): string
{
return match($this) {
self::Active => 'Active User',
self::Inactive => 'Inactive User',
};
}
}The method's values publish under the new key, one per case:
export const Status = defineEnum({
Active: "active",
Inactive: "inactive",
backed: true,
/** Human-readable label */
statusLabel: {
Active: "Active User",
Inactive: "Inactive User",
},
_cases: ["Active", "Inactive"],
_methods: ["statusLabel"],
} as const);The attribute takes these parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
name | string | Method name | The key name in the output. enums.method_case applies to it too. |
description | string | '' | A JSDoc comment above the property. |
params | array | [] | Named arguments to call the method with. See Methods With Required Parameters. |
Methods With Required Parameters
A method with required parameters is skipped by default, instead of publishing misleading null values. To include one, pass params. The package calls the method once per case, with those values as named arguments:
#[TsEnumMethod(description: 'Compare with threshold', params: ['threshold' => 1])]
public function isAboveThreshold(int $threshold): bool
{
return $this->value > $threshold;
}params values must be constant expressions, because they're PHP attribute arguments. A method whose parameters are all optional needs no params. If a method throws for a case, that case's value publishes as null.
Object and Array Values
An instance or static method's value publishes as json_encode() writes it, which is also what an EnumResource response sends. An object publishes as its JSON:
- A Carbon date publishes as its ISO 8601 string, such as
'2026-01-01T17:00:00.000000Z'. - A collection publishes as its items.
- A
JsonSerializableobject publishes what itsjsonSerialize()returns. - Any other object publishes its public properties. An object with only protected or private properties publishes
{}, and a plain PHPDateTimepublishes itsdate,timezone_typeandtimezonefields.
A value that json_encode() can't write, such as NAN or a string that isn't valid UTF-8, publishes as null.
Arrays With Out-of-Order Integer Keys
An array whose integer keys don't count up from 0 in order publishes as an object keyed by those numbers, because that's how json_encode() writes it. array_filter() keeps each item's key, so it returns such an array whenever it removes an item before one it keeps. An EnumResource response sends the same value as a list, so ts:publish warns about the method. Keys that are numeric strings, such as '01', are re-indexed the same way. Wrap the array in array_values() to publish and send a list, or use keys that aren't numeric to publish and send an object:
#[TsEnumMethod]
public function higherPriorities(): array
{
return array_values(array_filter(self::cases(), fn (self $case) => $case->value > $this->value));
}#[TsEnumStaticMethod]
#[TsEnumStaticMethod] takes the same name, description and params options. The package calls the method once, not once per case, and publishes its return value as one top-level property:
use AbeTwoThree\LaravelTsPublish\Attributes\TsEnumStaticMethod;
#[TsEnumStaticMethod]
public static function options(): array
{
return array_map(fn (self $s) => ['value' => $s->value, 'label' => $s->name], self::cases());
}The return value publishes as it is:
export const Status = defineEnum({
// ...cases
options: [
{ value: "active", label: "Active" },
{ value: "inactive", label: "Inactive" },
],
_static: ["options"],
} as const);A static method with required parameters is skipped unless you pass params, the same as with #[TsEnumMethod].
#[TsEnum]
#[TsEnum] renames the enum's TypeScript const, or adds a description to it:
use AbeTwoThree\LaravelTsPublish\Attributes\TsEnum;
#[TsEnum('UserStatus', description: 'All possible user account statuses')]
enum Status: string { case Active = 'active'; case Inactive = 'inactive'; }The new name also names the type aliases:
/**
* All possible user account statuses
*
* @see App\Enums\Status
*/
export const UserStatus = defineEnum({
Active: "active",
Inactive: "inactive",
// ...
} as const);
export type UserStatusType = "active" | "inactive";
export type UserStatusKind = "Active" | "Inactive";The file takes the new name too (user-status.ts), and models that use the enum refer to it as UserStatusType. The attribute takes these parameters:
| Parameter | Type | Description |
|---|---|---|
name | string | The TypeScript const name. Use it when two enums in one namespace publish the same name, or when a route file uses two same-named enums from different namespaces. |
description | string | A JSDoc comment. It takes priority over any PHPDoc description. |
An Enum Named Like Another Enum's Type
An enum Role publishes the type RoleType. A second enum named RoleType in the same namespace publishes a const with that name, and the two can't share a barrel file or a global namespace. ts:publish warns when it finds such a pair. Rename one of them with #[TsEnum]. In two different namespaces this pair is fine, because a file that imports both gets an alias for one.
#[TsCase]
#[TsCase] renames a case, changes its frontend value, or adds a description:
use AbeTwoThree\LaravelTsPublish\Attributes\TsCase;
enum Status: int
{
#[TsCase(name: 'Enabled', value: 'enabled', description: 'The user can sign in')]
case Active = 1;
#[TsCase(name: 'Disabled', value: 'disabled')]
case Inactive = 0;
}The published enum and its type aliases use the new names and values:
export const Status = defineEnum({
/** The user can sign in */
Enabled: "enabled",
Disabled: "disabled",
// ...
} as const);
export type StatusType = "enabled" | "disabled";
export type StatusKind = "Enabled" | "Disabled";The attribute takes these parameters:
| Parameter | Type | Description |
|---|---|---|
name | string | The case's key name. |
value | string | int | The case's value. |
description | string | A JSDoc comment above the case. |
WARNING
A changed value applies to the published enum and to EnumResource responses. Laravel still serializes an enum-cast model column with the PHP value, so a model's {Enum}Type property won't match the JSON your API sends for that column.
Value & Key Types
Every enum gets a {Name}Type alias of its case values. A backed enum also gets a {Name}Kind alias of its case names. A unit enum doesn't need one, because its values already are its case names:
export type StatusType = "active" | "inactive";
export type StatusKind = "Active" | "Inactive"; // backed enums onlyUse the aliases to type a raw value or a case name:
import type { StatusType, StatusKind } from "@js/types/data/app/enums";
function setStatus(status: StatusType) {} // only 'active' | 'inactive'
function setStatusByKey(status: StatusKind) {} // only 'Active' | 'Inactive'Metadata & the defineEnum() Wrapper
With enums.metadata_enabled on, every enum has a _cases list, plus _methods and _static lists when it publishes instance or static methods. You don't read these lists yourself. defineEnum() and the standalone from, tryFrom and cases functions use them to build a PHP-like instance from a case value:
import { Status } from "@js/types/data/app/enums";
const instance = Status.from("active");
// { name: 'Active', value: 'active', backed: true, label: 'Active User', options: [...] }An instance method resolves to its value for the matched case (label), and a static method passes through as it is (options). See Runtime Utilities for every function.
Disabling Metadata or the @tolki/ts Wrapper
Set enums.metadata_enabled to false to publish plain enum objects, without backed, the _cases, _methods and _static lists, or the defineEnum() wrapper:
// config/ts-publish.php
'enums' => [
'metadata_enabled' => false,
],To keep the metadata but drop the wrapper, turn off enums.use_tolki_package instead:
'enums' => [
'metadata_enabled' => true,
'use_tolki_package' => false,
],use_tolki_package Affects More Than Enums
The setting controls every AsEnum<> type the package publishes:
- Models: the
{Model}Resourceinterfaces and theimport { type AsEnum }line aren't generated. The main{Model}interface uses{Enum}Typefor enum columns either way. See Enum-Typed Columns. - API resources: an
EnumResource::make()property publishes asStatusTypeinstead ofAsEnum<typeof Status>, and the enum is imported as a type (import type { StatusType }) instead of a value (import { Status }). See Enum Properties WithEnumResource. - Inertia shared data: an
EnumResourceprop shared fromHandleInertiaRequests::share()publishes asRoleTypewith a type import, instead ofAsEnum<typeof Role>with a value import. See Inertia.
Leave it on unless your enums must not depend on @tolki/ts at runtime. Routes import @tolki/ts whatever this setting is.
Auto-Including All Enum Methods
By default, only methods marked with #[TsEnumMethod] or #[TsEnumStaticMethod] publish. To publish every public method without marking each one, turn on auto-include:
// config/ts-publish.php
'enums' => [
'auto_include_methods' => true, // all public instance methods
'auto_include_static_methods' => true, // all public static methods
],Auto-include adds public methods only. It never adds private or protected methods, magic methods such as __call(), or PHP's built-in cases(), from() and tryFrom(). You can still mark a method with its attribute to set its name, description or params while auto-include handles the rest. A method with required parameters is still skipped unless its attribute passes params.
WARNING
Both settings are off by default because they publish the return value of every public method on your enums. Check that's what you want before you turn them on.
PHPDoc Descriptions
The package reads doc blocks and turns their descriptions into JSDoc comments:
| Location | JSDoc placement |
|---|---|
| Enum class | Above the export const declaration |
| Enum case | Above the case property |
| Instance method | Above the method property |
| Static method | Above the static method property |
Tag lines such as @param, @return and @phpstan-type are removed, so only the prose description carries over. When an attribute sets a description too, the attribute wins over the PHPDoc block.
Filtering & Excluding Enums
Enums use the same include and exclude settings as models, resources and the other published types:
// config/ts-publish.php
'enums' => [
'included' => [App\Enums\Status::class], // only these (empty = all)
'excluded' => [App\Enums\Internal::class], // never publish these
'additional_directories' => ['modules/Blog/Enums'],
],#[TsExclude] on the enum class excludes the whole enum. On a method, it excludes only that method, even when auto-include or an attribute would otherwise publish it. See Excluding Enum Methods on the Excluding Content page, which also covers models, resources and routes.
Casing
enums.method_case sets the casing of published method keys: 'camel' (default), 'snake' or 'pascal'. It doesn't rename your PHP methods, and it also applies to a key set with the attributes' name parameter. See enums.method_case on the Casing Configurations page for examples.
Runtime Utilities
@tolki/ts exports these functions, which give defineEnum() its PHP-like behavior. You normally call them through the published enum (Status.from(...)) instead of importing them.
cases
Like PHP's cases method, cases returns an array with one resolved instance per case:
import { cases } from "@tolki/ts";
import { Status } from "@js/types/data/app/enums";
const result = cases(Status); // one resolved instance per case in StatusdefineEnum
The published files wrap each enum in defineEnum, which binds from, tryFrom and cases to the enum object:
import { defineEnum } from "@tolki/ts";
const Status = defineEnum({
ACTIVE: "active",
INACTIVE: "inactive",
_cases: ["ACTIVE", "INACTIVE"],
_methods: [],
_static: [],
});
Status.cases();
Status.from("active");
Status.tryFrom("non-valid-key"); // nullfrom
Like PHP's from, from returns the enum instance for a value, and throws if the value matches no case:
import { from } from "@tolki/ts";
import { Status } from "@js/types/data/app/enums";
const result = from(Status, "active");
from(Status, "non-valid-key"); // throwstryFrom
Like PHP's tryFrom, tryFrom works like from but returns null for a value that matches no case:
import { tryFrom } from "@tolki/ts";
import { Status } from "@js/types/data/app/enums";
const result = tryFrom(Status, "active");
const missing = tryFrom(Status, "non-valid-key"); // nullVite Plugin
The @tolki/ts package ships a Vite plugin that republishes enums and routes automatically during development. See the Vite Plugin documentation.
Configuration Reference
The Configuration Reference lists every enums.* config key, including the pipeline class overrides for advanced customization.
Type Reference
The runtime functions come from @tolki/ts. The types come from @tolki/types, and @tolki/ts re-exports AsEnum, CaseValue, DefineEnumResult, EnumConst and FromResult:
| Export | Description |
|---|---|
defineEnum() | Wraps a raw enum const with bound from, tryFrom and cases helpers. |
from(), tryFrom(), cases() | Standalone versions that take the enum object as their first argument. |
EnumConst | The base shape every generated enum const satisfies. |
CaseKeys<T> / CaseValue<T> | The union of case key names, or of case values, for an enum const. |
MethodKeys<T> / StaticKeys<T> | The union of instance method key names, or of static method key names. |
FromResult<T, V> | The resolved instance type that from(T, V) returns. |
DefineEnumResult<T> | The return type of defineEnum(): the const plus its bound helpers. |
AsEnum<T, V?> | A resolved instance at the type level: a discriminated union across all cases, or one case with the second parameter. It types the JSON that the EnumResource API resource returns. |