Skip to main content

Quickstart Guide

This guide will walk you through creating your first Ziggy-powered feature from a fresh Laravel installation to making your first route() call in JavaScript.

Prerequisites

Before starting, ensure you have:
  • Laravel 9.0 or higher installed
  • PHP 8.1 or higher
  • Node.js and npm (for frontend assets)

Step 1: Install Ziggy

Install Ziggy via Composer:
composer require tightenco/ziggy
Ziggy is now installed and automatically registered!

Step 2: Create Laravel Routes

Let’s create a simple blog post feature. Add these routes to routes/web.php:
routes/web.php
use App\Http\Controllers\PostController;

Route::get('/', function () {
    return view('welcome');
})->name('home');

Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');
Route::post('/posts', [PostController::class, 'store'])->name('posts.store');
The .name() method is crucial—Ziggy only works with named routes.

Step 3: Add @routes to Your Layout

Open your main layout file (typically resources/views/layouts/app.blade.php or create it if it doesn’t exist):
resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ config('app.name', 'Laravel') }}</title>
    
    {{-- Add Ziggy routes BEFORE your JavaScript --}}
    @routes
    
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
    @yield('content')
</body>
</html>
Place @routes before @vite or any other JavaScript to ensure routes are available when your scripts load.

Step 4: Create a View with JavaScript

Create a posts listing page at resources/views/posts/index.blade.php:
resources/views/posts/index.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <h1>Blog Posts</h1>
    
    <div id="posts-container">
        <p>Loading posts...</p>
    </div>
    
    <button id="create-post-btn" class="btn btn-primary">
        Create New Post
    </button>
</div>

<script>
    // Fetch posts using Ziggy's route() helper
    fetch(route('posts.index'))
        .then(response => response.json())
        .then(posts => {
            const container = document.getElementById('posts-container');
            
            if (posts.length === 0) {
                container.innerHTML = '<p>No posts yet.</p>';
                return;
            }
            
            container.innerHTML = posts.map(post => `
                <div class="post">
                    <h2>${post.title}</h2>
                    <p>${post.excerpt}</p>
                    <a href="${route('posts.show', post.id)}" class="btn btn-link">
                        Read More
                    </a>
                </div>
            `).join('');
        })
        .catch(error => console.error('Error loading posts:', error));
    
    // Handle create post button
    document.getElementById('create-post-btn').addEventListener('click', () => {
        // Navigate to create page using Ziggy
        window.location.href = route('posts.create');
    });
</script>
@endsection

Step 5: Use route() in Your JavaScript

Let’s create a more complex example with Axios in resources/js/app.js:
resources/js/app.js
import axios from 'axios';

// Configure Axios defaults
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

// Example 1: GET request
function loadPost(postId) {
    return axios.get(route('posts.show', postId))
        .then(response => response.data)
        .catch(error => {
            console.error('Failed to load post:', error);
            throw error;
        });
}

// Example 2: POST request with CSRF token
function createPost(postData) {
    return axios.post(route('posts.store'), postData)
        .then(response => {
            console.log('Post created successfully!');
            // Redirect to the new post
            window.location.href = route('posts.show', response.data.id);
        })
        .catch(error => {
            console.error('Failed to create post:', error);
            throw error;
        });
}

// Example 3: Using with query parameters
function searchPosts(searchTerm, page = 1) {
    const url = route('posts.index', {
        search: searchTerm,
        page: page,
        sort: 'created_at',
    });
    
    return axios.get(url).then(response => response.data);
}

// Make functions available globally
window.loadPost = loadPost;
window.createPost = createPost;
window.searchPosts = searchPosts;

Step 6: Test It Out

1

Start Your Development Server

php artisan serve
2

Open Browser Console

Visit http://localhost:8000 and open your browser’s developer console (F12).
3

Try Ziggy Commands

Test these commands in the console:
// Generate a URL
route('posts.show', 1);
// Output: 'http://localhost:8000/posts/1'

