NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Packagist · #824 most downloaded on Packagist
This is my package typescript-transformer
Last release 1 months ago
28 Aug 2026
Ships fairly regularly
a new release about every 3 months
Nearly every release is documented
notes for 35 of 37 stable releases
Nothing withdrawn
no release was ever pulled
6 years old
38 releases · first in 2020
One column per quarter.
Resolve inherited constructor docblocks against the declaring class
A child class that does not declare its own constructor came out with unknown for every promoted property typed through a docblock (#160). The parent transformed correctly, the child did not.
abstract class DashboardViewModel extends Data
{
/** @param array<SpanAggregationType> $aggregation_types */
public function __construct(
public array $aggregation_types,
) {}
}
class JsDashboardViewModel extends DashboardViewModel {}// JsDashboardViewModel
aggregation_types: unknown[]; // before
aggregation_types: SpanAggregationType[]; // afterReflection returns the inherited parent constructor, so ClassTransformer::resolvePropertyAnnotation() found the @param annotation but paired it with the child as the resolution context. Short class names were then looked up in the child file's use statements, which do not import them, and the type fell back to unknown. The annotation is now paired with the constructor's declaring class instead.
Classes that declare their own constructor are unaffected, since there the declaring class is that same class. Thanks @rubenvanassche.
Full Changelog: 3.3.0...3.3.1
A new option to skip manifest generation, a watch mode fix, and expanded route helper docs.
A new option to skip manifest generation, a watch mode fix, and expanded route helper docs.
withoutManifest() option to skip manifest file generation (#153)By default the transformer writes a typescript-transformer-manifest.json file to power its caching, only rewriting output files whose contents actually changed. That manifest is unwelcome in some setups: when the output directory is a committed git submodule it surfaces as an unexpected tracked file, and when you want a clean diff or run in CI the caching simply is not needed.
TypeScriptTransformerConfigFactory now exposes a withoutManifest() method that turns off manifest generation entirely. When disabled, WriteFilesAction skips the manifest read and write and writes every file directly.
$config
->outputDirectory(resource_path('frontend/types'))
->writer(new GlobalNamespaceWriter('generated.d.ts'))
->withoutManifest();Thanks @pawell67.
In watch mode attributes are reflected through Roave BetterReflection. PhpAttributeNode::newInstance() constructed each attribute with no arguments before invoking the result, which threw an ArgumentCountError for any attribute with required constructor arguments such as #[LiteralTypeScriptType('string[]')].
The arguments are now spread straight into the constructor, letting PHP bind positional, named, default, and variadic values itself. A regression test covers a constructor-argument attribute reflected through BetterReflection, the path that was previously untested.
Thanks @rubenvanassche.
route() throw behavior and hasRoute predicate by @rubenvanassche in #152Full Changelog: 3.2.0...3.3.0
A round of bug fixes and a couple of small extensibility improvements, plus broader generic and inherited type support.
A round of bug fixes and a couple of small extensibility improvements, plus broader generic and inherited type support.
A child class inheriting a @var annotation from a parent in another namespace would lose the type information. With a parent like:
// namespace App\Models
class ParentModel
{
/** @var string[]|SimpleGenericClass<int, string> */
public array $items;
}A Child extends ParentModel in App\Models\Children (no use of SimpleGenericClass) used to transform $items as unknown. The transformer now resolves the annotation against the declaring class's namespace, so inherited @var types keep working across namespaces. Class level @property and constructor @param annotations still resolve against the current class, since they belong to that class. Thanks @ragulka.
AttributedClassTransformer extensible (#142)AttributedClassTransformer had TypeScript::class hardcoded in two places, so swapping in a custom attribute meant duplicating the entire transformer. There is now a single attributeClass() hook:
class FrontEndAttributedClassTransformer extends AttributedClassTransformer
{
protected function attributeClass(): string
{
return FrontEnd::class;
}
}Thanks @CheshireC4t.
On macOS via Laravel Herd, PhpExecutableFinder::find() returns /Users/<me>/Library/Application Support/Herd/bin/php84. The space broke Process::fromShellCommandline("$phpBinary $command"):
sh: /Users/<me>/Library/Application: No such file or directory
The watcher then looped on Worker failed to start. Waiting for application to be fixed.... Wrapping the binary in escapeshellarg() fixes it without changing the workerCommand contract. Thanks @mdpoulter.
@template-covariant and @template-contravariant (#144)getTemplateTagValues() defaults to the @template name, so generic classes annotated with the variance variants were silently losing their type parameter:
/** @template-covariant T */
class Paginated { /* T was dropped */ }Both variant tag names are now collected alongside @template. Thanks @jakewtaylor.
When FixArrayLikeStructuresClassPropertyProcessor rewrote a Collection<int, string> next to an existing string[] in a union, the output ended up as string[] | string[]. The constructor time dedup on TypeScriptUnion runs once and cannot catch duplicates introduced by later mutations. A new TypeScriptDeduplicableNode interface (implemented by TypeScriptUnion, TypeScriptIntersection, and TypeScriptArray) is now invoked by the visitor after children are visited, so any Replace or Remove that introduces duplicates is cleaned up automatically:
interface TypeScriptDeduplicableNode
{
public function deduplicateNodes(): void;
}Fixes #137. Thanks @rubenvanassche.
A new OutputsTypeScriptLiteral trait centralizes how scalar values are written as TypeScript literals (string, int, float, bool, null). Strings are now escaped with a hand-rolled map (\, ', \n, \r, \t) and wrapped in single quotes, fixing invalid output for values like App\Models\User or it's. The trait replaces inline interpolation in TypeScriptLiteral, TypeScriptEnum, TypeScriptIdentifier, TypeScriptParameter, and TypeScriptImport, removing four duplicated quoting sites that all had the same hazard. Side effects: TypeScriptLiteral now emits single quoted strings (previously double quoted via json_encode), so the slash escaping problem from json_encode (e.g. image\/png) is gone, and float / null are now accepted by the constructor. Supersedes #138 by @pataar and #148 by @bram-pkg, both of which surfaced facets of the same bug. Thanks @rubenvanassche.
Full Changelog: 3.1.1...3.2.0
Throw exception when output directory does not exist instead of silently resolving to an empty path
Previously, when realpath() failed on a non-existent output directory, it would silently return false, which could cause file generation to target the filesystem root (/). The transformer now validates the output directory exists and throws a clear exception if it doesn't.
Full Changelog: 3.1.0...3.1.1
Support class-level @template generics in TypeScript output
@template generics in TypeScript output (#133)Classes with @template docblocks now produce generic type aliases:
/**
* @template T
*/
class PaginatedResponse
{
/** @param array<T> $data */
public function __construct(
public int $page = 1,
public array $data = [],
) {}
}Now correctly generates:
type PaginatedResponse<T> = {
page: number;
data: T[];
};Instead of the previous incorrect output where T was resolved as unknown.
Version 3 is a ground-up rewrite. It introduces a TypeScript AST, a visitor pattern, watch mode, a new extension system, and much more.
Version 3 is a ground-up rewrite. It introduces a TypeScript AST, a visitor pattern, watch mode, a new extension system, and much more.
The package now builds a proper TypeScript Abstract Syntax Tree before writing output. Instead of generating strings directly, transformers create node objects that can be traversed and manipulated before being written to disk:
new TypeScriptAlias('User', new TypeScriptObject([
new TypeScriptProperty('name', new TypeScriptString()),
new TypeScriptProperty('age', new TypeScriptNumber()),
]));
// Output: type User = { name: string; age: number }There are a lot of node types available and you can easily add your own!
A Visitor allows users to traverse the AST, allowing them to replace or completely remove nodes:
Visitor::create()
->after(function (TypeScriptUnion $node) {
if (count($node->types) === 1) {
return VisitorOperation::replace(array_values($node->types)[0]);
}
})
->execute($rootNode);A file system watcher monitors your PHP files and automatically re-transforms on changes. Your TypeScript definitions stay in sync as you develop - no manual re-running required.
This feature is in beta at the moment.
TypeScriptReference nodes connect generated types to the PHP classes they represent. The system automatically resolves references to the correct import paths based on your writer configuration.
A new provider interface lets you inject custom transformed types from any source - not just PHP classes:
class AddLaravelCollectionProvider implements TransformedProvider
{
public function provide(): array
{
return [new Transformed(
typeScriptNode: new TypeScriptAlias(
new TypeScriptGeneric(new TypeScriptIdentifier('Collection'), [new TypeScriptIdentifier('T')]),
new TypeScriptGeneric(new TypeScriptIdentifier('Array'), [new TypeScriptIdentifier('T')]),
),
reference: new ClassStringReference(Collection::class),
location: ['Illuminate', 'Support'],
)];
}
}
// Output: type Collection<T> = Array<T>Collectors have been removed. Transformers now decide both whether they can handle a type and how to transform it:
class MyTransformer extends ClassTransformer
{
protected function shouldTransform(PhpClassNode $phpClassNode): bool
{
return $phpClassNode->implementsInterface(Data::class);
}
}The EnumTransformer now supports union output, native TypeScript enums, and a pluggable EnumProvider interface for custom enum detection.
PHPDocumentor has been replaced by PHPStan's type parser. This provides more robust handling of generics, array shapes, key-of, value-of, and complex union/intersection types.
ModuleWriter generates TypeScript modules in a directory structure mirroring your PHP namespaces. GlobalNamespaceWriter outputs a single .d.ts declaration file with namespaced types in global scope.
Transformers now work with PhpClassNode, PhpPropertyNode, PhpMethodNode instead of raw PHP Reflection objects, providing a unified interface allowing updates to the files to be handled in the same process.
DtoTransformer removed - use ClassTransformer with custom property processorsTypeProcessors replaced by ClassPropertyProcessorRecordTypeScriptType and TypeScriptTransformer attributes removedSince this is a complete rewrite, there isn't an upgrade guide available. We recommend you to first read full documentation and then upgrade your projects accordingly.
The first beta release of TypeScript Transformer v3, a complete rewrite from scratch!
The first beta release of TypeScript Transformer v3, a complete rewrite from scratch!
I don't expect that many things will be changing between beta and release but be cautious.
Fix: EnumTransformer properly handling single-quotes in backed enum string values by @sugarmaplemedia in #100
Full Changelog: 2.4.0...2.5.0
Full Changelog: https://github.com/spatie/typescript-transformer/compare/2.4.0...2.5.0
Don't generate if an enum has no cases yet by @jameshulse in #87
nullToOptional config by @innocenzi in #88Full Changelog: 2.3.1...2.4.0
nullToOptional config by @innocenzi in https://github.com/spatie/typescript-transformer/pull/88Full Changelog: https://github.com/spatie/typescript-transformer/compare/2.3.1...2.4.0
feat(enum-collector): improve extensibility of EnumTransformer by @innocenzi in #78
EnumTransformer by @innocenzi in #78Full Changelog: 2.3.0...2.3.1
EnumTransformer by @innocenzi in https://github.com/spatie/typescript-transformer/pull/78Full Changelog: https://github.com/spatie/typescript-transformer/compare/2.3.0...2.3.1
Fix annotations doc by @cosmastech in #73
DtoTransformer@transformPropertyName() by @cosmastech in #74Full Changelog: 2.2.2...2.3.0
DtoTransformer@transformPropertyName() by @cosmastech in https://github.com/spatie/typescript-transformer/pull/74Full Changelog: https://github.com/spatie/typescript-transformer/compare/2.2.2...2.3.0
Allow Symfony 7 by @jmsche in https://github.com/spatie/typescript-transformer/pull/67
Full Changelog: https://github.com/spatie/typescript-transformer/compare/2.2.1...2.2.2
- Add support for pseudo types
Add support for hidden properties
- add support for record types
Ensure transformed types are unique
add support for optional attributes
fix: Support Collection with array-key key type
Allow non fully qualified names within annotations
allow transformation of interfaces
- add eslint formatter(#28) - let prettier formatter use npx
npx (#29)Allow whitespace in type definitions
fix the transformation of PHP native enums
Nothing published for this version
allow interfaces in default type replacements
add support for transforming to native TypeScript enums
- fix deprecations
- add support for PHP 8.1
Add declare keyword by default to generated output
declare keyword by default to generated output (#13)Fix ProcessTypes to work with Collection types
ProcessTypes to work with Collection typesFix default collector with missing symbols in attributes
Allow spatie/temporary-directory v2 on dev
Added TypeReflectors to reflect method return types, method parameters & class properties within your transformers
- Add support for Writers
Writers (#7)- Add PHP8 support
Fix some capitalization in namespace names
SpatieEnumTransformer from the laravel-typescript-transformer package- initial release
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →