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

Typescript Generator Bundle Laravel Package

janit/typescript-generator-bundle

A Symfony bundle that generates TypeScript definitions from PHP classes, helping keep frontend and backend types in sync. It automates type generation so TypeScript clients stay aligned with your application models.

View on GitHub
Deep Wiki
Context7

Getting Started

The first step is a framework reality check. janit/typescript-generator-bundle is a Symfony Bundle, not a Laravel package. There is no extra.laravel.providers, no auto-discovery, and no vendor:publish. composer require into a Laravel app gives you a class that expects a Symfony kernel and DI container; it will not produce an Artisan command on its own. So "getting started" for a Laravel developer means one of two entry points:

  1. Standalone Symfony harness (fastest path to seeing output). Spin up a throwaway Symfony skeleton in /tmp, composer require --dev janit/typescript-generator-bundle, register the bundle, point it at a directory of representative Laravel DTOs/FormRequests/Resources, and run the command. This answers the only question that matters before you invest: is the emitted TypeScript actually correct for my models? No Laravel runtime is touched.
  2. Extract the generator core. Bundles of this shape almost always wrap a standalone generator library. Check the composer.json of the bundle for a janit/php-typescript-generator-style dependency before you write any glue — if the core is framework-agnostic, you depend on it directly and write ~100 lines of Artisan plumbing. If it isn't, you lift the extractor service out of the container and adapt it. This is a 1–2 day job and changes the effort by an order of magnitude.

Where to look first, in order:

  • The console command class — this is the public surface. Everything the bundle does day-to-day flows through it. Read the command's configure() to learn the options (namespace targeting, output directory) and the execute() to see how services are wired.
  • The extractor/generator service — this is the reusable core. Confirm whether it has Symfony-typed constructor dependencies (logger, event dispatcher, config objects) or is plain PHP. Plain PHP means trivial extraction; container-coupled means adaptation.
  • Resources/config/services.yaml — this tells you exactly how the bundle is wired and what you must reproduce in a Laravel service provider.
  • The Bundle class itself — confirms registration semantics and whether the command is registered via tag discovery.

First real use case: pick one bounded module with well-formed DTOs (typed properties, explicit types, no lazy or magic) — a RegisterUserRequest, a UserResource/UserDTO, and one nested object. Generate, diff against your hand-written interfaces, and inspect. If it round-trips cleanly on three classes, the day-to-day workflow is real; if it emits any for your collections and mangles nullability, you've learned this cheaply and pivot to OpenAPI/schema-based generation.

The day-to-day workflow you are actually building toward:

# What you want to end up with (after extraction/glue):
php artisan types:generate --namespace=App\\Data --out=resources/js/types/generated.d.ts

# Committed to the frontend, regenerated on every payload change, drift-checked in CI.

Everything from here assumes you own the generator (forked or extracted), because the bundle itself is not a drop-in Laravel dependency.

Implementation Patterns

Pattern 1: Thin glue + vendored core. Keep your Laravel-facing code to an Artisan command and a mapping table only. Depend on the framework-free generator library unmodified so you can pull upstream fixes if they ever land. If the core is container-coupled, vendor the bundle's classes into app/Support/TypeGen/ and change nothing except constructor signatures — minimize the diff you own.

// app/Console/Commands/GenerateTypes.php — the entire Laravel surface
class GenerateTypes extends Command
{
    protected $signature = 'types:generate
        {--namespace=App\\Data : Root namespace of DTOs}
        {--out=resources/js/types/generated.d.ts : Output file}';

    public function handle(TypeScriptGenerator $generator): int
    {
        $output = $generator->generate(
            namespaces: [$this->option('namespace')],
            exclude: ['App\\Data\\Internal'], // see Gotchas: scope creep
        );

        File::put(base_path($this->option('out')), $output);
        $this->info('Generated ' . substr_count($output, 'export interface') . ' interfaces.');

        return self::SUCCESS;
    }
}

Pattern 2: Namespace allow-list, never directory-walk by convention. The bundle assumes a conventional Symfony layout. Your Laravel DTOs may live in app/Data, app/Http/Requests, app/Http/Resources, and app/ViewModels. Explicitly enumerate the namespaces you want reflected so Eloquent models, framework internals, and one-off value objects don't leak into the output.

Pattern 3: Golden-file test — the single highest-value piece of engineering. A fixture directory of PHP classes with a committed expected .ts output, asserted in CI. This is what converts an unmaintained dependency into pinned, verifiable behavior. It catches changes to the mapping (a PHP minor bump alters enum output, say), not incorrectness — you still pair it with runtime validation.

