spatie/typescript-transformer
Convert PHP classes, enums, and DTOs into TypeScript types with spatie/typescript-transformer. It supports complex types, generics, and generated TypeScript functions, making frontend/backend type sharing simple.
Architecture fit
This is a build-time code generation tool, not a runtime library. It occupies the seam between a Laravel/PHP backend and a TypeScript frontend, converting PHP classes, enums, and docblock types into .ts type definitions. That positions it as a contract-enforcement layer: the PHP DTO becomes the single source of truth and the TypeScript type is a derived artifact. Architecturally this is a good fit for teams already treating their API payloads as first-class objects (Form Requests, API Resources, Spatie Data objects), and a poor fit for teams with ad-hoc array payloads, since the tool can only transform what is explicitly modelled.
v3 is a ground-up rewrite with meaningfully better architecture than v2: a real TypeScript AST instead of string concatenation, a visitor pattern for post-processing nodes, TypeScriptReference nodes that resolve imports across files, a TransformedProvider interface for injecting non-PHP-derived types (e.g. framework generics), and a dual writer system (ModuleWriter for one-file-per-type mirroring PHP namespaces, GlobalNamespaceWriter for a single .d.ts). The AST + visitor design is the important one from a TPM perspective: it means generated output can be normalized and linted deterministically, which is what makes a "generated code is committed and CI-enforced" workflow viable.
Integration feasibility
Low technical lift to get a first output. composer require spatie/typescript-transformer, publish config, annotate classes, run the command. Feasibility is high for a bounded pilot; the complexity is entirely in the governance around the generated artifacts, not the install.
Things that materially affect feasibility:
key-of/value-of, unions/intersections) but it means the quality of output is directly proportional to the quality of existing docblocks. Sparse annotations produce unknown, not errors.npx. In a PHP-only CI container this either needs Node installed or the formatter step disabled. Not a blocker, but it's a cross-runtime dependency that surprises people.laravel-typescript-transformer, referenced as far back as 1.1.0). Confirm the current version pairing before assuming Eloquent/Collection transformations work out of the box.Technical risk
DtoTransformer removed, TypeProcessors replaced by ClassPropertyProcessor, TypeReflectors and inline types removed, two attributes removed. Budget this as a project, not a maintenance task.unknown rather than throwing. TypeScript compiles happily against unknown at the consumption site only if consumers narrow it, and in practice consumers cast or ignore it. Every release in the 3.1–3.2 line is a bug fix for exactly this class of failure (inherited @var across namespaces, @template generics resolving to unknown, enum quoting, empty enums being skipped). The failure mode is a quietly weaker contract, not a red build.Key questions
.d.ts? This decision is expensive to reverse because it changes every import path.unknown a pass or a fail? (Recommend: fail.)Stack fit Natural fit for Laravel + Inertia/Vue/React, Laravel + a separate Next.js/Nuxt SPA, or any PHP backend with a TypeScript consumer. The strongest fit is where payload shape is already modelled as classes (Spatie Data, API Resources, Form Request DTOs) rather than arrays — the tool converts structure that exists, it does not create it. If the backend is annotation-light, front-load a docblock hardening pass; the transformer's output quality is bounded by PHPStan's ability to resolve what's written.
Migration path
.ts types): run in parallel for one cycle. Generate into a scratch directory, diff against hand-written types, and treat every discrepancy as a resolved question about the contract.Compatibility
Verify the full matrix before committing: PHP 8.2+, nikic/php-parser 5, Symfony 7, and the paired laravel-typescript-transformer version. On the consumer side, the output is plain TypeScript — no runtime dependency, no version coupling with the TS toolchain, which is a significant advantage over schema-first alternatives. The only consumer-side coupling is the import path layout implied by the chosen writer.
Sequencing
.d.ts only for legacy consumers. Lock the output directory — 3.1.1 added a hard exception when it doesn't exist, so wire directory creation into the pipeline explicitly.unknown and for missing types. This audit is the real cost estimate.TransformedProvider rather than post-processing generated text.Maintenance MIT-licensed, Spatie-maintained, active changelog through 3.2.0, with PHPStan and a Pest test suite in CI — good hygiene signals. The maintenance burden shifts from hand-writing TypeScript types to maintaining transformer configuration, custom transformers, and a CI gate. That is a net reduction in ongoing work for a stable DTO surface, and a net increase for a fast-churning one. Budget one part-time owner, not a team.
Support No commercial SLA. Support is GitHub issues plus hosted Spatie docs. For a package on the critical path of a frontend contract, that means the internal team owns first-line support. This is acceptable for a build-time tool — a broken release degrades codegen, not production traffic — which is the single best argument for the risk profile.
Scaling
Transformation cost scales roughly linearly with the number of transformed classes; scoping to a namespace keeps CI time bounded. ModuleWriter produces one file per type mirroring PHP namespaces, so file count grows with the domain — fine for bundlers, potentially noisy in review. Large monorepos should scope per-application rather than transforming the whole codebase, both for runtime and to keep the generated diff reviewable. No runtime scaling concerns at all, which is the point.
Failure modes
unknown leakage (highest impact). Type resolution fails silently, TypeScript compiles, and the frontend has a weaker contract than it believes. Mitigation: assert against unknown in generated output as a CI check.@var and class-level @template both had unknown bugs fixed in 3.1.0/3.2.0. Assume more exist; pin versions and re-audit output on every bump.npx-based Prettier/ESLint failing in PHP-only containers breaks the pipeline. Mitigation: pin tooling, vendor it, or make formatting a separate gated step.Ramp-up
Backend developers need very little: install, annotate, run. The real ramp is for whoever writes custom transformers and owns the CI gate — the v3 extension model (AST nodes, visitors, shouldTransform, TransformedProvider, writer configuration) is well-designed but genuinely a new mental model, and there is no upgrade guide to learn from, only the docs. Estimate one engineer for roughly one to two weeks to reach a trustworthy, CI-enforced pipeline for a scoped DTO set, with the majority of that time spent auditing output quality rather than configuring the package. Frontend developers consume plain TypeScript with no new tooling, which keeps the blast radius of the adoption decision contained to one owner on the backend.
How can I help you explore Laravel packages today?