Laravel Wayfinder: Bridging the Gap Between PHP Backends and TypeScript Frontends

Overview: The Quest for End-to-End Type Safety

For years, developers building with

have faced a persistent friction point: the communication gap between the PHP backend and the
JavaScript
or
TypeScript
frontend. While PHP has evolved into a robust, type-heavy language, those types often vanish the moment data hits the network. You might define a precise Product model or a strict Enum in Laravel, but your frontend remains blissfully unaware, forced to rely on manual type definitions that inevitably drift out of sync with the server.

solves this by acting as an automated bridge. It doesn't just generate static files; it performs deep analysis of your application to extract routes,
Inertia.js
props, validation rules, and broadcast events, turning them into fully-typed
TypeScript
helpers. This ensures that a change in your Laravel controller immediately triggers a type error in your
Vue.js
or
React
components if the data contract is broken. It brings the "all-in-one" type safety of
Livewire
to the world of modern SPAs and separated repositories.

Prerequisites

To get the most out of this tutorial, you should be comfortable with:

  • Laravel 10+: Basic knowledge of routing, controllers, and Form Requests.
  • Modern Frontend Frameworks: Familiarity with
    React
    or
    Vue.js
    , specifically using
    Vite
    as a build tool.
  • TypeScript Basics: Understanding how interfaces and types provide editor autocomplete and build-time safety.
  • GitHub Actions: Basic knowledge of CI/CD workflows if you plan to sync types across separate repositories.

Key Libraries & Tools

  • Surveyor: A "mostly static" analysis tool that inspects your PHP classes, methods, and bindings to extract raw metadata about your app.
  • Ranger: A layer above Surveyor that consumes raw data and transforms it into rich, digestible Data Transfer Objects (DTOs).
  • Wayfinder Vite Plugin: The client-side companion that watches for backend changes and triggers the regeneration of
    TypeScript
    definitions in real-time.
  • Laravel Echo: When combined with Wayfinder, it provides type-safe event broadcasting payloads.

Code Walkthrough: Implementing Type-Safe Contracts

1. The Vite Integration

Everything starts with the

configuration. You must register the Wayfinder plugin to enable the watcher that tracks your PHP files.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import wayfinder from 'wayfinder-vite-plugin';

export default defineConfig({
    plugins: [
        laravel(['resources/js/app.ts']),
        wayfinder({
            // Patterns of files to watch for changes
            watch: ['app/Http/Controllers/**', 'app/Models/**'] 
        }),
    ],
});

2. Auto-Generating Shared Props

In an

application, shared props (like the current user or flash messages) are notoriously difficult to type. Wayfinder analyzes your HandleInertiaRequests middleware to sync these automatically.

// app/Http/Middleware/HandleInertiaRequests.php
public function share(Request $request): array
{
    return array_merge(parent::share($request), [
        'auth' => [
            'user' => $request->user(),
        ],
        'is_admin' => (bool) $request->user()?->admin,
    ]);
}

On the frontend, Wayfinder performs declaration merging so that the usePage hook knows exactly what is available:

import { usePage } from '@inertiajs/react';

const { props } = usePage();

// TypeScript knows 'is_admin' exists and is a boolean
if (props.is_admin) {
    console.log("Access granted");
}

3. Validation via Form Requests

One of the most powerful features in the latest beta is the extraction of validation rules. When you type-hint a FormRequest in your controller, Wayfinder generates a matching

interface.

// app/Http/Requests/ProductUpdateRequest.php
public function rules(): array
{
    return [
        'name' => 'required|string',
        'price' => 'required|numeric|min:0',
        'description' => 'nullable|string',
    ];
}

Wayfinder converts these rules into a type you can pass to Inertia's useForm hook, preventing you from sending the wrong data types to the server.

import { useForm } from '@inertiajs/react';
import { ProductUpdateRequest } from '@/types/generated';

const form = useForm<ProductUpdateRequest>({
    name: '',
    price: 0,
    description: null,
});

Syntax Notes: Specificity Matters

Wayfinder relies on the clarity of your PHP code. The more specific your types are in Laravel, the better the

output. For example, if a controller method returns a collection, use
PHP
DocBlocks or native type hints to specify the model within that collection. Wayfinder effectively "reads" your intent. If you mark a property as nullable in a Form Request, it will correctly append | null to the generated
TypeScript
definition.

Practical Example: Jumping the Fence

What happens if your Laravel backend and

frontend live in separate repositories? This is the "Jump the Fence" scenario. You can use a
GitHub Actions
workflow to keep them in sync. When you commit a change to the Laravel API, the workflow runs Wayfinder, generates the new types, and automatically opens a Pull Request against the frontend repository.

This workflow ensures that the frontend team is immediately notified when a route changes or a new field is added to an API response. It turns a manual communication task into a fail-safe automated process.

Tips & Gotchas

  • Cashing Issues: During beta, the internal cache of
    Surveyor
    can occasionally become corrupted. If your types aren't reflecting your PHP changes, try clearing your app cache or restarting the
    Vite
    dev server.
  • Performance in Large Apps: Because Wayfinder performs static analysis across your entire codebase, very large applications might experience a slight delay (a few seconds) between saving a PHP file and the
    TypeScript
    server picking up the change.
  • Tree Shaking: Unlike older tools that exported every route into a global object, Wayfinder exports individual route helpers. This allows modern bundlers to "tree-shake" away any routes that aren't actually imported in your frontend code, keeping your production bundles lean.
  • Eloquent Resources: Full support for complex JsonResource transformations is still in active development. For the most reliable results, stick to arrayable and jsonable objects for now.
5 min read