---
title: "Models & queries — Messages for Laravel | Roundly"
description: "The Thread, Message and Participant models — query scopes, relations, helpers, paginated listings and swapping in your own subclasses."
url: https://roundly-consulting.com/open-source/docs/messages-for-laravel/models-queries
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Messages for Laravel](https://roundly-consulting.com/open-source/docs/messages-for-laravel.md)

# Models & queries

## Query scopes

```php
use RoundlyConsulting\Messages\Models\Message;
use RoundlyConsulting\Messages\Models\Participant;
use RoundlyConsulting\Messages\Models\Thread;

Thread::query()->direct();
Thread::query()->forParticipant($user);
Thread::query()->between($alice, $bob);
Thread::query()->inboxFor($user);
Thread::query()->visibleTo($user);
Message::query()->unreadFor($user);
Participant::query()->unread();            // never marked the thread read
Participant::query()->readUpTo($message);  // read pointer at or past $message
```

- direct() — direct threads only.
- forParticipant($model) — the model’s threads, newest activity first.
- between($a, $b) — the direct thread of exactly these two models.
- inboxFor($model) — the inbox query with eager loads and unread\_count.
- visibleTo(?$model) — public threads plus the model’s own; public only when null.
- unreadFor($model) — messages the model has not read, excluding its own.
- Participant::unread() — participants who have never read their thread.
- Participant::readUpTo($message) — participants whose read pointer is at or past the message (what seenBy() runs).

## Relations and attributes

```php
// Thread
$thread->participants;              // HasMany<Participant>
$thread->messages;                  // HasMany<Message>
$thread->latestMessage;             // BelongsTo<Message>, via last_message_id
$thread->notifiableParticipants($sender); // Notifiable participant models, minus the sender
$thread->last_activity_at;          // touched on every send, join and leave

// Message
$message->message;                  // the body column
$message->type;                     // MessageType::Text|System
$message->meta;                     // ?array — quote snapshot, system params, your own keys
$message->thread;                   // BelongsTo<Thread>
$message->sender;                   // MorphTo — null for system messages
$message->parent;                   // BelongsTo<Message> — the quoted message
$message->replies;                  // HasMany<Message>
$message->preview();                // truncated to preview.length, type-aware

// Participant
$participant->participant;          // MorphTo — the User, Company, …
$participant->thread;               // BelongsTo<Thread>
$participant->role;                 // ?ParticipantRole
$participant->last_read_message_id; // the read pointer — the message they had reached
$participant->read_at;              // ?Carbon — when they last read
```

All three models use soft deletes and live in messaging\_threads, messaging\_messages and messaging\_participants.

## Swapping a model

The models are not final — extend one and point its models.\* key at your subclass:

```php
use RoundlyConsulting\Messages\Models\Message;

class ChatMessage extends Message
{
    // your own casts, relations and methods
}

// config/messages.php
'models' => [
    'message' => App\Models\ChatMessage::class,
    'thread' => RoundlyConsulting\Messages\Models\Thread::class,
    'participant' => RoundlyConsulting\Messages\Models\Participant::class,
],
```

The configured classes are used at every call site — relations, actions, scopes, the manager and the prune command. Absent or blank config resolves the packaged model; any other value must be that model or a subclass of it, or it throws InvalidConfigurationException naming the key — a foreign class is never silently replaced.

## Paginated listings

The facade paginates threads and a thread’s messages with the right eager loads and a deterministic order:

```php
use RoundlyConsulting\Messages\Facades\Messages;

Messages::threads($user, perPage: 20);              // $user's threads + every public thread
Messages::threads();                                // public threads only
Messages::thread($thread)->messages(perPage: 25);   // newest first, senders eager loaded
```

threads() lists newest activity first with the latest message and its sender eager-loaded; it runs the visibleTo() scope. Both default to 15 per page and accept page and pageName.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/messages-for-laravel.md)
