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

# Package Broadcast Channel Authorization and Overrides

> How a package's private channel authorization is evaluated, how an application can override it, and how to verify it with HTTP tests.

When a package broadcasts real-time events, separate **defining the channel name** from **the check that decides who may join**. A channel a package registers in `boot()` can be overwritten by the application's `routes/channels.php` if both use the same name. Understanding the registration and evaluation order helps avoid bugs during maintenance.

This page references the Laravel official [Broadcasting](https://kawax.biz/en/broadcasting.md) docs and the implementation in the latest release `v13.42.0` used for verification. For how to send events, see [Broadcasting](https://kawax.biz/en/broadcasting.md). For how providers are loaded, see [Package Discovery](https://kawax.biz/en/advanced/package-discovery.md).

## How channel authorization works

When a client subscribes to a private channel, it sends a request to `/broadcasting/auth`. `BroadcastController::authenticate()` hands off to `Broadcast::auth()`, and the `Broadcaster` evaluates the registered callbacks in order.

The evaluation rules are:

* If a callback returns `false`, an `AccessDeniedHttpException` is thrown immediately.
* If a callback returns a truthy value such as `true`, authorization succeeds at that point.
* If a callback returns a falsy value such as `null`, evaluation moves on to the next registered callback. If none grants access, an `AccessDeniedHttpException` is thrown at the end.

> [!NOTE]
>
> The `Pusher` and `Reverb` drivers strip the `private-` and `presence-` prefixes before authorization. For that reason, do not include the prefix in the channel names you register.

## Registering a channel in a package

As an example, a hypothetical `Acme\Orders` package broadcasts order status changes. The channel name is `acme.orders.{order}`, and only the order's owner may subscribe.

In the package's `ServiceProvider`, call `Broadcast::channel()` inside `boot()`. Delegate the decision to a class inside the package.

```php theme={null}
use Acme\Orders\Broadcasting\OrderChannel;
use Illuminate\Support\Facades\Broadcast;

public function boot(): void
{
    Broadcast::channel('acme.orders.{order}', OrderChannel::class);
}
```

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

namespace Acme\Orders\Broadcasting;

use Acme\Orders\Models\Order;
use Illuminate\Contracts\Auth\Authenticatable;

class OrderChannel
{
    public function join(Authenticatable $user, Order $order): bool
    {
        return $user->getAuthIdentifier() === $order->owner_id;
    }
}
```

The first argument of `join()` receives the user at authorization time. The remaining arguments receive the values of the `{...}` placeholders in the channel name. If the parameter name matches `{order}` and the `Order $order` type is a subclass of `UrlRoutable`, route model binding applies.

> [!NOTE]
>
> The `$callback` passed to `Broadcast::channel()` can be a `callable` or a class name whose `join()` method handles the check. For a class name, `Broadcaster::normalizeChannelHandlerToCallable()` builds the instance from the container and then calls `join()`.

## Overriding in the application

The file passed to `withBroadcasting()` (usually `routes/channels.php`) is loaded inside a `booted` callback that `withBroadcasting()` registers. The `booted` callbacks run after every service provider's `boot()` has finished, so the package's `boot()` registration happens first.

| Order | Step | What gets registered |
| - | - | - |
| 1 | The package's `boot()` | The `acme.orders.{order}` callback |
| 2 | Route loading in the application's `booted` callback | The `routes/channels.php` callback |

`Broadcaster::channel()` stores callbacks in an internal array keyed by channel name. If the application registers the same name, the second registration replaces the package's. Laravel does not raise an error for a duplicate name.

To avoid overrides, make the channel name unique. For example, a package-specific prefix such as `acme.orders.{order}` lowers the chance that the application registers the same name.

Channel names are a public contract with clients. Once chosen, do not change them. Changing a name prevents existing clients from subscribing.

> [!WARNING]
>
> A channel registered later with the same name silently takes precedence. `php artisan channel:list` shows each channel name and its resolver (a class name or `Closure`), so you can check which registration is in effect.

## Channel options and guards

The third argument to `Broadcast::channel()`, `$options`, is stored as per-channel configuration. Setting `guards` makes the authorization try the listed guards in order.

```php theme={null}
Broadcast::channel('acme.orders.{order}', OrderChannel::class, [
    'guards' => ['web', 'api'],
]);
```

If `guards` is omitted, the request's default user (`$request->user()`) is used for authorization. When `guards` is set, `$request->user($guard)` is called for each guard in order, and the first user returned is used. If a package fixes a guard name, the application's guard configuration can cause authorization to fail. Expose the guard names as configuration so the application can choose them.

## Verifying channel registration in tests

Registered channels can be retrieved from the driver with `getChannels()`, which returns a `Collection`. The keys are the channel names themselves.

```php theme={null}
use Illuminate\Support\Facades\Broadcast;

public function test_package_registers_order_channel(): void
{
    $channels = Broadcast::driver()->getChannels();

    $this->assertTrue($channels->has('acme.orders.{order}'));
}
```

Verify authorization results with an HTTP test against `/broadcasting/auth`. The `null` and `log` drivers return nothing from `auth()`, so they cannot be used for this test. Channels are registered on the default driver when the application boots, so set `BROADCAST_CONNECTION` to `reverb` or `pusher` in a test configuration such as `phpunit.xml` before sending the authenticated request.

```php theme={null}
use Acme\Orders\Models\Order;
use App\Models\User;

public function test_owner_can_join_order_channel(): void
{
    $user = User::factory()->create();
    $order = Order::factory()->for($user, 'owner')->create();

    $this->actingAs($user)
        ->post('/broadcasting/auth', [
            'channel_name' => 'private-acme.orders.'.$order->id,
            'socket_id' => '1234.5678',
        ])
        ->assertOk();
}
```

> [!TIP]
>
> The test app must register the `/broadcasting/auth` route. `Broadcast::routes()` registers it, and `BroadcastManager::channelRoutes()` only delegates to `routes()`. If the routes are cached, `routes()` does nothing.

## Maintenance notes

* Channel names are a public contract. Renaming them prevents existing clients from subscribing.
* Class-based channels are resolved from the `Container`. Make sure constructor dependencies can be resolved from the container.

## Summary

A package's channel authorization works when it registers `Broadcast::channel()` in `boot()`. If the application's `routes/channels.php` registers the same name later, the package's authorization is replaced. Using a prefixed channel name, separating the check into a class, and verifying `/broadcasting/auth` results with HTTP tests keeps the package easier to maintain.

## Primary sources

* [Laravel official docs: Broadcasting](https://github.com/laravel/docs/blob/13.x/broadcasting.md)
* [Framework v13.42.0: Broadcaster](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php)
* [Framework v13.42.0: UsePusherChannelConventions](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Broadcasting/Broadcasters/UsePusherChannelConventions.php)
* [Framework v13.42.0: BroadcastManager](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Broadcasting/BroadcastManager.php)
* [Framework v13.42.0: ApplicationBuilder](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php)
* [Framework v13.42.0: BroadcastController](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Broadcasting/BroadcastController.php)
* [Framework v13.42.0: channel:list command](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Foundation/Console/ChannelListCommand.php)


## Related topics

- [Package policy registration and overrides](https://kawax.biz/en/advanced/package-authorization.md)
- [Inside the Mercure broadcast driver](https://kawax.biz/en/advanced/mercure-broadcasting.md)
- [Broadcasting](https://kawax.biz/en/broadcasting.md)
- [Authorization (gates and policies)](https://kawax.biz/en/authorization.md)
- [Package translation overrides and updates](https://kawax.biz/en/advanced/package-translations.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.