Overview
Ziggy supports Laravel’s route-model binding , including custom route key names. When you pass a JavaScript object as a route parameter, Ziggy automatically extracts the correct property value using the model’s binding configuration.
How It Works
When you pass an object as a route parameter, Ziggy:
Checks if the route has a custom binding key defined
If yes, uses that key to extract the value from the object
If no custom key exists, falls back to the id property
const post = {
id: 3 ,
title: 'Introducing Ziggy v1' ,
slug: 'introducing-ziggy-v1' ,
date: '2020-10-23T20:59:24.359278Z' ,
};
route ( 'posts.show' , post );
// Ziggy uses the appropriate binding key automatically
Custom Route Keys
Laravel models can customize their route key using getRouteKeyName():
// app/Models/Post.php
class Post extends Model
{
public function getRouteKeyName ()
{
return 'slug' ;
}
}
Route :: get ( 'blog/{post}' , function ( Post $post ) {
return view ( 'posts.show' , [ 'post' => $post ]);
}) -> name ( 'posts.show' );
Ziggy automatically detects this configuration and uses the slug property:
const post = {
id: 3 ,
title: 'Introducing Ziggy v1' ,
slug: 'introducing-ziggy-v1' ,
date: '2020-10-23T20:59:24.359278Z' ,
};
route ( 'posts.show' , post );
// 'https://ziggy.test/blog/introducing-ziggy-v1'
// ✅ Uses 'slug' instead of 'id'
Ziggy reads the binding configuration from your Laravel routes when generating its JavaScript config. You don’t need to manually configure bindings in JavaScript.
Inline Custom Keys
Laravel allows you to specify custom binding keys directly in route definitions using the : syntax:
Route :: get ( 'authors/{author}/photos/{photo:uuid}' , fn ( Author $author , Photo $photo ) => /* ... */ )
-> name ( 'authors.photos.show' );
Ziggy respects these inline bindings:
const photo = {
uuid: '714b19e8-ac5e-4dab-99ba-34dc6fdd24a5' ,
filename: 'sunset.jpg' ,
};
route ( 'authors.photos.show' , [{ id: 1 , name: 'Ansel' }, photo ]);
// 'https://ziggy.test/authors/1/photos/714b19e8-ac5e-4dab-99ba-34dc6fdd24a5'
// ✅ Uses 'uuid' for photo parameter
Scoped Bindings
Ziggy supports Laravel’s scoped bindings with custom keys:
Route :: get ( 'posts/{post}/comments/{comment:uuid}' , fn ( Post $post , Comment $comment ) => /* ... */ )
-> name ( 'posts.comments.show' )
-> scopeBindings ();
const post = { id: 1 , title: 'Hello World' };
const comment = {
id: 42 ,
uuid: '9b7f4e8a-8e1c-4e5a-9c1d-8e7b4a9c1d2e' ,
body: 'Great post!' ,
};
route ( 'posts.comments.show' , [ post , comment ]);
// 'https://ziggy.test/posts/1/comments/9b7f4e8a-8e1c-4e5a-9c1d-8e7b4a9c1d2e'
While Ziggy uses the correct binding keys to generate URLs, the actual scope validation (ensuring the comment belongs to the post) happens on the Laravel backend.
How Bindings Are Resolved
Ziggy’s PHP code analyzes your routes to determine binding keys:
Binding Resolution Process (Ziggy.php)
private function resolveBindings ( array $routes ) : array
{
foreach ( $routes as $name => $route ) {
$bindings = [];
foreach ( $route -> signatureParameters ( UrlRoutable :: class ) as $parameter ) {
$model = Reflector :: getParameterClassName ( $parameter );
// Check if model overrides default route key name
$override = ( new ReflectionClass ( $model )) -> isInstantiable () && (
( new ReflectionMethod ( $model , 'getRouteKeyName' )) -> class !== Model :: class
|| ( new ReflectionMethod ( $model , 'getKeyName' )) -> class !== Model :: class
|| ( new ReflectionProperty ( $model , 'primaryKey' )) -> class !== Model :: class
);
// Use custom key or default to 'id'
$bindings [ $parameter -> getName ()] = $override ? app ( $model ) -> getRouteKeyName () : 'id' ;
}
// Merge with inline binding fields (e.g., {photo:uuid})
$routes [ $name ] = [ ... $bindings , ... $route -> bindingFields ()];
}
return $routes ;
}
This method:
Inspects route parameters that implement UrlRoutable
Checks if the model customizes its route key
Stores the binding configuration in Ziggy’s config
Merges inline binding fields from the route definition
Binding Substitution in JavaScript
When you pass an object, Ziggy’s JavaScript code extracts the value:
Binding Substitution (Router.js)
_substituteBindings ( params , { bindings , parameterSegments }) {
return Object . entries ( params ). reduce (( result , [ key , value ]) => {
// Skip if not an object or not a route parameter
if (
! value ||
typeof value !== 'object' ||
Array . isArray ( value ) ||
! parameterSegments . some (({ name }) => name === key )
) {
return { ... result , [key]: value };
}
// Find the binding key
const binding = value . hasOwnProperty ( bindings [ key ])
? bindings [ key ]
: value . hasOwnProperty ( 'id' )
? 'id'
: undefined ;
if ( binding === undefined ) {
throw new Error (
`Ziggy error: object passed as ' ${ key } ' parameter is missing route model binding key ' ${ bindings [ key ] } '.`
);
}
return { ... result , [key]: value [ binding ] };
}, {});
}
Error Handling
If you pass an object that doesn’t have the required binding key, Ziggy throws an error: const post = { title: 'Hello' }; // Missing 'slug' key
route ( 'posts.show' , post );
// Error: Ziggy error: object passed as 'post' parameter is missing route model binding key 'slug'.
Fallback to ID
If no custom binding key is defined, Ziggy defaults to using the id property:
Route :: get ( 'users/{user}' , fn ( User $user ) => /* ... */ ) -> name ( 'users.show' );
// User model doesn't override getRouteKeyName()
const user = { id: 5 , name: 'Jane Doe' };
route ( 'users.show' , user );
// 'https://ziggy.test/users/5'
// ✅ Falls back to 'id'
Mixed Parameter Types
You can mix objects with route-model binding and primitive values:
const author = {
id: 1 ,
name: 'Ansel Adams' ,
slug: 'ansel-adams' ,
};
// Object + primitive
route ( 'authors.photos.show' , [ author , '714b19e8' ]);
// 'https://ziggy.test/authors/ansel-adams/photos/714b19e8'
// Object + object
const photo = { uuid: '714b19e8' , filename: 'sunset.jpg' };
route ( 'authors.photos.show' , [ author , photo ]);
// 'https://ziggy.test/authors/ansel-adams/photos/714b19e8'
Best Practices
Pass full model objects when available
// ✅ Let Ziggy handle binding resolution
route ( 'posts.show' , post );
// ❌ Manual property access defeats the purpose
route ( 'posts.show' , post . slug );
Ensure objects have the required keys
// ✅ Object has all binding keys
const post = await axios . get ( '/api/posts/1' );
route ( 'posts.show' , post . data ); // Has 'slug' property
// ❌ Incomplete object will cause errors
const partial = { title: post . title };
route ( 'posts.show' , partial ); // Missing 'slug'!
Re-generate Ziggy config after model changes
If you change a model’s getRouteKeyName() method, regenerate Ziggy’s config: php artisan ziggy:generate
Or refresh the page if using the @routes Blade directive.
Common Use Cases
API Responses
When working with API data that includes model objects:
axios . get ( '/api/posts' ). then (( response ) => {
response . data . forEach (( post ) => {
const url = route ( 'posts.show' , post );
console . log ( ` ${ post . title } : ${ url } ` );
});
});
Vue Components
< template >
< a : href = " route ( 'posts.show' , post ) " >
{{ post . title }}
</ a >
</ template >
< script setup >
const post = {
id: 1 ,
slug: 'hello-world' ,
title: 'Hello World' ,
};
</ script >
React Components
import { useRoute } from 'ziggy-js' ;
export default function PostLink ({ post }) {
const route = useRoute ();
return (
< a href = { route ( 'posts.show' , post ) } >
{ post . title }
</ a >
);
}
Next Steps
Default Parameters Learn about default parameter values
TypeScript Support Get autocomplete for routes and parameters