Skip to main content

Overview

Groups extend conversations to support multiple users with additional settings and permissions. They provide features like custom names, descriptions, avatars, and fine-grained control over member capabilities.

Group Types

WireChat supports two group types:
use Wirechat\Wirechat\Enums\GroupType;

enum GroupType: string
{
    case PRIVATE = 'private';
    case PUBLIC = 'public';
}

Private Groups

Invite-only groups with restricted access:
use Wirechat\Wirechat\Enums\GroupType;

$group = Group::create([
    'conversation_id' => $conversation->id,
    'name' => 'My Private Group',
    'type' => GroupType::PRIVATE,
]);

Public Groups

Groups that can be discovered and joined:
$group = Group::create([
    'conversation_id' => $conversation->id,
    'name' => 'Community Group',
    'type' => GroupType::PUBLIC,
]);

Database Structure

Groups have the following attributes:
/**
 * @property int $id
 * @property int $conversation_id
 * @property string|null $name
 * @property string|null $description
 * @property string|null $avatar_url
 * @property GroupType $type
 * @property bool $allow_members_to_send_messages
 * @property bool $allow_members_to_add_others
 * @property bool $allow_members_to_edit_group_info
 * @property int $admins_must_approve_new_members
 * @property \Illuminate\Support\Carbon|null $created_at
 * @property \Illuminate\Support\Carbon|null $updated_at
 */

Creating a Group

Groups are created alongside group conversations:
use Wirechat\Wirechat\Models\Conversation;
use Wirechat\Wirechat\Models\Group;
use Wirechat\Wirechat\Enums\ConversationType;
use Wirechat\Wirechat\Enums\GroupType;
use Wirechat\Wirechat\Enums\ParticipantRole;

// Create the conversation
$conversation = Conversation::create([
    'type' => ConversationType::GROUP,
]);

// Create the group
$group = Group::create([
    'conversation_id' => $conversation->id,
    'name' => 'Product Team',
    'description' => 'Discussions about product development',
    'type' => GroupType::PRIVATE,
]);

// Add the creator as owner
$conversation->addParticipant($user, ParticipantRole::OWNER);

Relationships

Conversation

Every group belongs to a conversation:
$conversation = $group->conversation;

// Access participants through conversation
$participants = $group->conversation->participants;

Cover Image

Groups can have a cover image:
// Check if group has cover
if ($group->cover) {
    echo $group->cover_url;
}

// The cover_url accessor
echo $group->cover_url; // Returns $group->cover?->url

Member Permissions

Groups provide granular control over member capabilities:

Allow Members to Send Messages

Control whether regular members can send messages:
if ($group->allowsMembersToSendMessages()) {
    // Members can send messages
} else {
    // Only admins can send messages
}
Implementation from source:
public function allowsMembersToSendMessages(): bool
{
    return $this->allow_members_to_send_messages == true;
}

Allow Members to Add Others

Control whether members can invite others:
if ($group->allowsMembersToAddOthers()) {
    // Members can invite others
} else {
    // Only admins can add members
}
Implementation:
public function allowsMembersToAddOthers(): bool
{
    return $this->allow_members_to_add_others == true;
}

Allow Members to Edit Group Info

Control whether members can edit group details:
if ($group->allowsMembersToEditGroupInfo()) {
    // Members can edit name, description, etc.
} else {
    // Only admins can edit group info
}
Implementation:
public function allowsMembersToEditGroupInfo(): bool
{
    return $this->allow_members_to_edit_group_info == true;
}

Admin Approval

Require admin approval for new members:
$group->update([
    'admins_must_approve_new_members' => true,
]);

if ($group->admins_must_approve_new_members) {
    // New members must be approved by admins
}

Ownership

Check if a user owns a group:
if ($group->isOwnedBy($user)) {
    echo "User is the group owner";
}
From the source code:
public function isOwnedBy(Model|Authenticatable $user): bool
{
    $conversation = $this->conversation;

    // Check if participants are already loaded
    if ($conversation->relationLoaded('participants')) {
        return $conversation->participants->contains(function ($participant) use ($user) {
            return $participant->participantable_id == $user->getKey() &&
                $participant->participantable_type == $user->getMorphClass() &&
                $participant->role == ParticipantRole::OWNER;
        });
    }

    // If not loaded, perform the query
    return $conversation->participants()
        ->where('participantable_id', $user->getKey())
        ->where('participantable_type', $user->getMorphClass())
        ->where('role', ParticipantRole::OWNER)
        ->exists();
}
The isOwnedBy() method efficiently checks loaded relationships first before querying the database.

Managing Members

Groups inherit participant management from their conversation:

Adding Members