public function test_generated_output_matches_golden_file(): void
{
    $actual = $this->generator->generate(namespaces: ['Tests\\Fixtures\\Dtos']);
    $expected = File::get(__DIR__ . '/fixtures/expected.d.ts');

    $this->assertSame($expected, $actual); // diffable, deterministic output
}

Pattern 4: CI drift gate, not convention. Run generation on PRs touching DTO namespaces and fail the build if the committed file changes. A developer editing a DTO and forgetting to regenerate is the #1 operational failure mode; only automation prevents it.

# .github/workflows/types.yml
- run: php artisan types:generate
- run: git diff --exit-code resources/js/types/generated.d.ts

Pattern 5: Centralize the semantic mapping. PHP→TS is lossy in ways that are project-specific: int/float→number, arrays→T[] vs Array<T> vs any, nullable→| null vs ?, mixed→unknown vs any. Whatever mapping the generator produces, if it's wrong for your project, patch it in one place and document the decisions there. Scattered per-DTO overrides defeat the purpose of generation.

Pattern 6: Commit generated output to the frontend repo. Treat the .d.ts as a reviewed artifact alongside the DTO change that produced it. This is what makes the type diff visible in code review — the whole point of the tool. Publishing as a build artifact is an alternative, but it severs the "review the type change with the API change" workflow.

Pattern 7: Fallback path. Keep the option to swap to openapi-typescript-style schema-based generation open. If the project goes stale or semantic coverage fails on a construct you can't patch cheaply, you want the migration to be a swapping of the generator command, not a rewrite of the pipeline. The Artisan command and CI gate are the durable assets; the generator is the replaceable piece.

Gotchas and Tips

This is not a Laravel package — the largest gotcha. There is no bundle registration mechanism in Laravel, no services.yaml compilation in the Symfony sense, and no tag-based command discovery. Do not attempt to register the Symfony bundle in a Laravel kernel. The only sane paths are the standalone harness or extraction. Budget a day to confirm which before committing.

Silent type drift is the dangerous failure mode. A PHP change (?string → string, or a new readonly property) that the generator mishandles emits TypeScript that compiles fine but is wrong at runtime. The golden-file test catches changes; it does not catch incorrect mappings. If correctness matters, pair generation with runtime validation (Zod or equivalent) at the API boundary.

The PHP→TS semantic gap is real and lossy. Reflection sees typed properties; it does not see docblock generics, @var annotations, or promoted-constructor intent unless the tool parses annotations. mixed becomes any, collections often degrade to any[], and union/intersection types are frequently unsupported by unmaintained reflectors. Inspect the output for any — every any is a place the tool gave up.

Check PHP version support before you extract. The reflection surface (enums, readonly, promoted params, union/intersection types) moves with every PHP minor. If the upstream reflector predates PHP 8.1 enums or 8.2 readonly classes, patching it is likely cheaper than replacing it — but budget for it explicitly. Confirm the generator runs on your exact PHP minor (8.1–8.3 for typical Laravel 10/11) before writing the Artisan glue.

Nullable handling must satisfy noImplicitAny / strict. Emitted output should target the frontend's tsconfig strict settings. Nullable PHP types that map to implicit any will fail noImplicitAny and force the frontend to patch generated types by hand — which defeats the tool. Verify strict-mode compile on the first generated file.

Scope creep is a config bug waiting to happen. The tool reflects whatever it's pointed at. Point it at Eloquent models with dozens of relationships and it will emit noise and leak persistence concerns into frontend types. Allow-list namespaces; never rely on src/ convention.

Output must be deterministic and diffable. Stable ordering and formatting are prerequisites for committing the output and drift-checking in CI. Verify by generating twice and diffing — non-deterministic ordering makes the CI gate useless (every run fails).

Bus factor of one; pin and vendor. 16 stars, 0 dependents, no SLA, no vendor to escalate to. MIT means forking is legally clean — do it. Keep the fork thin (glue + mapping only), vendor the core unmodified, and name an owner. An unowned fork becomes abandoned code in two quarters.

Document the contract explicitly. The generated file should carry a header: // GENERATED — do not edit. Regenerate with: php artisan types:generate. And the team must agree that generated types are authoritative and hand-edited interfaces outside the generated file are not permitted — without that agreement, teams maintain parallel hand-written types and the tool becomes pure overhead.

Debugging tip: start with one class. When the output is wrong, point the generator at a single DTO, not the whole namespace. The reflection and mapping logic is much easier to reason about in isolation than against a 200-interface diff.

Extension points. The bundle's likely extension points are the type-mapping table and the namespace-filter config. These are where you customize; avoid patching the reflection loop unless you must, because that is the part most likely to receive upstream fixes (and the part you want to keep mergeable with the vendored core).

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource