PHPackages                             finller/laravel-conversations - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. finller/laravel-conversations

Abandoned → [elegantly/laravel-conversations](/?search=elegantly%2Flaravel-conversations)Library[Utility &amp; Helpers](/categories/utility)

finller/laravel-conversations
=============================

Attach chat to any model

v2.0.3(3w ago)19.9kMITPHPPHP ^8.1

Since Aug 17Pushed 3w agoCompare

[ Source](https://github.com/ElegantEngineeringTech/laravel-conversations)[ Packagist](https://packagist.org/packages/finller/laravel-conversations)[ Docs](https://github.com/ElegantEngineeringTech/laravel-conversations)[ RSS](/packages/finller-laravel-conversations/feed)WikiDiscussions main Synced yesterday

READMEChangelog (10)Dependencies (48)Versions (42)Used By (0)

Attach a chat to any model with granular read tracking
======================================================

[](#attach-a-chat-to-any-model-with-granular-read-tracking)

[![Latest Version on Packagist](https://camo.githubusercontent.com/d328dbc7a7bd9f5e78836ea3cf8886fe8ab13e2b9ac525af8320f92c90a3e190/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f656c6567616e746c792f6c61726176656c2d636f6e766572736174696f6e732e737667)](https://packagist.org/packages/elegantly/laravel-conversations)[![Total Downloads](https://camo.githubusercontent.com/51400b18c230814433fdf076db9627492c3db707fcc7053f6fb7ae42604b4a69/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f656c6567616e746c792f6c61726176656c2d636f6e766572736174696f6e732e737667)](https://packagist.org/packages/elegantly/laravel-conversations)[![Tests](https://github.com/ElegantEngineeringTech/laravel-conversations/actions/workflows/run-tests.yml/badge.svg)](https://github.com/ElegantEngineeringTech/laravel-conversations/actions/workflows/run-tests.yml)[![Laravel Pint](https://github.com/ElegantEngineeringTech/laravel-conversations/actions/workflows/pint.yml/badge.svg)](https://github.com/ElegantEngineeringTech/laravel-conversations/actions/workflows/pint.yml)[![PHPStan](https://github.com/ElegantEngineeringTech/laravel-conversations/actions/workflows/phpstan.yml/badge.svg)](https://github.com/ElegantEngineeringTech/laravel-conversations/actions/workflows/phpstan.yml)

This package gives your Laravel app a lightweight, backend-only chat system. It supports multiple users per conversation, per-message read tracking, pivot-level read pointers, and optional model attachment via polymorphic relations.

Features
--------

[](#features)

- **Multiple participants** per conversation
- **Per-message read receipts** (`MessageRead` model)
- **Fast unread queries** via denormalized `last_read_message_id` on the pivot
- **Mute and archive** conversations per user
- **Attach conversations** to any Eloquent model (e.g. a `Mission`, `Order`, or `Project`)
- **Widget messages** for rich content (e.g. custom Livewire/Vue components)
- **Markdown helper** with inline-only parsing, autolinks, and safe external links

Requirements
------------

[](#requirements)

- PHP ^8.1
- Laravel 11, 12, or 13

Installation
------------

[](#installation)

```
composer require elegantly/laravel-conversations
```

Publish and run migrations:

```
php artisan vendor:publish --tag="conversations-migrations"
php artisan migrate
```

Publish the config (optional):

```
php artisan vendor:publish --tag="conversations-config"
```

Setup
-----

[](#setup)

### 1. Let users participate in conversations

[](#1-let-users-participate-in-conversations)

Add the `ParticipateToConversations` trait to your `User` model:

```
use Elegantly\Conversation\Concerns\ParticipateToConversations;

class User extends Authenticatable
{
    use ParticipateToConversations;
}
```

### 2. Attach a conversation to another model (optional)

[](#2-attach-a-conversation-to-another-model-optional)

If you want a conversation tied to, say, a `Mission` or `Project`, use `HasConversation`:

```
use Elegantly\Conversation\Concerns\HasConversation;

class Mission extends Model
{
    use HasConversation;
}
```

Usage
-----

[](#usage)

### Create a conversation

[](#create-a-conversation)

```
use Elegantly\Conversation\Conversation;

$conversation = new Conversation();
$conversation->save();

// Attach participants
$conversation->users()->sync([$user->id, $user2->id]);

// Optional: attach to a parent model
$conversation->conversationable()->associate($mission);
$conversation->save();

// Optional: set an owner
$conversation->owner()->associate($admin);
$conversation->save();
```

### Send a message

[](#send-a-message)

```
use Elegantly\Conversation\Message;

$message = new Message([
    'content' => 'Hey team, the deployment is live!',
]);

$message->user()->associate(auth()->user());

$conversation->send($message);
```

When you call `send()`, the package will:

1. Save the message
2. Update the conversation’s `latest_message_id` and `messaged_at`
3. Automatically mark the message as read for the sender

### Read status

[](#read-status)

#### Mark a message as read

[](#mark-a-message-as-read)

```
$message->markAsReadBy($user);

// Or force-update the read timestamp
$message->markAsReadBy($user, force: true);
```

#### Mark a message as unread

[](#mark-a-message-as-unread)

```
$message->markAsUnreadBy($user);
```

#### Check read status

[](#check-read-status)

```
$message->isReadBy($user);
$message->isNotReadBy($user);
$message->isReadByAnyone();
$message->isReadByAll([$user1, $user2]);

// Read timestamp
$message->getReadByAt($user);
```

#### Mark everything as read via the pivot (fast denormalized pointer)

[](#mark-everything-as-read-via-the-pivot-fast-denormalized-pointer)

```
$conversation
    ->getConversationUser($user)
    ?->markAsRead($message);
```

This updates the `conversation_user.last_read_message_id` column, which makes unread-conversation queries extremely fast.

### Query unread / read conversations

[](#query-unread--read-conversations)

#### Via Conversation scopes (checks `MessageRead` rows)

[](#via-conversation-scopes-checks-messageread-rows)

```
use Elegantly\Conversation\Conversation;

// Conversations with unread messages for a user
Conversation::query()->unreadBy($user)->get();

// Conversations fully read by a user
Conversation::query()->readBy($user)->get();
```

#### Via the User relationship (denormalized pivot)

[](#via-the-user-relationship-denormalized-pivot)

```
// Fast unread queries using the pivot column
$user->denormalizedUnreadConversations()->get();

$user->denormalizedReadConversations()->get();
```

### Query unread / read messages

[](#query-unread--read-messages)

```
// Messages inside a conversation
$conversation->messages()->unreadBy($user)->get();
$conversation->messages()->readBy($user)->get();

// Sent by a specific user
$conversation->messages()->byUser($user)->get();

// Not sent by a specific user
$conversation->messages()->notByUser($user)->get();
```

### Mute and archive conversations

[](#mute-and-archive-conversations)

Each participant can mute or archive a conversation via the pivot:

```
// Mute
$conversation->users()->updateExistingPivot($user->id, ['muted_at' => now()]);

// Unmute
$conversation->users()->updateExistingPivot($user->id, ['muted_at' => null]);

// Archive
$conversation->users()->updateExistingPivot($user->id, ['archived_at' => now()]);

// Unarchive
$conversation->users()->updateExistingPivot($user->id, ['archived_at' => null]);
```

Convenience accessors on the user:

```
$user->conversationsNotMuted()->get();
$user->conversationsMuted()->get();
$user->conversationsNotArchived()->get();
$user->conversationsArchived()->get();
```

### Widget messages

[](#widget-messages)

Sometimes a message is not plain text but a UI component. You can store a widget payload:

```
$message = new Message();
$message->user()->associate($user);
$message->setWidget('invoice-widget', [
    'invoice_id' => 123,
    'total' => 499.00,
]);

$conversation->send($message);
```

Helpers on the message:

```
$message->hasWidget();                 // true
$message->getWidgetComponent();      // 'invoice-widget'
$message->getWidgetProps();            // ['invoice_id' => 123, 'total' => 499.0, 'message' => $message]
```

### Markdown helper

[](#markdown-helper)

Messages can be rendered as safe inline Markdown:

```
// On an existing message
$message->toMarkdown();

// Or manually
Message::markdown($rawString);
```

This uses `league/commonmark` with inline-only, autolinks, and safe external links.

Configuration
-------------

[](#configuration)

```
return [
    'model_user' => User::class,
    'model_message' => Message::class,
    'model_conversation' => Conversation::class,
    'model_conversation_user' => ConversationUser::class,
    'model_read' => MessageRead::class,

    // When a User is deleted, also delete his messages
    'cascade_user_delete_to_messages' => false,

    // When a Conversation is deleted, also delete its messages
    'cascade_conversation_delete_to_messages' => false,

    // When the parent model is deleted, also delete the conversation
    'cascade_conversationable_delete_to_conversation' => false,

    'markdown' => [
        'environment' => [
            'allow_unsafe_links' => false,
            'html_input' => 'strip',
            'external_link' => [
                'internal_hosts' => env('APP_URL', '') ?: null,
                'open_in_new_window' => true,
                'html_class' => '',
                'nofollow' => 'external',
                'noopener' => 'external',
                'noreferrer' => 'external',
            ],
        ],
    ],
];
```

Custom models
-------------

[](#custom-models)

You can extend any model and override it in the config. For example, if you need extra casts or methods on `Message`:

```
namespace App\Models;

use Elegantly\Conversation\Message as BaseMessage;

class Message extends BaseMessage
{
    protected static function booted(): void
    {
        parent::booted();
        // your logic
    }
}
```

Then update `config/conversations.php`:

```
'model_message' => App\Models\Message::class,
```

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](https://github.com/quentinGab/.github/blob/main/CONTRIBUTING.md) for details.

Security
--------

[](#security)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Quentin Gabriele](https://github.com/quentinGab)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

52

—

FairBetter than 96% of packages

Maintenance95

Actively maintained with recent releases

Popularity22

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity68

Established project with proven stability

 Bus Factor1

Top contributor holds 79.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~41 days

Recently: every ~18 days

Total

35

Last Release

21d ago

Major Versions

v0.5.3 → 1.0.02025-02-05

v1.3.0 → v2.0.02026-05-30

### Community

Maintainers

![](https://www.gravatar.com/avatar/d769563728757858e249f266ac2555f28db5640124df92821ed17d93ee76fc7b?d=identicon)[QuentinGab](/maintainers/QuentinGab)

---

Top Contributors

[![QuentinGab](https://avatars.githubusercontent.com/u/40128136?v=4)](https://github.com/QuentinGab "QuentinGab (114 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (26 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (3 commits)")

---

Tags

laravelchatElegantlylaravel-conversations

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/finller-laravel-conversations/health.svg)

```
[![Health](https://phpackages.com/badges/finller-laravel-conversations/health.svg)](https://phpackages.com/packages/finller-laravel-conversations)
```

###  Alternatives

[spatie/laravel-pdf

Create PDFs in Laravel apps

1.0k4.8M47](/packages/spatie-laravel-pdf)[codewithdennis/filament-select-tree

The multi-level select field enables you to make single selections from a predefined list of options that are organized into multiple levels or depths.

329530.5k29](/packages/codewithdennis-filament-select-tree)[worksome/exchange

Check Exchange Rates for any currency in Laravel.

124603.0k](/packages/worksome-exchange)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3914.6k](/packages/rawilk-profile-filament-plugin)[elegantly/laravel-translator

All on one translations management for Laravel

6333.1k](/packages/elegantly-laravel-translator)[finller/laravel-media

A flexible media library for Laravel

472.1k](/packages/finller-laravel-media)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
