RocketChat/Rocket.Chat · error

You can't delete messages because the room is readonly.

Error message

You can't delete messages because the room is readonly.

What it means

canDeleteMessageAsync throws this plain Error (not a Meteor.Error) when the room is read-only (room.ro === true), the user lacks the 'post-readonly' permission in that room, and their username is not in room.unmuted. Read-only rooms only allow deletions by users who may still post there. Because it is a plain Error, method/REST callers see it as an internal error and must match on the message string.

Source

Thrown at apps/meteor/server/lib/authorization/canDeleteMessage.ts:72

		allowedToDeleteAny || (deletingUser._id === u._id && (await hasPermissionAsync(deletingUser._id, 'delete-own-message', rid)));
	if (!allowed) {
		return false;
	}
	const bypassBlockTimeLimit = await hasPermissionAsync(deletingUser._id, 'bypass-time-limit-edit-and-delete', rid);

	if (!bypassBlockTimeLimit) {
		const blockDeleteInMinutes = await getValue('Message_AllowDeleting_BlockDeleteInMinutes');

		if (blockDeleteInMinutes) {
			const timeElapsedForMessage = elapsedTime(ts);
			return timeElapsedForMessage <= blockDeleteInMinutes;
		}
	}

	if (room.ro === true && !(await hasPermissionAsync(deletingUser._id, 'post-readonly', rid))) {
		// Unless the user was manually unmuted
		if (deletingUser.username && !(room.unmuted || []).includes(deletingUser.username)) {
			throw new Error("You can't delete messages because the room is readonly.");
		}
	}

	return true;
};

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Grant the role 'post-readonly' permission (Administration -> Permissions) so the user may act in read-only rooms
  2. Unmute the specific user in the read-only channel (adds their username to room.unmuted)
  3. Turn off read-only for the channel if deletion should be allowed
  4. In server code note the earlier branch: when Message_AllowDeleting_BlockDeleteInMinutes is set the function returns false instead of throwing — handle both outcomes
Defensive patterns

Strategy: try-catch

Validate before calling

const canDeleteInRoom =
  room.ro !== true ||
  (await hasPermissionAsync(uid, 'post-readonly', room._id)) ||
  (room.unmuted || []).includes(username);
if (!canDeleteInRoom) {
  // skip the delete call and inform the user instead of letting the server throw
}

Try / catch

try {
  await deleteMessageAsync(...);
} catch (e) {
  if (e instanceof Error && e.message.includes('room is readonly')) {
    // plain Error, not Meteor.Error: match the message; show 'deletion blocked in read-only channel'
  }
  throw e;
}

Prevention

When it happens

Trigger: Deleting a message (UI delete, chat.delete) in a read-only channel as a user without post-readonly permission who has not been unmuted by a moderator.

Common situations: Announcement channels set read-only; users holding 'delete-own-message' permission still cannot delete in read-only rooms because delete capability there rides on post-readonly; moderators unmute speakers for posting but forget deletions need the same unmute.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/bbe1a8ec55ef24a8. Report an issue: GitHub.