Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Typescript Transformer Laravel Package

spatie/laravel-typescript-transformer

Convert PHP classes, enums, and complex types into TypeScript definitions automatically. This Laravel package handles generics, nullable types, and even TypeScript functions, keeping your frontend and backend types in sync.

View on GitHub
Deep Wiki
Context7

Getting Started

Check the version first. This package had a full rewrite at v3.0: PHP 8.2+, Laravel 10+, and configuration moved out of config/typescript-transformer.php into an application-level service provider. Almost every tutorial, blog post, and Stack Overflow answer you'll find online describes the v2 API (config/typescript-transformer.php, Transformers\DtoTransformer, writers array). If you're starting fresh, install ^3.0 and read the v3 docs at spatie.be/docs/typescript-transformer/v3 — if you're maintaining an older app, stay on 2.6.x and don't mix instructions between the two.

Minimal path:

composer require spatie/laravel-typescript-transformer
php artisan typescript:install

The install command scaffolds an application service provider (in v3 this is where all configuration lives) and registers it — in Laravel 11+ that's bootstrap/providers.php, in older apps config/app.php. Open that generated provider first; it is the single source of truth for output paths, transformers, writers, and route/controller generation. Then run:

php artisan typescript:transform

First use case — typed API payloads for the frontend. Take the DTO or model shape you're already passing to Inertia or a JSON endpoint and mark it:

use Spatie\TypeScriptTransformer\Attributes\TypeScript;

#[TypeScript]
class UserData
{
    public int $id;
    public string $name;
    public ?string $address;
}

Run the transform command and you get export type UserData = { id: number; name: string; address: string | null; }; in your configured output file. Import it in a Vue/React/Svelte component the same day and you get compile-time errors when the backend shape drifts. That single loop — annotate, regenerate, let tsc catch the break — is the whole value proposition, so prove it on one class before wiring up anything else.

Enums are the second-easiest win and work without ceremony since collection of enums was enabled by default in 2.3:

enum Languages: string { case TYPESCRIPT = 'typescript'; case PHP = 'php'; }
// => export type Languages = 'typescript' | 'php';

Implementation Patterns

Discovery: attribute vs. directory scan. In v3 you can either mark classes with #[TypeScript] or point the transformer at directories to scan. Prefer explicit attributes for anything hand-authored (DTOs, value objects, enums that cross the boundary) — scanning directories tends to sweep in classes you didn't intend to expose, and silently produces types nobody uses. Reserve path scanning for broad model coverage where annotating hundreds of Eloquent models is impractical.

Keep the generated file out of hand-editing. Treat the output (typically resources/js/types/generated.d.ts) as a build artifact. Two viable git strategies: commit it and add a CI check that regenerates and fails on git diff --exit-code (this is the pattern that actually catches backend/frontend drift in review), or generate it in the frontend build step and gitignore it. Committing without the CI check is the worst of both worlds — it rots quietly.

Wire it into the dev loop, not the request loop. Transformation is a compile-time concern. Run typescript:transform and its watch mode alongside npm run dev / vite in the local workflow, and as a discrete step in CI/build. Never call it from a controller or a service provider boot method.

Laravel Data integration. If you're on spatie/laravel-data, this is the highest-leverage combination — data classes are already the canonical representation of your payloads, so the transformer generates the full graph for you. Note that since 3.1.0 the processor honours config/data.php's name_mapping_strategy.output plus class- and property-level MapName / MapOutputName attributes, so if your API emits snake_case, the generated types now match. On 3.0.3 and earlier they silently didn't.

Controller and route type generation (v3). The big v3 addition is generating types for controller actions plus a typed route() helper with autocomplete on route names and parameters. The day-to-day payoff is Inertia:

router.visit(SomeController.update(), { data: { ... } });

method is a literal union ('get' | 'post' | ...) since 3.2.0, so it drops straight into Inertia's router.visit without casts. Use httpMethodsPriority on LaravelControllerTransformedProvider to control both which verbs are emitted and their order; the default drops HEAD and OPTIONS, which Laravel auto-registers for every GET and which SPAs never call.

Route filters. NamedRouteFilter, ControllerRouteFilter, and ClosureRouteFilter let you scope route generation to the routes the frontend actually consumes. For any non-trivial app, use one — emitting every route including admin and health-check endpoints produces a route helper file that's noisy and slow to typecheck.

Formatting. Prettier (or another formatter) integration exists so generated output matches your ESLint/Prettier config and doesn't produce diffs that fight your editor's format-on-save. Worth configuring on day one; retrofitting it later means a giant one-off reformat commit mixed into a real change.

Custom transformers for value objects. If you have a Money, UserId, or Email value object that should collapse to number/string rather than expanding to its internals, write a transformer and register it in the service provider. Order matters — transformers are consulted in sequence, so register specific ones before broad fallbacks.

Gotchas and Tips

  • Pin a recent 3.x. Several early-3.x bugs bite hard in normal use: typescript:install silently failing to register the provider when bootstrap/providers.php used imported short class names (fixed in 3.3.0, with older-app detection also fixed there), the route watcher crashing with a null-byte error whenever any route filter was configured (3.1.0), and GlobalNamespaceWriter mangling absolute paths (3.1.0). Use ^3.3 unless you have a reason not to.
  • Writers take relative filenames. If you pass an absolute path to GlobalNamespaceWriter, you get the output directory prepended to it — resources/js/generated/home/user/project/.... Always give it a path relative to the configured output directory.
  • Root route double-slash. Prior to 3.1.0, Route::get('/') produced route('home') === '//'. Worth a one-line unit test on 3.0.x.
  • nullToOptional is a semantic choice, not a formatting one. Switching from string | null to address?: string changes what the frontend can express — null and "absent" are genuinely different for PATCH semantics. Decide per project; don't flip it casually mid-stream.
  • Enums are collected by default since 2.3.0. If you upgraded from 2.2 or earlier, you may suddenly get types for enums you never asked for. That's expected, not a misconfiguration.
  • Generics and collections are the fragile edge. Generic arity mismatches (e.g. the paginator interface aliases, fixed in 3.0.1) and union types combined with the collection transformer (fixed back in 2.1.0) are the areas where the transformer is most likely to emit something tsc rejects. If output fails to compile, isolate the offending class and check whether it's a generic, a collection, or a union — those three are where to look first.
  • Debugging output: when a class doesn't appear, check (a) it has #[TypeScript] or its directory is registered, (b) it isn't in an excluded vendor path, (c) another transformer isn't matching it first. Dumping the registered transformer list from the service provider is faster than reading the generated file.
  • Non-determinism in diffs. Generated property ordering, route ordering, and file ordering can drift between runs. If your CI does a regenerate-and-diff check, a non-deterministic emitter gives you flaky builds — sort your route filters and transformer registrations deliberately.
  • Watch mode is a dev convenience, not a correctness guarantee. It's a filesystem watcher; it will miss changes on some setups (network mounts, container volume mounts, WSL). The authoritative check is always a clean typescript:transform in CI.
  • Extension points to know: custom transformers, custom writers (multiple writers are supported for splitting types across files/namespaces), formatters, and the TypeReflector API for reflecting method return types, parameters, and properties from your own transformers. Reach for TypeReflector before regexing PHPDoc.
  • Don't expect runtime safety. This generates types, not validation. A generated UserData type will happily describe a response your controller never actually returns. Pair it with Laravel Data / form requests if contract correctness matters; the transformer only guarantees the two sides are described the same way, not that the runtime matches the description.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin