Skip to main content

Overview

WireChat is built with extensibility in mind. You can extend models, add custom traits, override Livewire components, and modify panel behavior to fit your application’s needs.

Extending Models

Using InteractsWithWirechat Trait

Add the InteractsWithWirechat trait to your User model:
app/Models/User.php
namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Wirechat\Wirechat\Traits\InteractsWithWirechat;

class User extends Authenticatable
{
    use InteractsWithWirechat;
    
    // Customize WireChat attributes
    protected $appends = [
        'wirechat_name',
        'wirechat_avatar_url',
        'wirechat_profile_url',
    ];
    
    public function getWirechatNameAttribute(): string
    {
        return $this->full_name ?? $this->name;
    }
    
    public function getWirechatAvatarUrlAttribute(): ?string
    {
        return $this->avatar_url ?? "https://ui-avatars.com/api/?name={$this->name}";
    }
    
    public function getWirechatProfileUrlAttribute(): ?string
    {
        return route('profile.show', $this);
    }
}
The InteractsWithWirechat trait provides all essential methods for conversations, messages, and participant management.

Available Traits

InteractsWithWirechat

The main trait providing chat functionality:
src/Traits/InteractsWithWirechat.php
trait InteractsWithWirechat
{
    // Relationships
    public function conversations();
    
    // Conversation management
    public function createConversationWith(Model $peer, ?string $message = null);
    public function createGroup(string $name, ?string $description = null);
    public function exitConversation(Conversation $conversation);
    public function deleteConversation(Conversation $conversation);
    public function clearConversation(Conversation $conversation);
    
    // Messaging
    public function sendMessageTo(Model $model, string $message);
    
    // Utilities
    public function belongsToConversation(Conversation $conversation);
    public function hasConversationWith(Model $user);
    public function getUnreadCount(?Conversation $conversation = null);
    
    // Roles
    public function isAdminIn(Group|Conversation $entity);
    public function isOwnerOf(Group|Conversation $entity);
}

Actionable

Track actions performed on models:
src/Traits/Actionable.php
trait Actionable
{
    public function actions(): MorphMany
    {
        return $this->morphMany(Action::class, 'actionable');
    }
}
Usage:
use Wirechat\Wirechat\Traits\Actionable;

class Message extends Model
{
    use Actionable;
}

// Track actions on messages
$message->actions()->create([
    'actor_type' => User::class,
    'actor_id' => $user->id,
    'action' => 'edited',
    'metadata' => ['old_body' => $oldBody],
]);

Actor

Track actions performed by models:
src/Traits/Actor.php
trait Actor
{
    public function performedActions()
    {
        return $this->morphMany(Action::class, 'actor');
    }
}
Usage:
use Wirechat\Wirechat\Traits\Actor;

class User extends Model
{
    use Actor;
}

// Get all actions performed by user
$user->performedActions()->get();

Custom Panel Configuration

Creating a Custom Panel

Extend the base Panel class:
app/Panels/CustomPanel.php
namespace App\Panels;

use Wirechat\Wirechat\Panel as BasePanel;
use Closure;

class CustomPanel extends BasePanel
{
    protected bool|Closure $enableAIModeration = false;
    protected array $customFeatures = [];
    
    public function aiModeration(bool|Closure $condition = true): static
    {
        $this->enableAIModeration = $condition;
        return $this;
    }
    
    public function hasAIModeration(): bool
    {
        return (bool) $this->evaluate($this->enableAIModeration);
    }
    
    public function features(array $features): static
    {
        $this->customFeatures = $features;
        return $this;
    }
    
    public function getFeatures(): array
    {
        return $this->customFeatures;
    }
}

Using Custom Panel

app/Providers/CustomPanelProvider.php
namespace App\Providers;

use Wirechat\Wirechat\PanelProvider;
use App\Panels\CustomPanel;

class CustomPanelProvider extends PanelProvider
{
    public function panel(CustomPanel $panel): CustomPanel
    {
        return $panel
            ->id('custom')
            ->default()
            ->aiModeration(true)
            ->features([
                'voice-messages' => true,
                'video-calls' => true,
                'screen-share' => false,
            ]);
    }
}
Use the EvaluatesClosures trait (included in Panel) to support both static values and closures in your custom methods.

Panel Concerns Pattern

WireChat uses the “Concerns” pattern to organize panel functionality. You can create custom concerns:
app/Panels/Concerns/HasVoiceMessages.php
namespace App\Panels\Concerns;

use Closure;

trait HasVoiceMessages
{
    protected bool|Closure $allowVoiceMessages = true;
    protected int|Closure $maxVoiceDuration = 300; // seconds
    
    public function voiceMessages(bool|Closure $condition = true): static
    {
        $this->allowVoiceMessages = $condition;
        return $this;
    }
    
    public function hasVoiceMessages(): bool
    {
        return (bool) $this->evaluate($this->allowVoiceMessages);
    }
    
    public function maxVoiceDuration(int|Closure $seconds): static
    {
        $this->maxVoiceDuration = $seconds;
        return $this;
    }
    
    public function getMaxVoiceDuration(): int
    {
        return (int) $this->evaluate($this->maxVoiceDuration);
    }
}
Use in Panel:
use App\Panels\Concerns\HasVoiceMessages;

class CustomPanel extends Panel
{
    use HasVoiceMessages;
}

Extending Livewire Components

Override Component Views

Publish WireChat views:
php artisan vendor:publish --tag=wirechat-views
Modify views in resources/views/vendor/wirechat/:
resources/views/vendor/wirechat/livewire/chat/chat.blade.php
<div class="custom-chat-container">
    {{-- Your custom layout --}}
    <x-wirechat::custom-header :conversation="$conversation" />
    
    {{-- Original content --}}
    @include('wirechat::components.messages')
    
    <x-wirechat::custom-footer />
</div>

Extend Livewire Components

Create a custom component extending WireChat’s:
app/Livewire/CustomChat.php
namespace App\Livewire;

use Wirechat\Wirechat\Livewire\Chat\Chat as BaseChat;

class CustomChat extends BaseChat
{
    public bool $enableMarkdown = true;
    
    public function sendMessage()
    {
        // Custom pre-processing
        if ($this->enableMarkdown) {
            $this->body = $this->parseMarkdown($this->body);
        }
        
        // Call parent method
        parent::sendMessage();
        
        // Custom post-processing
        $this->dispatch('message-sent');
    }
    
    protected function parseMarkdown(string $text): string
    {
        // Your markdown parsing logic
        return $text;
    }
}

Custom Middleware

Panel Middleware

Add custom middleware to panels:
app/Http/Middleware/TrackChatActivity.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Cache;

class TrackChatActivity
{
    public function handle($request, Closure $next)
    {
        $user = $request->user();
        
        if ($user) {
            Cache::put(
                "chat:active:{$user->id}",
                now(),
                now()->addMinutes(5)
            );
        }
        
        return $next($request);
    }
}
Apply to Panel:
return $panel
    ->id('app')
    ->middleware([
        'auth',
        \App\Http\Middleware\TrackChatActivity::class,
    ]);

Chat-Specific Middleware

Add middleware for conversation routes:
app/Http/Middleware/VerifyConversationAccess.php
namespace App\Http\Middleware;

use Closure;
use Wirechat\Wirechat\Models\Conversation;

class VerifyConversationAccess
{
    public function handle($request, Closure $next)
    {
        $conversationId = $request->route('conversation');
        $user = $request->user();
        
        if (!$user->belongsToConversation(
            Conversation::find($conversationId)
        )) {
            abort(403);
        }
        
        return $next($request);
    }
}
Apply to Chat Routes:
return $panel
    ->id('app')
    ->chatMiddleware([
        \App\Http\Middleware\VerifyConversationAccess::class,
    ]);
Chat middleware always includes belongsToConversation as the first middleware. Your custom middleware will be appended.

Custom Events

Create custom events following WireChat’s pattern:
app/Events/MessageReaction.php
namespace App\Events;

use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Wirechat\Wirechat\Models\Message;
use Wirechat\Wirechat\Traits\InteractsWithPanel;

class MessageReaction implements ShouldBroadcastNow
{
    use InteractsWithPanel;
    
    public function __construct(
        public Message $message,
        public string $reaction,
        public $user,
        ?string $panel = null
    ) {
        $this->resolvePanel($panel);
    }
    
    public function broadcastOn(): array
    {
        $panelId = $this->getPanel()->getId();
        return [
            new PrivateChannel(
                "$panelId.conversation.{$this->message->conversation_id}"
            ),
        ];
    }
    
