RocketChat/Rocket.Chat · error

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

Error message

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

What it means

The send-path readonly guard: validateRoomMessagePermissionsAsync throws this plain Error when room.ro === true, the user lacks the 'post-readonly' permission in that room, and their username is not in room.unmuted. Read-only rooms accept posts only from unmuted users or holders of post-readonly (typically owners and moderators).

Source

Thrown at apps/meteor/server/lib/authorization/canSendMessage.ts:45

		throw new Error('room_is_archived');
	}
	if (args.type !== 'app' && !(await canAccessRoomAsync(room, 'uid' in args ? { _id: args.uid } : args, extraData))) {
		throw new Error('error-not-allowed');
	}

	if (
		await roomCoordinator.getRoomDirectives(room.t).allowMemberAction(room, RoomMemberActions.BLOCK, 'uid' in args ? args.uid : args._id)
	) {
		const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, 'uid' in args ? args.uid : args._id, subscriptionOptions);
		if (subscription && (subscription.blocked || subscription.blocker)) {
			throw new Error('room_is_blocked');
		}
	}

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

	if (args.username && room?.muted?.includes(args.username)) {
		throw new Error('You_have_been_muted');
	}
}
// TODO: remove option uid and username and type
export async function canSendMessageAsync(
	rid: IRoom['_id'],
	user: { uid: IUser['_id']; username: IUser['username']; type: IUser['type'] } | IUser,
	extraData?: Record<string, any>,
): Promise<IRoom> {
	const room = await Rooms.findOneById(rid);
	if (!room) {
		throw new Error('error-invalid-room');
	}

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Grant 'post-readonly' to the role that must speak in read-only rooms (Administration -> Permissions)
  2. Unmute the specific users in the read-only room (adds them to room.unmuted)
  3. Turn read-only off when the restriction is no longer needed
  4. In clients, disable the composer when room.ro is set for users without post-readonly, so the error never fires
Defensive patterns

Strategy: validation

Validate before calling

const canSpeak =
  room.ro !== true ||
  (await hasPermissionAsync(uid, 'post-readonly', room._id)) ||
  (room.unmuted || []).includes(username);
if (!canSpeak) {
  // disable the composer for this room instead of letting the send fail
}

Try / catch

try {
  await sendMessage(...);
} catch (e) {
  if (e instanceof Error && e.message.includes('room is readonly')) {
    // permanent state: inform the user the channel is read-only
  }
  throw e;
}

Prevention

When it happens

Trigger: chat.sendMessage / chat.postMessage / UI send in a read-only channel as a regular member who has not been unmuted by a moderator.

Common situations: Announcement channels that are read-only by design; read-only toggled on during an incident while responders were never unmuted; default roles lacking post-readonly.

Related errors


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