Getting started with Laravel
Live updates, chat, who is online, calls and group calls in a Laravel app, through WordPlus Cloud, with the broadcasting built into Laravel. wordplus/realtime-php brings:
- A
wordplusbroadcasting driver.routes/channels.phpdeclares your rooms and who may join them, with what each keeps and allows as a third argument, andbroadcast( new OrderUpdated( $order ) )sends to the event rooms, sealed. - The browser routes:
POST /wordplus/realtime/auth,/callsand/keys, on thewebmiddleware, for the session user, and/webhook. @wordplusRealtime: the page settings, with the signed-in user keys and the CSRF token.- The
Realtimefacade: calls, sending to users, asking about rooms, and keys.
The page joins rooms with @wordplus/realtime (emit, emitWithAck, volatile), not Laravel Echo, and you need neither Reverb nor Pusher. What goes through WordPlus Cloud is sealed with keys only your app and the browsers of your users hold. Ready-made parts come with it, all optional: the Better Messages chat, its list of who is online and its call screen. The example app is a whole shop built this way.
Laravel can run websockets of its own. Why WordPlus Cloud says what the cloud runs for you, and what it adds.
Try it live at laravel.docs.wordplus.cloud: the example app of this guide as a demo, a shop of your own for a day, with Ben and Cara opening in windows of their own.

1. Your app in WordPlus Cloud
In your WordPlus Cloud account, go to Apps → Add app. Name it, and list the addresses its pages are served from: https://shop.example.com, and http://localhost:8000 while you develop. Only pages on those addresses may connect.
You get the app key, public because every page carries it, and its secret, for your server only. Make a local key of your own too: everything sealed is sealed with keys derived from it, and it never leaves your servers.
BROADCAST_CONNECTION=wordplus
WORDPLUS_SITE=s1500000007
WORDPLUS_SECRET=…
WORDPLUS_LOCAL_KEY=… # openssl rand -hex 32; the same on every server of the app, kept as long as the app
2. Install
composer require wordplus/realtime-php # PHP 8.1 or later, with sodium
npm install @wordplus/realtime
Laravel finds the package by itself. Copy the browser files to public/ in your build, so every release serves the ones its package came with:
"scripts": { "build": "wordplus-realtime copy public && vite build" }
The worker lets the tabs of a user share one connection, the room client runs group calls, and realtime-ui.min.js is the ready-made parts.
Laravel 11 and later leave out config/broadcasting.php and routes/channels.php. Add the connection, and the channels file to bootstrap/app.php, without Reverb or Echo:
// config/broadcasting.php
return [
'default' => env( 'BROADCAST_CONNECTION', 'null' ),
'connections' => [
'wordplus' => [ 'driver' => 'wordplus' ],
'log' => [ 'driver' => 'log' ],
'null' => [ 'driver' => 'null' ],
],
];
// bootstrap/app.php
->withRouting(
web: __DIR__ . '/../routes/web.php',
channels: __DIR__ . '/../routes/channels.php',
// …
)
3. Your rooms
// routes/channels.php: the room of an order, orders.1042, for its customer, the last 20 updates kept.
Broadcast::channel( 'orders.{order}', fn ( User $user, Order $order ) => $user->id === $order->customer_id, [ 'history' => 20 ] );
Each channel is a room of the same name. A PrivateChannel( 'orders.1042' ) is the room orders.1042, and the third argument says what the room keeps and allows (rooms).
4. The page
<head>
@wordplusRealtime
@vite( 'resources/js/app.js' )
</head>
// resources/js/app.js
import { connect } from '@wordplus/realtime';
const rt = await connect( window.wordplusRealtimeConfig );
const order = rt.join( 'orders.1042' );
order.on( 'updated', ( data ) => showStatus( data.status ) );
order.on( 'error', ( e ) => console.warn( e.code ) ); // forbidden, no_rule …
@wordplusRealtime writes window.wordplusRealtimeConfig: where to connect, the routes with the CSRF token, and the signed-in user keys, so the page connects with no request first.
5. Send
class OrderUpdated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets;
public function __construct( public Order $order ) {}
public function broadcastOn(): array
{
return [ new PrivateChannel( 'orders.' . $this->order->id ) ];
}
public function broadcastAs(): string
{
return 'updated';
}
public function broadcastWith(): array
{
return [ 'status' => $this->order->status ];
}
}
$order->update( [ 'status' => 'shipped' ] );
broadcast( new OrderUpdated( $order ) );
Everyone in the room gets { status }, sealed with the room key on the way, and a page that reconnects later gets it from the room history.
The config
php artisan vendor:publish --tag=wordplus-realtime copies it to config/wordplus-realtime.php.
| Key | Default | |
|---|---|---|
site, secret, local_key | WORDPLUS_SITE, WORDPLUS_SECRET, WORDPLUS_LOCAL_KEY | |
routes.prefix | wordplus/realtime | where the browser routes are |
routes.middleware | ['web'] | how the routes know the user |
routes.enabled | true | false to register RealtimeController yourself |
worker, rooms | /realtime-worker.js, /realtime-rooms.min.js | where your app serves the files |
generations | WORDPLUS_REALTIME_GENERATIONS | the cache store for moving keys on |
seal | true | false sends everything plain |
broadcasting | wordplus | the connection whose routes/channels.php declares the rooms |
server, api | WordPlus Cloud | WORDPLUS_REALTIME_SERVER, WORDPLUS_REALTIME_API |
Next
- Rooms, presence and who is online, sending from your server and events from the page, with the ready-made chat
- Calls and group calls, with the call screen
- Encryption and your users
- Customizing the screens: their look, a piece of your own, or their whole code
- Deploying, and the example app
- The PHP reference, the browser reference, errors and limits