> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom pivot models and chaperone

> How to treat belongsToMany pivot tables as custom Pivot models, plus the chaperone method added in Laravel 13 for automatic eager loading.

## What is a custom pivot model?

By default, the pivot table used by a [belongsToMany (many-to-many)](/en/eloquent-relationships#belongstomany-many-to-many) relationship is represented by a plain `Illuminate\Database\Eloquent\Relations\Pivot` instance. When you want to store extra columns on the pivot table (an approval timestamp, a role type, etc.), or add accessors, mutators, or custom methods, create a custom model that extends `Pivot`.

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
    protected function casts(): array
    {
        return [
            'approved' => 'boolean',
        ];
    }
}
```

Call `using()` on your `belongsToMany` definition to tell the relationship to use this custom model.

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Role extends Model
{
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class)
            ->using(RoleUser::class);
    }
}
```

<Info>
  When you save a custom pivot model, name it using the **singular form of the two model names in alphabetical order** (`RoleUser`, not `UserRole`). This is only a naming convention — you can pick any class name you like.
</Info>

## Specifying pivot attributes with `as()`

By default, pivot values are accessed through a `pivot` property. Use the `as()` method to change that name.

```php theme={null}
return $this->belongsToMany(Role::class)
    ->using(RoleUser::class)
    ->as('membership')
    ->withTimestamps()
    ->withPivot('approved');
```

```php theme={null}
foreach ($user->roles as $role) {
    echo $role->membership->approved;
    echo $role->membership->created_at;
}
```

## Extra columns and timestamps

When your pivot table has additional columns like `approved`, include them explicitly with `withPivot()`. Call `withTimestamps()` if you want Eloquent to manage `created_at` / `updated_at`.

```php theme={null}
return $this->belongsToMany(Role::class)
    ->using(RoleUser::class)
    ->withPivot('approved')
    ->withTimestamps();
```

<Warning>
  Eloquent automatically updates the pivot table's `updated_at` only when the pivot model is explicitly specified via `using()`. `withTimestamps()` also works with the default `Pivot` class, but pointing `using()` at a custom model unlocks custom events, custom casts, and more.
</Warning>

## Inverse relationships from the pivot model

You are free to define `belongsTo` relationships back to the declaring model and the related model on your custom pivot.

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
    public function role(): BelongsTo
    {
        return $this->belongsTo(Role::class);
    }

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}
```

With these in place, you can access `$roleUser->role` or `$roleUser->user` from a standalone pivot instance. If you want to automatically eager-load these when running the parent query, use the `chaperone()` method described below.

## Automatic eager loading with `chaperone()` (Laravel 13)

Laravel 13 introduced the `chaperone()` method, which automatically hydrates `belongsTo` relationships defined on a pivot model — such as `role()` / `user()` — when the `belongsToMany` query runs.

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Role extends Model
{
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class)
            ->using(RoleUser::class)
            ->chaperone();
    }
}
```

When you call `chaperone()`, Eloquent infers the names of the `belongsTo` relationships on the pivot model (`RoleUser`) and, when you retrieve a collection with something like `Role::with('users')`, wires up references from each pivot to the declaring and related models with no additional queries.

```php theme={null}
$role = Role::with('users')->first();

foreach ($role->users as $user) {
    // Access the parent model through the pivot with no extra query
    echo $user->pivot->role->name;
}
```

### Non-standard relationship names

If the `belongsTo` methods on your pivot model don't follow the standard naming convention (singular camelCase of the declaring or related model name), pass explicit names as arguments to `chaperone()`.

```php theme={null}
return $this->belongsToMany(User::class)
    ->using(RoleUser::class)
    ->chaperone(declaring: 'role', related: 'user');
```

<Tip>
  `chaperone()` extends N+1 prevention to references made through the pivot table. It works well alongside regular `with()` eager loading in applications that frequently read parent-model data through the pivot (for example, showing an approval timestamp next to a user name).
</Tip>

## Next steps

<Card title="Relationships" icon="link" href="/en/eloquent-relationships">
  Review the basics of defining relationships, including belongsToMany.
</Card>

<Card title="Eloquent Observers and Model Events" icon="bolt" href="/en/advanced/eloquent-observers">
  Learn how to use model events to hook into saving and updating pivot models.
</Card>


## Related topics

- [Eloquent relationships](/en/eloquent-relationships.md)
- [Upgrade guide: Laravel 12 to 13](/en/blog/upgrade-12-to-13.md)
- [Custom providers](/en/packages/laravel-copilot-sdk/custom-providers.md)
- [Custom Authentication Guards](/en/advanced/custom-auth-guard.md)
- [Eloquent Custom Casts](/en/advanced/eloquent-casts.md)