    public function broadcastWith(): array
    {
        return [
            'message_id' => $this->message->id,
            'reaction' => $this->reaction,
            'user_id' => $this->user->id,
        ];
    }
}

Database Customization

Custom Table Prefix

Set a custom prefix in config/wirechat.php:
config/wirechat.php
return [
    'table_prefix' => 'chat_',
];
Table prefix is only used during initial migrations and cannot be changed after installation.

UUID Primary Keys

Enable UUID for conversations:
config/wirechat.php
return [
    'uses_uuid_for_conversations' => true,
];

Extend Models

Create custom models extending WireChat’s:
app/Models/CustomConversation.php
namespace App\Models;

use Wirechat\Wirechat\Models\Conversation as BaseConversation;

class CustomConversation extends BaseConversation
{
    protected $appends = ['participant_count'];
    
    public function getParticipantCountAttribute(): int
    {
        return $this->participants()->count();
    }
    
    public function archive(): void
    {
        $this->update(['archived_at' => now()]);
    }
    
    public function scopeActive($query)
    {
        return $query->whereNull('archived_at');
    }
}

Storage Customization

Configure custom storage for attachments:
config/wirechat.php
return [
    'storage' => [
        'disk' => 's3',
        'visibility' => 'private',
        'directories' => [
            'attachments' => 'chat/attachments',
        ],
    ],
];
Access storage configuration:
use Wirechat\Wirechat\Facades\Wirechat;

$disk = Wirechat::storage()->disk();
$directory = Wirechat::storage()->attachmentsDirectory();
$visibility = Wirechat::storage()->visibility();

Advanced Panel Methods

use Wirechat\Wirechat\Panel\Concerns\HasAuth;

trait HasAuth
{
    protected bool|Closure $canCreateChats = true;
    protected bool|Closure $canCreateGroups = true;
    
    public function authorizeChats(bool|Closure $condition): static
    {
        $this->canCreateChats = $condition;
        return $this;
    }
    
    public function authorizeGroups(bool|Closure $condition): static
    {
        $this->canCreateGroups = $condition;
        return $this;
    }
}

// Usage
return $panel
    ->authorizeChats(fn() => auth()->user()->isPremium())
    ->authorizeGroups(fn() => auth()->user()->can('create-groups'));
use Wirechat\Wirechat\Panel\Concerns\HasRoutes;

trait HasRoutes
{
    protected string|Closure $path = 'chat';
    
    public function path(string|Closure $path): static
    {
        $this->path = $path;
        return $this;
    }
    
    public function getPath(): string
    {
        return $this->evaluate($this->path);
    }
    
    public function chatRoute($conversationId): string
    {
        return url($this->getPath() . '/' . $conversationId);
    }
}
public function panel(Panel $panel): Panel
{
    return $panel
        ->id('app')
        ->groups(fn() => config('features.chat.groups'))
        ->attachments(fn() => config('features.chat.attachments'))
        ->emojiPicker(fn() => config('features.chat.emojis'));
}

Helper Methods

Create Conversations Programmatically

use App\Models\User;

$user = User::find(1);
$recipient = User::find(2);

// Create private conversation
$conversation = $user->createConversationWith(
    $recipient,
    message: 'Hello!'
);

// Create group
$group = $user->createGroup(
    name: 'Team Discussion',
    description: 'Weekly team sync'
);

Send Messages

$user->sendMessageTo($recipient, 'Hello!');

$user->sendMessageTo($conversation, 'Group message');

Check Permissions

$isOwner = $user->isOwnerOf($conversation);
$isAdmin = $user->isAdminIn($conversation);
$isMember = $user->belongsToConversation($conversation);

Testing Extensions

tests/Feature/CustomPanelTest.php
use Tests\TestCase;
use App\Panels\CustomPanel;

class CustomPanelTest extends TestCase
{
    public function test_custom_panel_features()
    {
        $panel = CustomPanel::make()
            ->id('test')
            ->aiModeration(true)
            ->features(['voice-messages' => true]);
        
        $this->assertTrue($panel->hasAIModeration());
        $this->assertTrue($panel->getFeatures()['voice-messages']);
    }
}

Next Steps

Events

Learn about WireChat events

Broadcasting

Configure real-time features