Generate runtime-usable TypeScript enums from PHP Laravel enums with namespace organization. Unlike traditional TypeScript enums which are only available at compile-time, this package generates TypeScript code that provides both type safety and runtime functionality in organized namespaces.
- 🚀 Runtime-usable: Generated TypeScript provides both types and runtime objects
- 🔄 Automatic Generation: Scan your PHP enums and generate TypeScript equivalents
- 🛡️ Type Safety: Full TypeScript type support with union types
- 🧰 Utility Functions: Built-in validation, conversion, and enumeration utilities
- 📁 Namespace Organization: Groups enums by PHP namespace into TypeScript namespaces
- 🎯 Single File Output: All enums in one organized TypeScript file
- 🔧 Configurable: Choose between type-only, runtime objects, per-type utils, or generic utils
- 🔍 Smart Detection: Automatically detects backed vs pure enums
- 📦 Laravel Integration: Seamless Laravel artisan command integration
Install the package via Composer:
composer require diagonal/laravel-ts-enum-generator
Publish the configuration file:
php artisan vendor:publish --tag=ts-enum-generator-config
- Create PHP enums in your Laravel project:
<?php
// app/Enums/UserRole.php
namespace App\Enums;
enum UserRole: string
{
case ADMIN = 'admin';
case USER = 'user';
case MODERATOR = 'moderator';
}
<?php
// app/Enums/Status.php
namespace App\Enums;
enum Status
{
case PENDING;
case APPROVED;
case REJECTED;
}
- Generate TypeScript enums:
php artisan ts-enums:generate
- Use in your TypeScript/JavaScript code:
// Types for compile-time checking
function setUserRole(role: App.Enums.UserRole) {
// role is typed as 'admin' | 'user' | 'moderator'
}
// Runtime object access
const adminRole = App.Enums.UserRole.ADMIN; // 'admin'
// Per-type utilities
const isValid = App.Enums.UserRoleUtils.isValid('admin'); // true
const allValues = App.Enums.UserRoleUtils.values; // ['admin', 'user', 'moderator']
// Generic utilities (work with any enum)
const isValidGeneric = App.Enums.EnumUtils.isValid(App.Enums.UserRole, 'admin'); // true
For PHP enums like this:
namespace App\Enums;
enum UserRole: string
{
case ADMIN = 'admin';
case USER = 'user';
case MODERATOR = 'moderator';
}
enum Status
{
case PENDING;
case APPROVED;
case REJECTED;
}
The package generates a single enums.ts
file:
// Auto-generated TypeScript enums from PHP enums
declare namespace App.Enums {
export type UserRole = 'admin' | 'user' | 'moderator';
export const UserRole = {
ADMIN: 'admin' as const,
USER: 'user' as const,
MODERATOR: 'moderator' as const,
} as const;
export const UserRoleUtils = {
values: Object.values(UserRole),
keys: Object.keys(UserRole) as Array<keyof typeof UserRole>,
entries: Object.entries(UserRole) as Array<[keyof typeof UserRole, UserRole]>,
isValid: (value: any): value is UserRole => Object.values(UserRole).includes(value),
fromKey: (key: keyof typeof UserRole): UserRole => UserRole[key],
};
export type Status = 'PENDING' | 'APPROVED' | 'REJECTED';
export const Status = {
PENDING: 'PENDING' as const,
APPROVED: 'APPROVED' as const,
REJECTED: 'REJECTED' as const,
} as const;
export const StatusUtils = {
values: Object.values(Status),
keys: Object.keys(Status) as Array<keyof typeof Status>,
entries: Object.entries(Status) as Array<[keyof typeof Status, Status]>,
isValid: (value: any): value is Status => Object.values(Status).includes(value),
fromKey: (key: keyof typeof Status): Status => Status[key],
};
export const EnumUtils = {
isValid: <T extends Record<string, string>>(enumObject: T, value: any): value is T[keyof T] => {
return Object.values(enumObject).includes(value);
},
values: <T extends Record<string, string>>(enumObject: T): T[keyof T][] => {
return Object.values(enumObject);
},
keys: <T extends Record<string, string>>(enumObject: T): (keyof T)[] => {
return Object.keys(enumObject) as (keyof T)[];
},
entries: <T extends Record<string, string>>(enumObject: T): [keyof T, T[keyof T]][] => {
return Object.entries(enumObject) as [keyof T, T[keyof T]][];
},
fromKey: <T extends Record<string, string>>(enumObject: T, key: keyof T): T[keyof T] => {
return enumObject[key];
},
};
}
// Type-safe function parameters
function checkPermission(role: App.Enums.UserRole) {
if (role === App.Enums.UserRole.ADMIN) {
return 'full access';
}
return 'limited access';
}
// Runtime object usage
const currentRole = App.Enums.UserRole.USER; // 'user'
// Validate user input with strong typing
function processUserRole(input: unknown) {
if (App.Enums.UserRoleUtils.isValid(input)) {
// input is now typed as App.Enums.UserRole
console.log(`Valid role: ${input}`);
}
}
// API response validation
const apiResponse = await fetch('/api/user');
const userData = await apiResponse.json();
if (App.Enums.UserRoleUtils.isValid(userData.role)) {
// Safe to use userData.role as App.Enums.UserRole
}
// Generic utilities work with any enum
const roles = App.Enums.EnumUtils.values(App.Enums.UserRole); // ['admin', 'user', 'moderator']
const statuses = App.Enums.EnumUtils.values(App.Enums.Status); // ['PENDING', 'APPROVED', 'REJECTED']
// Generic validation
const isValidRole = App.Enums.EnumUtils.isValid(App.Enums.UserRole, 'admin'); // true
const isValidStatus = App.Enums.EnumUtils.isValid(App.Enums.Status, 'PENDING'); // true
// Get all possible values
const allRoles = App.Enums.UserRoleUtils.values; // ['admin', 'user', 'moderator']
// Get all keys
const allKeys = App.Enums.UserRoleUtils.keys; // ['ADMIN', 'USER', 'MODERATOR']
// Get key-value pairs
const entries = App.Enums.UserRoleUtils.entries; // [['ADMIN', 'admin'], ...]
// Convert from key to value
const roleValue = App.Enums.UserRoleUtils.fromKey('ADMIN'); // 'admin'
php artisan ts-enums:generate [options]
--source=PATH
: Source directory containing PHP enums (default:app/Enums
)--destination=PATH
: Destination directory for TypeScript files (default:resources/ts/enums
)
# Generate from custom source directory
php artisan ts-enums:generate --source=app/Models/Enums
# Generate to custom destination
php artisan ts-enums:generate --destination=resources/js/types/enums
# Custom source and destination
php artisan ts-enums:generate --source=app/Enums --destination=frontend/src/types
The config/ts-enum-generator.php
file provides extensive customization options:
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Source Directory
|--------------------------------------------------------------------------
*/
'default_source_dir' => 'app/Enums',
/*
|--------------------------------------------------------------------------
| Default Destination Directory
|--------------------------------------------------------------------------
*/
'default_destination_dir' => 'resources/ts/enums',
/*
|--------------------------------------------------------------------------
| Output Options
|--------------------------------------------------------------------------
*/
'output' => [
'use_namespaces' => true, // Use declare namespace syntax
'single_file' => true, // Generate single file vs multiple files
'output_filename' => 'enums.ts', // Name of the generated file
'namespace_separator' => '.', // Separator for namespace parts
'include_comments' => true, // Include auto-generated comments
'generate_runtime_objects' => true, // Generate const objects
'generate_per_type_utils' => true, // Generate XxxUtils for each enum
'generate_generic_utils' => true, // Generate generic EnumUtils
'types_only' => false, // Only generate type definitions
],
/*
|--------------------------------------------------------------------------
| Namespace Configuration
|--------------------------------------------------------------------------
*/
'namespace' => [
'root_namespace' => null, // If null, uses the first namespace part
'strip_namespace_prefix' => null, // Strip this prefix from PHP namespaces
'namespace_suffix' => 'Enums', // Add this suffix to namespace
],
/*
|--------------------------------------------------------------------------
| File Processing
|--------------------------------------------------------------------------
*/
'processing' => [
'file_extensions' => ['php'],
'exclude_patterns' => [
'*/Tests/*',
'*/tests/*',
'*/vendor/*',
],
'include_only_enums' => true,
],
];
use_namespaces
: Whentrue
, generatesdeclare namespace
syntax. Whenfalse
, generates traditional export syntax.single_file
: Whentrue
, all enums go into one file. Whenfalse
, each enum gets its own file.output_filename
: Name of the generated file whensingle_file
istrue
.namespace_separator
: Character used to separate namespace parts (e.g.,App.Enums
vsApp_Enums
).include_comments
: Whether to include auto-generated comments in the output.generate_runtime_objects
: Whether to generateconst
objects for runtime usage.generate_per_type_utils
: Whether to generateXxxUtils
objects for each enum type.generate_generic_utils
: Whether to generate genericEnumUtils
that work with any enum.types_only
: Whentrue
, only generates type definitions (like.d.ts
files).
root_namespace
: Override the root namespace. Ifnull
, uses the first part of the PHP namespace.strip_namespace_prefix
: Remove this prefix from PHP namespaces (e.g.,App\\
becomes empty).namespace_suffix
: Add this suffix to the end of namespaces (e.g.,App
becomesApp.Enums
).
Types Only (like .d.ts files):
'output' => [
'types_only' => true,
'generate_runtime_objects' => false,
'generate_per_type_utils' => false,
'generate_generic_utils' => false,
],
Runtime Objects Only (no utilities):
'output' => [
'generate_runtime_objects' => true,
'generate_per_type_utils' => false,
'generate_generic_utils' => false,
],
Generic Utils Only:
'output' => [
'generate_per_type_utils' => false,
'generate_generic_utils' => true,
],
Custom Namespace Structure:
'namespace' => [
'strip_namespace_prefix' => 'App\\',
'namespace_suffix' => 'Types',
],
// App\Enums\UserRole becomes Enums.Types.UserRole
Use glob patterns to scan multiple directories:
php artisan ts-enums:generate --source="app/*/Enums"
This will scan app/Models/Enums
, app/Services/Enums
, etc.
Control how PHP namespaces map to TypeScript namespaces:
// PHP: App\Admin\Enums\UserRole
// Default TS: App.Admin.Enums.UserRole
// With strip_namespace_prefix = 'App\\'
// Result TS: Admin.Enums.UserRole
// With namespace_suffix = 'Types'
// Result TS: Admin.Enums.Types.UserRole
The generated types work seamlessly with TypeScript:
// Function parameters
function updateStatus(status: App.Enums.Status) { }
// Object properties
interface User {
role: App.Enums.UserRole;
status: App.Enums.Status;
}
// Union with other types
type UserAction = App.Enums.UserRole | 'guest';
// Generic constraints
function processEnum<T extends App.Enums.UserRole>(value: T): T {
return value;
}
Problem: TypeScript namespace conflicts when using multiple enum sources.
Solution: Use strip_namespace_prefix
to avoid deeply nested namespaces:
'namespace' => [
'strip_namespace_prefix' => 'App\\',
],
Problem: Choosing between generic utilities and per-type utilities.
Per-Type Utils (Better IntelliSense):
App.Enums.UserRoleUtils.isValid('admin'); // Strong typing, better autocomplete
Generic Utils (DRY, Less Code):
App.Enums.EnumUtils.isValid(App.Enums.UserRole, 'admin'); // More flexible, less generated code
Recommendation: Use both for maximum flexibility, or disable one if you prefer a specific approach.
Problem: Generated file becomes very large with many enums.
Solutions:
- Set
generate_per_type_utils
tofalse
and use only generic utils - Set
types_only
totrue
if you don't need runtime functionality - Use multiple source directories with different configurations
Problem: How to import the generated types and objects.
Solution: Import from the generated file:
// For types
import type { UserRole, Status } from './path/to/enums';
// For runtime objects and utilities
import { UserRole, UserRoleUtils, EnumUtils } from './path/to/enums';
Problem: Enum values with special characters.
Solution: The generator automatically escapes special characters:
enum SpecialChars: string
{
case QUOTE = "It's working";
case BACKSLASH = "path\\to\\file";
}
Becomes:
export const SpecialChars = {
QUOTE: 'It\'s working' as const,
BACKSLASH: 'path\\to\\file' as const,
} as const;
Problem: Integrating with build tools.
Solution: Add the generation command to your build process:
{
"scripts": {
"build": "php artisan ts-enums:generate && npm run build"
}
}
The package supports both backed and pure enums:
// Backed enum
enum UserRole: string
{
case ADMIN = 'admin';
case USER = 'user';
}
// Pure enum
enum Status
{
case PENDING;
case APPROVED;
}
Both generate identical TypeScript structures, with pure enums using the case name as the value.
If you're upgrading from a version that generated separate files, the new namespace approach offers several advantages:
- Single Import: Import everything from one file
- Namespace Organization: Clear separation by PHP namespace
- Reduced File Count: Easier to manage and deploy
- Better IntelliSense: Organized autocomplete experience
To migrate, simply run the generator again. The old files can be safely deleted.
- PHP 8.1+
- Laravel 10.0+ or 11.0+
Contributions are welcome! Please feel free to submit a Pull Request.
The MIT License (MIT). Please see License File for information.