// Check if route exists
route().has('posts.index');
// Output: true

// Get current route
route().current();
// Output: 'home' (or whatever page you're on)

// List all routes
Object.keys(Ziggy.routes);
// Output: ['home', 'posts.index', 'posts.show', 'posts.store']
Success! You’re now using Ziggy to generate type-safe URLs in JavaScript.

Common Use Cases

With Alpine.js

<div x-data="{
    posts: [],
    async loadPosts() {
        const response = await fetch(route('posts.index'));
        this.posts = await response.json();
    }
}" x-init="loadPosts()">
    <template x-for="post in posts" :key="post.id">
        <a :href="route('posts.show', post.id)" x-text="post.title"></a>
    </template>
</div>

With Vue 3

First, install the Vue plugin:
resources/js/app.js
import { createApp } from 'vue';
import { ZiggyVue } from 'ziggy-js';

const app = createApp({});

// Install Ziggy's Vue plugin
app.use(ZiggyVue);

app.mount('#app');
Then use it in your components:
PostsList.vue
<template>
    <div>
        <h1>Posts</h1>
        <div v-for="post in posts" :key="post.id">
            <a :href="route('posts.show', post.id)">
                {{ post.title }}
            </a>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted, inject } from 'vue';
import axios from 'axios';

const route = inject('route');
const posts = ref([]);

onMounted(async () => {
    const response = await axios.get(route('posts.index'));
    posts.value = response.data;
});
</script>

With React

PostsList.jsx
import React, { useState, useEffect } from 'react';
import { useRoute } from 'ziggy-js';
import axios from 'axios';

export default function PostsList() {
    const route = useRoute();
    const [posts, setPosts] = useState([]);
    
    useEffect(() => {
        axios.get(route('posts.index'))
            .then(response => setPosts(response.data))
            .catch(error => console.error(error));
    }, []);
    
    return (
        <div>
            <h1>Posts</h1>
            {posts.map(post => (
                <div key={post.id}>
                    <a href={route('posts.show', post.id)}>
                        {post.title}
                    </a>
                </div>
            ))}
        </div>
    );
}

With Form Submissions

<form id="post-form">
    <input type="text" name="title" placeholder="Post title" required>
    <textarea name="body" placeholder="Post content" required></textarea>
    <button type="submit">Create Post</button>
</form>

<script>
document.getElementById('post-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const formData = new FormData(e.target);
    const data = Object.fromEntries(formData);
    
    try {
        const response = await fetch(route('posts.store'), {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
            },
            body: JSON.stringify(data),
        });
        
        const post = await response.json();
        
        // Redirect to the new post
        window.location.href = route('posts.show', post.id);
    } catch (error) {
        console.error('Error creating post:', error);
        alert('Failed to create post');
    }
});
</script>

Debugging Tips

Problem: Uncaught ReferenceError: route is not definedSolution:
  • Ensure @routes is added to your layout before your JavaScript
  • Clear your view cache: php artisan view:clear
  • Check that you’re on a page that uses the layout with @routes
Problem: Ziggy error: route 'posts.show' is not in the route listSolution:
  • Verify the route exists: php artisan route:list --name=posts.show
  • Make sure the route has a name: ->name('posts.show')
  • Clear route cache: php artisan route:clear
  • Reload the page to get the latest route list
Problem: URLs have wrong domain or protocol (http vs https)Solution:

Next Steps

Route Parameters

Learn advanced parameter handling and route model binding

Filtering Routes

Control which routes are exposed to JavaScript

TypeScript Setup

Enable route name autocompletion with TypeScript

Framework Integration

Deep dives for Vue, React, and other frameworks

You’re All Set!

Congratulations! You’ve successfully:
1

Installed Ziggy

2

Added the @routes directive

3

Created named routes

4

Used route() in JavaScript

You’re now ready to build amazing features with type-safe routing!

Build docs developers (and LLMs) love