Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
W

wayfinder

> 编程语言
Open source

<p align="center"</p Introduction Laravel Wayfinder bridges your Laravel backend and TypeScript frontend with zero friction. It automatical…

1.8K stars0 likes4 views
WebsiteGitHub

About

<p align="center"</p Introduction Laravel Wayfinder bridges your Laravel backend and TypeScript frontend with zero friction. It automatical…

Introduction

Laravel Wayfinder bridges your Laravel backend and TypeScript frontend with zero friction. It automatically generates fully-typed, importable TypeScript functions for your controllers and routes — so you can call your Laravel endpoints directly in your client code just like any other function. No more hardcoding URLs, guessing route parameters, or syncing backend changes manually.

[!IMPORTANT] Wayfinder is currently in Beta, the API is subject to change prior to the v1.0.0 release. All notable changes will be documented in the changelog.

[!NOTE] Want to try the next version of Wayfinder? You can find the beta here.

Installation

To get started, install Wayfinder via the Composer package manager:

composer require laravel/wayfinder

Next, install the Wayfinder Vite plugin to ensure that your routes are generated during Vite's build step and also whenever your files change while running the Vite's dev server.

First, install the plugin via NPM:

bash
npm i -D @laravel/vite-plugin-wayfinder

Then, update your application's vite.config.js file to watch for changes to your application's routes and controllers:

typescript
import { wayfinder } from "@laravel/vite-plugin-wayfinder";

export default defineConfig({
    plugins: [
        wayfinder(),
        // ...
    ],
});

You can read about all of the plugin's configuration options in the documentation.

Generating TypeScript Definitions

The wayfinder:generate command can be used to generate TypeScript definitions for your routes and controller methods:

php artisan wayfinder:generate

By default, Wayfinder generates files in three directories (wayfinder, actions, and routes) within resources/js, but you can configure the base path:

php artisan wayfinder:generate --path=resources/js/wayfinder

The --skip-actions and --skip-routes options may be used to skip TypeScript definition generation for controller methods or routes, respectively:

php artisan wayfinder:generate --skip-actions
php artisan wayfinder:generate --skip-routes

You can safely .gitignore the wayfinder, actions, and routes directories as they are completely re-generated on every build.

Deploying

Wayfinder reads routes from the registered router, so if Laravel boots with a cached route table left over from a previous release, generation will run against those stale routes. Any routes added since the last route:cache will be silently missing from the generated routes/ and actions/ directories, and Vite will fail with errors like Could not load resources/js/routes/<name>.

If your deploy script runs php artisan optimize (or route:cache directly) at the end of a deploy, run php artisan route:clear before regenerating on the next deploy. With the Vite plugin, this needs to happen before npm run build, since the plugin invokes wayfinder:generate during the build:

bash
php artisan route:clear
npm run build

Usage

Wayfinder functions return an object that contains the resolved URL and default HTTP method:

typescript
import { show } from "@/actions/App/Http/Controllers/PostController";

show(1); // { url: "/posts/1", method: "get" }

If you just need the URL, or would like to choose a method from the HTTP methods defined on the server, you can invoke additional methods on the Wayfinder generated function:

typescript
import { show } from "@/actions/App/Http/Controllers/PostController";

show.url(1); // "/posts/1"
show.head(1); // { url: "/posts/1", method: "head" }

Wayfinder functions accept a variety of shapes for their arguments:

typescript
import { show, update } from "@/actions/App/Http/Controllers/PostController";

// Single parameter action...
show(1);
show({ id: 1 });

// Multiple parameter action...
update([1, 2]);
update({ post: 1, author: 2 });
update({ post: { id: 1 }, author: { id: 2 } });

[!NOTE] If you are using a JavaScript reserved word such as delete or import, as a method in your controller, Wayfinder will rename it to [method name]Method (deleteMethod, importMethod) when generating its functions. This is because these words are not allowed as variable declarations in JavaScript.

If you've specified a key for the parameter binding, Wayfinder will detect this and allow you to pass the value in as a property on an object:

typescript
import { show } from "@/actions/App/Http/Controllers/PostController";

// Route is /posts/{post:slug}...
show("my-new-post");
show({ slug: "my-new-post" });

Invokable Controllers

If your controller is an invokable controller, you may simply invoke the imported Wayfinder function directly:

typescript
import StorePostController from "@/actions/App/Http/Controllers/StorePostController";

StorePostController();

Importing Controllers

You may also import the Wayfinder generated controller definition and invoke its individual methods on the imported object:

typescript
import PostController from "@/actions/App/Http/Controllers/PostController";

PostController.show(1);

[!NOTE] In the example above, importing the entire controller prevents the PostController from being tree-shaken, so all PostController actions will be included in your final bundle.

Importing Named Routes

Wayfinder can also generate methods for your application's named routes as well:

typescript
import { show } from "@/routes/post";

// Named route is `post.show`...
show(1); // { url: "/posts/1", method: "get" }

Multiple Routes To The Same Action

