Overview
WireChat supports multiple independent chat panels in the same application. Each panel can have its own configuration, authentication guards, middleware, routes, and styling.
Use Cases
Multi-tenant applications - Separate chat panels per tenant
User & Admin chats - Different panels for customers and staff
Department isolation - Sales, support, and internal communications
Testing environments - Separate staging and production panels
Panel Registry
The PanelRegistry manages all registered panels and tracks the current active panel:
class PanelRegistry
{
protected array $panels = [];
protected ? Panel $defaultPanel = null ;
protected ? Panel $currentPanel = null ;
public function register ( Panel $panel ) : void
{
$id = $panel -> getId ();
// Skip if already registered
if ( isset ( $this -> panels [ $id ])) {
return ;
}
$this -> panels [ $id ] = $panel ;
if ( $panel -> isDefault ()) {
$this -> defaultPanel = $panel ;
}
$panel -> register ();
}
}
Each panel must have a unique ID. Attempting to register a duplicate panel ID will be silently skipped.
Creating Multiple Panels
Step 1: Create Panel Providers
Create separate panel providers for each panel:
php artisan make:provider CustomerChatPanelProvider
php artisan make:provider AdminChatPanelProvider
app/Providers/CustomerChatPanelProvider.php
namespace App\Providers ;
use Wirechat\Wirechat\ Panel ;
use Wirechat\Wirechat\ PanelProvider ;
class CustomerChatPanelProvider extends PanelProvider
{
public function panel ( Panel $panel ) : Panel
{
return $panel
-> id ( 'customer' )
-> default () // This is the default panel
-> path ( 'chat' )
-> authGuards ([ 'web' ])
-> middleware ([ 'auth' , 'verified' ])
-> colors ([
'primary' => '#3b82f6' ,
]);
}
}
app/Providers/AdminChatPanelProvider.php
namespace App\Providers ;
use Wirechat\Wirechat\ Panel ;
use Wirechat\Wirechat\ PanelProvider ;
class AdminChatPanelProvider extends PanelProvider
{
public function panel ( Panel $panel ) : Panel
{
return $panel
-> id ( 'admin' )
-> path ( 'admin/chat' )
-> authGuards ([ 'admin' ])
-> middleware ([ 'auth:admin' , 'can:access-admin-chat' ])
-> colors ([
'primary' => '#dc2626' ,
])
-> messagesQueue ( 'admin-messages' )
-> eventsQueue ( 'admin-events' );
}
}
Step 3: Register Providers
'providers' => [
// ...
App\Providers\ CustomerChatPanelProvider :: class ,
App\Providers\ AdminChatPanelProvider :: class ,
],
Default Panel
One panel must be marked as default:
return $panel
-> id ( 'customer' )
-> default (); // Mark as default
Only one panel can be marked as default. If multiple panels call ->default(), an exception will be thrown.
Accessing the Default Panel
use Wirechat\Wirechat\Facades\ Wirechat ;
$defaultPanel = Wirechat :: getDefaultPanel ();
Accessing Panels
Get Panel by ID
use Wirechat\Wirechat\Facades\ Wirechat ;
$customerPanel = Wirechat :: getPanel ( 'customer' );
$adminPanel = Wirechat :: getPanel ( 'admin' );
Get Current Panel
$currentPanel = Wirechat :: getCurrentPanel ();
Get All Panels
$panels = app ( PanelRegistry :: class ) -> all ();
foreach ( $panels as $panel ) {
echo $panel -> getId ();
}
Panel-Specific Configuration
Different Routes
Each panel can have its own route prefix:
// Customer panel: /chat
$customerPanel -> path ( 'chat' );
// Admin panel: /admin/chat
$adminPanel -> path ( 'admin/chat' );
Different Guards
Use separate authentication guards:
// Customer panel uses web guard
$customerPanel -> authGuards ([ 'web' ]);
// Admin panel uses admin guard
$adminPanel -> authGuards ([ 'admin' ]);
Different Middleware
Apply panel-specific middleware:
$customerPanel -> middleware ([ 'auth' , 'verified' ]);
$adminPanel -> middleware ([ 'auth:admin' , 'can:access-admin-chat' ]);
Different Colors
Customize colors per panel:
$customerPanel -> colors ([
'primary' => '#3b82f6' , // Blue for customers
]);
$adminPanel -> colors ([
'primary' => '#dc2626' , // Red for admins
]);
Broadcasting with Multiple Panels
Each panel creates its own broadcast channels:
// Customer panel channels:
// - customer.conversation.{id}
// - customer.participant.{type}.{id}
// Admin panel channels:
// - admin.conversation.{id}
// - admin.participant.{type}.{id}
Panel-Specific Events
When dispatching events, specify the panel:
use Wirechat\Wirechat\Events\ MessageCreated ;
// Dispatch to customer panel
event ( new MessageCreated ( $message , panel : 'customer' ));
// Dispatch to admin panel
event ( new MessageCreated ( $message , panel : 'admin' ));
InteractsWithPanel Trait
WireChat uses the InteractsWithPanel trait to resolve panels:
src/Traits/InteractsWithPanel.php
trait InteractsWithPanel
{
public ? string $panel ;
public function resolvePanel ( ? string $panel = null ) : void
{
if ( is_string ( $panel ) && filled ( $panel )) {
$this -> panel = Wirechat :: getPanel ( $panel ) -> getId ();
} else {
$this -> panel = Wirechat :: getDefaultPanel () -> getId ();
}
if ( ! $this -> panel ) {
throw NoPanelProvidedException :: make ();
}
}
public function getPanel () : ? Panel
{
return Wirechat :: getPanel ( $this -> panel );
}
}
Setting Current Panel
The current panel is set automatically based on the route, but you can set it manually:
use Wirechat\Wirechat\Facades\ Wirechat ;
Wirechat :: setCurrentPanel ( 'admin' );
$current = Wirechat :: getCurrentPanel ();
echo $current -> getId (); // 'admin'
Queue Configuration
Use separate queues for each panel to isolate workloads:
$customerPanel
-> messagesQueue ( 'customer-messages' )
-> eventsQueue ( 'customer-events' );
$adminPanel
-> messagesQueue ( 'admin-messages' )
-> eventsQueue ( 'admin-events' );
Run dedicated workers:
# Customer messages worker
php artisan queue:work --queue=customer-messages,customer-events
# Admin messages worker
php artisan queue:work --queue=admin-messages,admin-events
Separate queues prevent one panel’s heavy traffic from affecting other panels’ performance.
Middleware Patterns
// Middleware to ensure users only access their tenant's chat
namespace App\Http\Middleware ;
class EnsureTenantAccess
{
public function handle ( $request , Closure $next )
{
$tenantId = $request -> user () -> tenant_id ;
$panel = Wirechat :: getCurrentPanel ();
// Verify panel belongs to tenant
if ( $panel -> getId () !== "tenant-{ $tenantId }" ) {
abort ( 403 , 'Unauthorized panel access' );
}
return $next ( $request );
}
}
// Conditionally enable panels based on feature flags
public function panel ( Panel $panel ) : Panel
{
return $panel
-> id ( 'beta-chat' )
-> middleware ([
'auth' ,
function ( $request , $next ) {
if ( ! $request -> user () -> hasFeature ( 'beta-chat' )) {
abort ( 404 );
}
return $next ( $request );
},
]);
}
// Different panels for different roles
public function panel ( Panel $panel ) : Panel
{
return $panel
-> id ( 'manager-chat' )
-> authGuards ([ 'web' ])
-> middleware ([
'auth' ,
'role:manager' , // Using spatie/laravel-permission
]);
}
Dynamic Panel Registration
Register panels dynamically at runtime:
app/Providers/DynamicPanelProvider.php
namespace App\Providers ;
use Illuminate\Support\ ServiceProvider ;
use Wirechat\Wirechat\ Panel ;
use Wirechat\Wirechat\ PanelRegistry ;
use App\Models\ Tenant ;
class DynamicPanelProvider extends ServiceProvider
{
public function boot () : void
{
$registry = app ( PanelRegistry :: class );
// Register a panel for each tenant
Tenant :: all () -> each ( function ( $tenant ) use ( $registry ) {
$panel = Panel :: make ()
-> id ( "tenant-{ $tenant -> id }" )
-> path ( "tenants/{ $tenant -> slug }/chat" )
-> authGuards ([ 'web' ])
-> colors ( $tenant -> brand_colors );
$registry -> register ( $panel );
});
}
}
Dynamic registration can impact performance with many panels. Consider caching panel configurations.
Resolving Panels from Providers
The PanelRegistry can resolve panels from provider classes:
protected function resolvePanelFromProvider ( string $providerClass ) : ? Panel
{
if ( ! class_exists ( $providerClass )) {
return null ;
}
$reflection = new ReflectionClass ( $providerClass );
if ( $reflection -> isSubclassOf ( PanelProvider :: class ) &&
$reflection -> hasMethod ( 'panel' )) {
$provider = $reflection -> newInstanceWithoutConstructor ();
return $provider -> panel ( Panel :: make ());
}
return null ;
}
This allows lazy loading of panels:
// Panels are only instantiated when accessed
$panel = Wirechat :: getPanel ( CustomerChatPanelProvider :: class );
Testing Multiple Panels
tests/Feature/MultiplePanelsTest.php
use Tests\ TestCase ;
use Wirechat\Wirechat\Facades\ Wirechat ;
class MultiplePanelsTest extends TestCase
{
public function test_panels_are_isolated ()
{
$customerPanel = Wirechat :: getPanel ( 'customer' );
$adminPanel = Wirechat :: getPanel ( 'admin' );
$this -> assertEquals ( 'customer' , $customerPanel -> getId ());
$this -> assertEquals ( 'admin' , $adminPanel -> getId ());
$this -> assertNotEquals (
$customerPanel -> getPath (),
$adminPanel -> getPath ()
);
}
public function test_default_panel_is_set ()
{
$default = Wirechat :: getDefaultPanel ();
$this -> assertEquals ( 'customer' , $default -> getId ());
}
}
Troubleshooting
Panel Not Found
// NoPanelProvidedException
Solution: Ensure the panel provider is registered in config/app.php and at least one panel is marked as default.
Duplicate Panel ID
// Exception: Panel already registered with ID 'app'
Solution: Use unique IDs for each panel. Duplicate IDs are silently skipped by PanelRegistry::register().
Multiple Default Panels
// Exception: Only one panel can be marked as default
Solution: Only call ->default() on one panel.
Next Steps
Broadcasting Configure real-time updates
Extending Extend WireChat functionality