Editing & system messages
Messages::message() returns a handle for one message:
use RoundlyConsulting\Messages\Facades\Messages;
Messages::message($message)->edit('edited body', by: $alice);
Messages::message($message)->delete(by: $alice); // soft delete ("unsent")Only the author may edit a message — not even an owner or admin, and regardless of messages.permissions.enabled. Deleting is allowed to the author and, in a group thread with roles enforced, to owners and admins. Either way the actor must still be a participant of the thread. Leave by: out for trusted server-side calls.
Editing or deleting an already-deleted message throws a typed RoundlyConsulting\Messages\Exceptions\MessageException. Editing fires MessageEdited; deleting fires MessageDeleted. A message counts as edited only when its text really changed: a change of the body records meta.edited_at (ISO-8601), and MessageResource’s edited_at reads that — never updated_at, which an unsend and a restore stamp too. Messages edited before 1.1.0 carry no marker and report edited_at as null.
Scoped to a thread
When the thread and the message both come from the request, go through the thread — a message from another thread throws MessageException::notInThread():
// PATCH /threads/{thread}/messages/{message} — refuses a message of another thread
Messages::thread($thread)->message($message)->edit($request->body, by: $request->user());
Messages::thread($thread)->message($message)->delete(by: $request->user());What unsending does
- The message is soft-deleted — the row stays until a force delete or prune.
- Broadcasts and MessageResource hide the body (null), and preview() returns “This message was deleted.”
- The thread’s latest-message pointer moves to the previous surviving message.
- Attachment files are kept; only a force delete or prune removes them.
System messages
Enable system messages with messages.system-messages.enabled (MESSAGES_SYSTEM_MESSAGES). They are stored as a MessageType::System message with no sender, a translation key as the body and the parameters in the meta JSON column, so they render in any locale. With the switch on, the package writes them itself:
- a participant joins — messages::messages.system.participant_joined, with the participant’s participateAs() array under meta.participant;
- a participant leaves — messages::messages.system.participant_left, same meta;
- a thread is renamed — messages::messages.system.thread_renamed, with meta.name.
preview() renders them in the current locale. :participant reads as the stored snapshot’s name — or as “An unnamed participant” when it has none or the model does not implement ParticipatesInMessaging. Messages stored by 1.0.0–1.0.1 render the same way.
Write your own with the builder. preview() translates the key with the scalar meta values as replacements:
use RoundlyConsulting\Messages\Facades\Messages;
$message = Messages::to($thread)
->asSystem('messages::messages.system.thread_renamed', ['name' => 'Launch'])
->send(); // type System, no sender, body = the translation key, params in meta
$message->preview(); // "The conversation was renamed to Launch."Translation keys
English and Slovak ship with the package. Publish the strings with the messages-translations tag to reword them or add a language:
| Key | English default |
|---|---|
messages::messages.system.participant_joined | :participant joined the conversation. |
messages::messages.system.participant_left | :participant left the conversation. |
messages::messages.system.thread_renamed | The conversation was renamed to :name. |
messages::messages.system.unnamed_participant | An unnamed participant |
messages::messages.preview.deleted | This message was deleted. |
messages::messages.reply.cross-thread | A reply must target a message in the same thread. |
messages::messages.participation.not-a-participant | [:participant] is not a participant of this thread. |
messages::messages.participation.ownership-by-transfer | Ownership changes hands only through transferOwnership(). |
messages::messages.participation.owner-must-transfer | The owner must transferOwnership() before leaving the thread. |
messages::messages.participation.not-the-owner | [:participant] does not own this thread, so has no ownership to transfer. |
messages::messages.participation.direct-has-no-roles | A direct thread has no roles to change. |
messages::messages.participation.direct-is-closed | A direct thread takes no new participants; start a group thread to bring someone in. |
messages::messages.participation.direct-takes-two | A direct thread has one or two participants; start a group thread for more. |
messages::messages.participation.participant-missing | The model behind participant [:participant] no longer exists. |
messages::messages.participation.thread-missing | The thread of participant [:participant] no longer exists. |
messages::messages.participation.interface-required | Missing implementation of the :interface interface for class :class. |
messages::messages.permissions.requires-role | [:actor] requires the :role role to :action. |
messages::messages.permissions.unauthorized | [:actor] is not authorized to :action. |
messages::messages.scope.message-in-another-thread | Message [:message] belongs to another thread. |
messages::messages.scope.participant-in-another-thread | Participant [:participant] belongs to another thread. |
UnauthorizedMessagingAction::for(Model $actor, string $action) and requiresRole(Model $actor, ParticipantRole $required, string $action) take $action as a messages::messages.permissions.actions.* key and read it in the current locale — a plain phrase that is no key is used as it is. The required role reads from messages::messages.permissions.roles.* (owner, admin, member), so a refusal is worded entirely in one language. In Slovak it reads: […] potrebuje rolu „vlastník“, ak chce odovzdať vlastníctvo.
| permissions.actions.* key | English default |
|---|---|
join-thread | join this thread |
send-messages | send messages to this thread |
add-participants / remove-participants | add participants / remove participants |
remove-participant | remove this participant |
change-roles | change participant roles |
transfer-ownership | transfer ownership |
rename-thread / archive-thread | rename the thread / archive the thread |
delete-message / edit-message | delete this message / edit this message |
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 cryptoBy donating, you agree to our donation terms.
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.