Broadcast Channels
The Laravel TypeScript Publisher compiles every channel you register in routes/channels.php into one broadcast-channels.ts file. The file holds a BroadcastChannel union of template literal types, and a BroadcastChannels const with an accessor for every dynamic segment. You build a channel name the way you call a route helper, instead of typing {placeholder} strings by hand.
Broadcast channels don't need the @tolki/ts runtime. The output is a plain union type and a plain object of strings and accessor functions. See Installing @tolki/ts for the features that do.
How Broadcast Channel Types Are Generated
Broadcast channels work differently from enums, models, resources and form requests. There's no class to collect or filter, and no attributes:
- Every registered channel: the package reads every channel name you register with
Broadcast::channel(...). - Only the name matters: a closure and a channel class publish the same way, as Both Registration Styles shows.
- One combined file: every channel goes into one file, named by
broadcast_channels.filename(broadcast-channels.tsby default). There's noindex.tsbarrel, no per-channel files, and noincluded,excludedoradditional_directoriesfiltering, since no PHP class backs a channel. With no channels registered, the file isexport {};. - No attributes:
#[TsExclude]and#[TsCasts]don't apply, for the same reason. See No Per-Channel Attributes.
Anatomy of the Generated File
Given these registrations:
// routes/channels.php
use App\Broadcasting\PublicAnnouncementsChannel;
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('orders.{orderId}', function ($user, $orderId) {
return true;
});
Broadcast::channel('user.{userId}.notifications', function ($user, $userId) {
return (int) $user->id === (int) $userId;
});
// Both a channel of its own and the start of the channel below.
Broadcast::channel('chat.{roomId}', function ($user, $roomId) {
return true;
});
Broadcast::channel('chat.{roomId}.messages', function ($user, $roomId) {
return true;
});
// A channel class. Only the name string affects the TypeScript output.
Broadcast::channel('public-announcements', PublicAnnouncementsChannel::class);The package generates:
export type BroadcastChannel =
| `orders.${string | number}`
| `user.${string | number}.notifications`
| `chat.${string | number}`
| `chat.${string | number}.messages`
| `public-announcements`;
export const BroadcastChannels = {
orders: (orderId: string | number) => `orders.${orderId}` as const,
user: (userId: string | number) => ({
notifications: `user.${userId}.notifications` as const,
}),
chat: (roomId: string | number) => ({
$channel: `chat.${roomId}` as const,
messages: `chat.${roomId}.messages` as const,
}),
"public-announcements": `public-announcements` as const,
};The file has two exports:
BroadcastChannel: a union of template literal types. Each{param}segment becomes${string | number}, whether PHP binds it to a model, an enum or a scalar, since only the channel name matters.BroadcastChannels: an object that mirrors the dot-notation names. A channel with no dynamic segments is a string constant. A channel that ends in a{param}is a function that returns the channel name. A channel with a{param}and more segments after it, likeuser.{userId}.notifications, is a function that returns an object of the segments that follow.
A static segment that isn't a valid JavaScript identifier, such as public-announcements with its hyphen, becomes a quoted key. See Quoted Keys.
How Channel Names Become Accessors
The accessor shape follows Laravel Wayfinder's. The package splits each channel name on . and builds BroadcastChannels from the pieces:
- Static segments become keys, nested in the order they appear.
{param}segments become the parameters of the function for the static segment before them, soorders.{orderId}givesorders(orderId).- Shared leading segments share one key.
chat.{roomId}andchat.{roomId}.messagesboth live underBroadcastChannels.chat(roomId).
Shared Segments Need the Same Parameter Names
Channels that share a segment have to give its parameters the same names. orders.{orderId} beside orders.{slug}.timeline gives the orders key two different parameters, and ts:publish fails with a "conflicting parameter names" error. Rename one wildcard so the two match.
Both Registration Styles
Laravel lets you register a channel with a closure or with a channel class that has a join() method:
// A closure
Broadcast::channel('orders.{orderId}', function ($user, $orderId) {
return true;
});
// A channel class, which only affects authorization in PHP
Broadcast::channel('order.{orderId}', OrderChannel::class);Both produce the same TypeScript for the same channel name, because the package reads only the name. It never reads the closure or the class.
The $channel Accessor for Overlapping Prefixes
When a channel name is a channel of its own and also the start of other channels, like chat.{roomId} beside chat.{roomId}.messages, its accessor returns an object. $channel on that object is the shorter channel's own name:
BroadcastChannels.chat(42).$channel; // 'chat.42', the chat room itself
BroadcastChannels.chat(42).messages; // 'chat.42.messages', the room's message streamWithout $channel, you couldn't reach the plain chat.{roomId} channel once chat returns an object with a messages key.
Quoted Keys
A static segment with characters a bare JavaScript key can't hold, such as a hyphen, is quoted:
export const BroadcastChannels = {
"public-announcements": `public-announcements` as const,
"order-status": (statusId: string | number) =>
`order-status.${statusId}` as const,
};Reach a quoted key with bracket notation:
BroadcastChannels["public-announcements"];
BroadcastChannels["order-status"](3);The BroadcastChannel Type
Each registered channel adds one member to the BroadcastChannel union. Use it to type a helper that accepts any valid channel name without naming a specific one:
import type { BroadcastChannel } from "@js/types/data/broadcast-channels";
function subscribe(channel: BroadcastChannel) {
return Echo.private(channel);
}
subscribe(BroadcastChannels.orders(42)); // ✓
subscribe("not-a-real-channel"); // ✗ type errorNo Per-Channel Attributes
Channels are collected as plain name strings, not as PHP classes, so the attributes available for enums, models and form requests don't apply:
- No
#[TsExclude]: to leave a channel out, remove itsBroadcast::channel(...)registration, or skip it with a condition inroutes/channels.php, such asif (! app()->isProduction()). - No
#[TsCasts]: there's no per-channel type to override. Every dynamic segment isstring | number, matching how Laravel resolves a channel name when it authorizes a subscription, whatever PHP type the segment binds to. - No
included,excludedoradditional_directoriesconfig: every channel your app registers is included. There's no directory to search, since channels don't live in class files.
Configuration Reference
The Configuration Reference lists every broadcast_channels.* key, including the class overrides for customizing the pipeline.