laravel/wayfinder
Generates fully typed, importable TypeScript functions from your Laravel routes and controllers. Call backend endpoints like normal TS functions—no hardcoded URLs or manual syncing. Includes an Artisan generator and Vite plugin for auto-regeneration.
Installation:
composer require laravel/wayfinder
npm i -D @laravel/vite-plugin-wayfinder
Update vite.config.js:
import { wayfinder } from "@laravel/vite-plugin-wayfinder";
export default defineConfig({
plugins: [wayfinder()],
});
Generate TypeScript Definitions:
php artisan wayfinder:generate
This creates resources/js/wayfinder/, actions/, and routes/ directories (auto-gitignored).
First Use Case: Import a controller action in your frontend:
import { show } from "@/actions/App/Http/Controllers/PostController";
show(1); // Returns { url: "/posts/1", method: "get" }
Controller-Centric Development:
import { store } from "@/actions/App/Http/Controllers/PostController";
store({ title: "Hello" }); // Auto-generates POST `/posts`
import StorePostController from "@/actions/App/Http/Controllers/StorePostController";
StorePostController({ title: "Hello" });
Route-Centric Development:
import { show } from "@/routes/post";
show(1); // Resolves to `/posts/1`
show(1, { query: { page: 2 } }); // `/posts/1?page=2`
Form Integration:
.form():
<form {...store.form()}>
{/* Auto-generates action="/posts" method="post" */}
</form>
<form {...update.form.put(1)}>
{/* Auto-generates action="/posts/1?_method=PUT" */}
</form>
Inertia.js Integration:
import { useForm } from "@inertiajs/react";
import { store } from "@/actions/App/Http/Controllers/PostController";
const form = useForm({ title: "Hello" });
form.submit(store()); // Auto-resolves URL/method
import { Link } from "@inertiajs/react";
import { show } from "@/actions/App/Http/Controllers/PostController";
<Link href={show(1)}>View Post</Link>
Dynamic Route Resolution:
import { index } from "@/actions/App/Http/Controllers/ClientPaymentsController";
index["/clients/{client}/payments"]({ client: 1 });
import { index } from "@/routes/clients/payments";
index({ client: 1 });
Query Parameter Management:
show(1, { mergeQuery: { page: 2 } }); // Preserves other params
null:
show(1, { mergeQuery: { page: null } });
HTTP Method Overrides:
show.head(1); // Forces HEAD method
show.url(1); // Returns only the URL
Tree-Shaking:
import { show } from "@/actions/App/Http/Controllers/PostController";
// Only `show` is bundled
Route Cache Inconsistencies:
route:cache may generate stale TypeScript definitions.php artisan route:clear
npm run build
Reserved JavaScript Words:
delete or import are renamed to deleteMethod/importMethod.Multiple Routes to Same Action:
index["/clients/{client}/payments"]({ client: 1 });
Optional Route Parameters:
0, false) may not render in URLs.null or omit the parameter.Base URL Mismatches:
APP_URL if not configured.APP_URL is set in .env or use absolute paths.Regenerate Definitions:
php artisan wayfinder:generate --force to overwrite files.Check Vite Plugin:
@laravel/vite-plugin-wayfinder is enabled in vite.config.js.Inspect Generated Files:
resources/js/wayfinder/ for correct imports and types.TypeScript Errors:
npm run dev
Custom Output Paths:
php artisan wayfinder:generate --path=custom/path
Skip Generation:
php artisan wayfinder:generate --skip-actions
Form Variants:
php artisan wayfinder:generate --with-form
Runtime Defaults:
show(1, { defaults: { page: 1 } });
const { show } = await import("@/actions/App/Http/Controllers/PostController");
How can I help you explore Laravel packages today?