If two or more routes point at the same controller method, Wayfinder can't tell which URL you meant from the action alone, so the generated export becomes a dictionary keyed by URI instead of a callable:

php
Route::get('clients/{client}/payments', [ClientPaymentsController::class, 'index'])
    ->name('clients.payments.index');

Route::get('clients/{client}/payments-archive', [ClientPaymentsController::class, 'index'])
    ->name('clients.payments.archive');
typescript
import { index } from "@/actions/App/Http/Controllers/ClientPaymentsController";

// `index` is not callable directly — pick the URI you want:
index["/clients/{client}/payments"]({ client: 1 });

If two of those routes share a URI and differ only by verb, each key is prefixed with the verb, so you can still pick the one you want:

php
Route::get('/exports/{report}', ExportController::class)
    ->name('exports.show');

Route::post('/exports/{report}', ExportController::class)
    ->middleware('throttle:5,1')
    ->name('exports.run');
typescript
import ExportController from "@/actions/App/Http/Controllers/ExportController";

ExportController["get /exports/{report}"]({ report: 1 });
ExportController["post /exports/{report}"]({ report: 1 });

A route that answers to more than one verb joins them with |, as in ExportController["put|patch /exports/{report}"]. Exports whose URIs are already unique keep the plain URI keys shown above.

In most cases it is easier to import the route by name from your generated routes/ directory instead:

typescript
import { index } from "@/routes/clients/payments";

index({ client: 1 }); // { url: "/clients/1/payments", method: "get" }

Conventional Forms

If your application uses conventional HTML form submissions, Wayfinder can help you out there as well. First, opt into form variants when generating your TypeScript definitions:

bash
php artisan wayfinder:generate --with-form

Then, you can use the .form variant to generate <form> object attributes automatically:

typescript
import { store, update } from "@/actions/App/Http/Controllers/PostController";

const Page = () => (
    <form {...store.form()}>
        {/* <form action="/posts" method="post"> */}
        {/* ... */}
    </form>
);

const Page = () => (
    <form {...update.form(1)}>
        {/* <form action="/posts/1?_method=PATCH" method="post"> */}
        {/* ... */}
    </form>
);

If your form action supports multiple methods and would like to specify a method, you can invoke additional methods on the form:

typescript
import { store, update } from "@/actions/App/Http/Controllers/PostController";

const Page = () => (
    <form {...update.form.put(1)}>
        {/* <form action="/posts/1?_method=PUT" method="post"> */}
        {/* ... */}
    </form>
);

Query Parameters

All Wayfinder methods accept an optional, final options argument to which you may pass a query object. This object can be used to append query parameters onto the resulting URL:

typescript
import { show } from "@/actions/App/Http/Controllers/PostController";

const options = {
    query: {
        page: 1,
        sort_by: "name",
    },
};

show(1, options); // { url: "/posts/1?page=1&sort_by=name", method: "get" }
show.get(1, options); // { url: "/posts/1?page=1&sort_by=name", method: "get" }
show.url(1, options); // "/posts/1?page=1&sort_by=name"
show.form.head(1, options); // { action: "/posts/1?page=1&sort_by=name&_method=HEAD", method: "get" }

You can also merge with the URL's existing parameters by passing a mergeQuery object instead:

typescript
import { show } from "@/actions/App/Http/Controllers/PostController";

// window.location.search = "?page=1&sort_by=category&q=shirt"

const options = {
    mergeQuery: {
        page: 2,
        sort_by: "name",
    },
};

show.url(1, options); // "/posts/1?page=2&sort_by=name&q=shirt"

If you would like to remove a parameter from the resulting URL, define the value as null or undefined:

typescript
import { show } from "@/actions/App/Http/Controllers/PostController";

// window.location.search = "?page=1&sort_by=category&q=shirt"

const options = {
    mergeQuery: {
        page: 2,
        sort_by: null,
    },
};

show.url(1, options); // "/posts/1?page=2&q=shirt"

Wayfinder and Inertia

When using Inertia, you can pass the result of a Wayfinder method directly to the submit method of useForm, it will automatically resolve the correct URL and method:

https://inertiajs.com/forms#wayfinder

typescript
import { useForm } from "@inertiajs/react";
import { store } from "@/actions/App/Http/Controllers/PostController";

const form = useForm({
    name: "My Big Post",
});

form.submit(store()); // Will POST to `/posts`...

You may also use Wayfinder in conjunction with Inertia's Link component:

https://inertiajs.com/links#wayfinder

typescript
import { Link } from "@inertiajs/react";
import { show } from "@/actions/App/Http/Controllers/PostController";

const Nav = () => <Link href={show(1)}>Show me the first post</Link>;

Contributing

Thank you for considering contributing to Wayfinder! You can read the contribution guide here.

Code of Conduct

In order to ensure that the Laravel community is welcoming to all, please review and abide by the Code of Conduct.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

License

Wayfinder is open-sourced software licensed under the MIT license.

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

PHP

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言