use Wirechat\Wirechat\Enums\ParticipantRole;

// Add as regular member
$group->conversation->addParticipant($user, ParticipantRole::PARTICIPANT);

// Add as admin
$group->conversation->addParticipant($adminUser, ParticipantRole::ADMIN);

Removing Members

$participant = $group->conversation->participant($user);
$participant->removeByAdmin($adminUser);

Member Count

$memberCount = $group->conversation->participants()->count();

echo "This group has {$memberCount} members";

Cover Images

Groups use the polymorphic attachment system for cover images:
use Wirechat\Wirechat\Models\Attachment;

// Upload and attach cover
$attachment = Attachment::create([
    'attachable_type' => Group::class,
    'attachable_id' => $group->id,
    'file_path' => $path,
    'file_name' => $filename,
    'mime_type' => $mimeType,
]);

// Access cover URL
echo $group->cover_url;
The cover relationship:
public function cover(): MorphOne
{
    return $this->morphOne(Attachment::class, 'attachable');
}

public function getCoverUrlAttribute(): ?string
{
    return $this->cover?->url;
}

Cascade Deletion

When a group is deleted, its cover image is automatically deleted:
protected static function boot()
{
    parent::boot();

    static::deleted(function ($group) {
        if ($group->cover?->exists()) {
            $group->cover->delete();
        }
    });
}
Deleting a conversation will also delete its associated group and all participants.

Updating Group Settings

Update group information and permissions:
$group->update([
    'name' => 'Updated Group Name',
    'description' => 'New description',
    'allow_members_to_send_messages' => false,
    'allow_members_to_add_others' => false,
    'allow_members_to_edit_group_info' => false,
    'admins_must_approve_new_members' => true,
]);
Always check if a user has permission to edit group settings before allowing updates.

Permission Checking

Check user permissions within a group:
function canEditGroup($user, $group): bool
{
    $participant = $group->conversation->participant($user);
    
    if (!$participant) {
        return false;
    }
    
    // Admins can always edit
    if ($participant->isAdmin()) {
        return true;
    }
    
    // Check if members are allowed to edit
    return $group->allowsMembersToEditGroupInfo();
}

function canInviteMembers($user, $group): bool
{
    $participant = $group->conversation->participant($user);
    
    if (!$participant) {
        return false;
    }
    
    if ($participant->isAdmin()) {
        return true;
    }
    
    return $group->allowsMembersToAddOthers();
}

Best Practices

  • Always create a GROUP type conversation before creating a group
  • Set sensible defaults for permissions based on your use case
  • Use isOwnedBy() to check ownership before allowing destructive actions
  • Validate permissions before allowing users to modify group settings
  • Load the conversation relationship when accessing participants
  • Consider using admins_must_approve_new_members for private communities

Common Patterns

Complete Group Creation

use Illuminate\Support\Facades\DB;

function createGroup($creator, $name, $description): Group
{
    return DB::transaction(function () use ($creator, $name, $description) {
        // Create conversation
        $conversation = Conversation::create([
            'type' => ConversationType::GROUP,
        ]);
        
        // Create group
        $group = Group::create([
            'conversation_id' => $conversation->id,
            'name' => $name,
            'description' => $description,
            'type' => GroupType::PRIVATE,
            'allow_members_to_send_messages' => true,
            'allow_members_to_add_others' => false,
            'allow_members_to_edit_group_info' => false,
        ]);
        
        // Add creator as owner
        $conversation->addParticipant($creator, ParticipantRole::OWNER);
        
        return $group;
    });
}

Permission-Based Message Sending

function canSendMessageToGroup($user, $group): bool
{
    $participant = $group->conversation->participant($user);
    
    if (!$participant) {
        return false;
    }
    
    if ($participant->hasExited() || $participant->isRemovedByAdmin()) {
        return false;
    }
    
    // Admins can always send
    if ($participant->isAdmin()) {
        return true;
    }
    
    // Check group settings
    return $group->allowsMembersToSendMessages();
}

Restrict Admin-Only Groups

function createAdminOnlyGroup($owner, $name): Group
{
    $conversation = Conversation::create([
        'type' => ConversationType::GROUP,
    ]);
    
    $group = Group::create([
        'conversation_id' => $conversation->id,
        'name' => $name,
        'type' => GroupType::PRIVATE,
        'allow_members_to_send_messages' => false, // Only admins
        'allow_members_to_add_others' => false,
        'allow_members_to_edit_group_info' => false,
        'admins_must_approve_new_members' => true,
    ]);
    
    $conversation->addParticipant($owner, ParticipantRole::OWNER);
    
    return $group;
}

Next Steps

Participants

Learn about participant roles and management

Messages

Understand how messages work in groups