stubbedev/laravel-stoli
A Laravel package by stubbedev that integrates Stoli into your application, providing a service provider, configuration options, and a straightforward Composer-based setup so you can add Stoli functionality to Laravel projects with minimal effort. It follows standard Laravel conventions.
Stoli turns each Laravel route file into a single typed <Module>Routes definition, and generates the matching TypeScript from it. Everything — createRoute(), RouteService, the routers, the URL helpers — reads that one definition, so your PHP route contract and your TS types cannot drift apart.
composer require --dev the package alongside spatie/laravel-data:^4 (mandatory as of 1.0). Publish the config and note the new stoli.ts output file.typescript:transform, add Stoli to it — it now runs from the very types the transformer writes, and --watch is supported. If you'd rather keep them decoupled, set 'transform' => false and call php artisan stoli:generate separately.stoli.ts first, not the docs. It shows you the exact shape of the input types, URL functions and routers you'll be importing, which is faster than reverse-engineering the config.POST /users), export its <Module>Routes definition, regenerate, then type your form handler with the emitted StoreUserDataInput and the typed validation error shape. That single route demonstrates the whole value proposition — input type, status code (201 inferred for POST), and error keys derived from the route's fields.One definition file per route module. The intended workflow is a routes/users.ts-style PHP file (or a UsersRoutes class/array export) that declares parameters, response, status and methods together. Treat it as the contract: if a route changes, the definition changes first, then stoli:generate --check tells you what else went stale.
CI gate. stoli:generate --check fails the build on stale generated files. Put it next to your Pint/TS lint step so generated types are reviewed in the PR rather than committed silently by a --watch session.
Typed inputs come from two sources, and they're deliberately different:
laravel-data takes them in: property names, nested and recursive Data classes, and validation attributes (In, Enum, Uuid, Required, Confirmed, …) surface as TS types.rules(), subclasses included, so a shared BaseRequest with common rules types correctly on every child.Typed validation errors. Bind your error handling to the generated error type keyed by the route's fields — no more Record<string, string[]> guessing. This plays well with Inertia-style errors props or a fetch/axios wrapper that returns { errors: ... }.
Pick a client once. 'client' => 'fetch' or 'client' => 'axios' gives you the same router API either way, so you can migrate HTTP layers without touching call sites. File uploads and method spoofing are handled by the router, so you don't hand-roll FormData for PUT/PATCH anymore.
Tree-shakeable URLs. Enable 'urls' => true when you want per-route URL helper functions. They're individually importable, so unused ones drop out of the bundle — good for large route surfaces where you'd otherwise ship a giant helper object.
Narrow parameters are free. where constraints, backed enums, int arguments and route-model binding all narrow the generated parameter types. Lean on whereIn/enum binding in PHP and you get literal unions or enum types in TS without extra annotations.
stoli.js and stoli.d.ts become a single stoli.ts and the old files are removed on the next run — expect a dirty working tree and stale imports after the first generate. Update imports in one pass.StoreUserDataInput), not its Data type. This is the subtle breakage: code that typed route params as StoreUserData still compiles in some places but is semantically wrong for inputs. Grep for your Data class names in TS.createURLWithoutQuery() is gone — use resolve(). Behavior differs too: resolve() returns the leftover parameters instead of deleting them, so call sites that relied on mutation need rewriting.'axios' => true still works but is legacy. Prefer 'client' => 'axios'; don't mix both in one config or you'll get confusing precedence.unknown rather than Record<string, unknown>. That's safer, but it will surface as type errors anywhere you previously indexed into the response without narrowing. Fix with a proper response type or a type guard rather than casting to any.'transform' => false is the escape hatch when typescript:transform and Stoli fight over output or you want generated files in a different commit cadence. It's also the simplest way to debug — run stoli:generate alone and diff.response or a status override is almost always the real cause. Regenerating won't help until the definition is fixed.How can I help you explore Laravel packages today?