Events

A list of chat room events, the data each one receives, and what is actually blocked when a before handler returns false.

Last updated October 20, 2026

On this page

You receive what happens in a chat room with U.chat(room).on(name, function (room, data) { ... }). This page lists, based on the code, when each event fires, what data contains, and what is actually blocked when a before. handler returns false. For how to register and remove handlers, see U.chat() and the room object.

Rules

  • Most events come in before.name and after.name pairs. before. fires before the default handling, after. fires after it.
  • Callbacks receive (room, data). room is the room object.
  • If a before. callback returns exactly false, the callbacks after it are not called, and only the handling listed in the "If before returns false" column below stops. What gets blocked differs per event.
  • If you change values in data inside before., the handling that follows (display, sending) uses the changed values. The table notes which events you can modify.
  • When the default handling is blocked, after. does not fire either.

Event list

Event When data If before returns false
before.create Room installation starts (before settings and skin are loaded). Fires again on every reinstall {} Cannot be blocked (installation continues)
after.create Room screen is ready (before connecting). Fires again on every reinstall (room.reload()) { id } —
before.join Right before the join request is sent to the server. Fires again on every reconnect { user } Rooms without member integration: the join request is not sent. Rooms with member integration: see the warning below
after.join Join completed and your info received. Fires again on every reconnect { my } (no before)
before.message, after.message One chat message line (including past messages and your own messages) Message Not shown on screen
before.whisper, after.whisper A whisper (including the echo of whispers you sent) Whisper Not shown on screen
before.send, after.send When you send a message (excluding /command and !command input) { message, bold, italic, underline, color } Not sent. room.action.send() returns false
before.command, after.command When a command runs { type, data } Not run. room.action.command() returns false
before.plugin, after.plugin When channel data arrives, or when you enter a !command Channel Received data: onReceived callbacks are not called. !command: addParser callbacks are not called and room.action.plugin() returns false
before.user.join, after.user.join Someone else joins. For cases where it does not fire, see User changes { user } The join notice is not shown. The user is already in room.user.list
before.user.quit, after.user.quit Someone else leaves { user: { nick } } The leave notice is not shown and the user is not removed from room.user.list
before.user.modi, after.user.modi Someone's (including your own) nickname, role, or mute changes { target, changed } Not applied to room.my, room.user.list, or the screen. room.user.get() may already return the new value. We recommend not blocking this
before.user.count, after.user.count Only the user count changes (for when this fires instead of join/leave, see User changes) { count } The user count on screen is not updated
before.update, after.update Room settings change Changed values { key: value } Not applied to room.info or the screen
before.system, after.system System notice lines (notices, bans, mutes, calls, etc., including room.print) { type, data } Not shown on screen
before.control, after.control Server control signals (clearing the chat, vote status, etc.) { type, data } Not passed to the skin
before.error, after.error Errors (server errors and client-side errors such as command argument checks) { code } The error is not shown on screen
before.disconnect, after.disconnect The connection drops (does not fire when leaving the page or removing the tag) {} The skin is not notified, and automatic reconnection after a network error does not happen

Blocking in before. does not undo what happened on the server. For example, hiding a ban notice with before.system does not stop the ban, and blocking before.user.quit does not keep that person in the room. In most cases, only what appears on your own screen changes.

Order of events

When the room opens

  1. before.create — installation starts. room.info and room.my are still empty.
  2. Room settings, language, plugins, and skin are loaded (plugin scripts run here).
  3. after.create — the skin screen is ready. Register skin UI and channels here.
  4. before.join → join request to the server → after.join.
  5. Past messages and notices arrive as message, whisper, and system events.

In rooms that show a join check (bot check) screen, step 1 starts after the check passes. If you reconnect because of a network problem, events start again from step 4.

When you send a message

  1. before.send → sent to the server → after.send
  2. The server sends the message to everyone in the room (including you).
  3. before.message → shown on screen → after.message

after.send only means "sent to the server", not that it was accepted. If the server rejects it, the message does not come back; instead you get an error event (no permission 410, repeated content 430, etc.), a system event (muted, bad_word), or a control event (slowWait). Use after.message to confirm that the message actually went through.

If the chat input starts with /, it goes to the command event instead of send; if it starts with !, it goes to the plugin event. The default skin sends a !command as a normal message when no channel handles it or it is blocked.

When the connection drops

  1. before.disconnect → skin notified → automatic reconnect decided → after.disconnect
  2. If the drop was caused by a network error, the client reconnects and events start again from before.join.

If you were banned, or the connection dropped right after the server sent an error, the client does not reconnect. The disconnect event does not fire when the browser is closed, the user navigates to another page, the <u-chat> tag is removed, or room.reload() is called. Do anything that must happen before leaving in the browser's pagehide event.

Join

U.chat('my-room').on('after.join', function (room, data) {
	// data.my is a user object with the same shape as room.my
	room.print('Welcome, ' + data.my.nick + '.');
});

before.create fires when there is no screen and no info yet, so you rarely need it. It cannot be blocked; to change join info, use before.join.

data.user in before.join:

Room data.user What you can change
Room without member integration { nick } — the nickname saved in this browser (empty if none) Changing data.user.nick requests the join with that nickname
Room with member integration The decoded user_data (or user_data_url): the keys you put in user_data, plus time and hash Do not change it. The signature will not match and you get error 202

Warning: In a room with member integration, returning false from before.join does not block the join. Instead, the request is sent with empty join info and ends in an error. Do not use before.join to block joining in rooms with member integration. To keep someone out, leave out user_data or do not add the tag.

Message

U.chat('*').on('after.message', function (room, data) {
	console.log(data.user.nick, data.content);
});
Key Description
user The sender's user object
content Content
style { bold, italic, underline, size, color }. If color is missing, the room's default text color applies
created Time sent, 'YYYY-MM-DD HH:MM:SS' (in the viewer's time zone)
time Time sent (Unix seconds)
uid Unique message ID (hex string). Empty if none
past true for past chat received again when joining or reconnecting. Absent on newly posted messages
  • If you change data.content in before.message, the changed content is shown only on your screen.
  • Messages from people you ignore (below moderator) reach before.message only; they are not shown and after.message does not fire.

Whisper

Key Description
from, to The sender's and recipient's user objects
content Content
style Currently always an empty string. Its shape differs from the message style object
created, time Time sent (string, Unix seconds)
  • The server sends a whisper to both the sender and the recipient, so whispers you send also come back through this event. If data.from.nick is your nickname, you sent it.
  • Unlike messages, there is no uid.
  • When you join, whispers involving you arrive again along with past messages.
  • If the whisper is from someone you ignore (below moderator), or your settings refuse whispers, it reaches before.whisper only; it is not shown and after.whisper does not fire.

Sending and commands

// Append a signature to outgoing messages
U.chat('*').on('before.send#sign', function (room, data) {
	data.message += ' ~';
});

// Block the whisper command
U.chat('*').on('before.command#no-whisper', function (room, data) {
	if (data.type === 'whisper') return false;
});
  • If you change message, bold, italic, underline, or color in before.send, the changed values are sent.
  • The type in before.command is the name in lowercase with _ removed (un_ignore → unignore). If you change data.data (the argument array), the command runs with the changed arguments.

Channel

before.plugin/after.plugin fire in two cases, with a different data shape for each.

Case data
Channel data sent by someone else or the server arrives { user, type, data } — user is the sender, type is 0 (to everyone) or 1 (to me only), data is the value array
!name args entered in the chat input (or room.action.plugin()) { type: 'name', data: ['arg', ...] } — type is the channel name

Data sent by the server (REST API) and channel saved values received again on join have user.auth set to 4 and an empty nickname. Check whether data came from the server only with data.user.auth === 4.

Received data does not include the channel name. To receive data per channel, use room.plugin.add(channel).onReceived. See Channel API and limits.

User changes

U.chat('*').on('after.user.modi', function (room, data) {
	// data.target  nickname before the change
	// data.changed whichever of { nick?, auth?, mute? } changed
	if (data.changed.nick) room.print(data.target + ' → ' + data.changed.nick);
});
  • Fires only for changes after joining. It also fires when your own nickname, role, or mute changes, and room.my changes too. For the role at join time, look at data.user in user.join or data.my in after.join.
  • Inside before.user.modi, room.my and room.user.list have not changed yet. room.user.get() may already return the new value.
  • In some cases user.join and user.quit do not fire, and only user.count (every 3 seconds) does:
    • The default skin. This is always the case, whether the user list is open or closed.
    • The legacy skin while the user list is closed
    • Rooms with 100 or more users at the time you join
  • In these cases, joins and leaves are reflected only in the user list on screen; there is no event telling the page script who joined or left. room.user.list is not updated either. The only thing the page script can rely on is the user count (user.count).
  • Build features such as join/leave notifications on the assumption that they only work in small rooms using the legacy skin with the user list open. If your server needs to know, use the user count from the Server REST API.
  • The user.list event ({ list }) fires only in legacy skins. The default skin receives the user list in parts, so this event does not fire.

System notices

data.type is the notice type and data.data is the value array. The shape of the value array differs by type.

type Meaning
notice Notice, room.print()
ban Ban
mute, permanent_mute Mute (timed, permanent)
un_mute Mute lifted
to_call, from_call Call sent, received
invite, invite_notice Private channel invite received, sent
ignore, un_ignore Ignore, unignore
frozen, unfrozen Room frozen, unfrozen
op, deop Moderator assigned, removed
user_info Result of the info command
ip Result of the ip command
admin_login, admin_logout Someone became admin with /login, stepped down
clear_log Chat cleared
muted Your message was rejected because you are muted
bad_word Your message was rejected by the word filter
retry_connect Reconnect attempt (number of attempts)
U.chat('*').on('after.system', function (room, data) {
	if (data.type === 'from_call') window.focus();
});

For users who have turned off receiving calls or invites, even before.system for from_call and invite does not fire.

Control signals

data.type is the signal name and data.data is the value array. Most of these are internal signals used by the skin, so names and shapes may change. The ones useful in page scripts are roughly these:

type Meaning data
clearLog Chat cleared []
removeChat An admin deleted messages List of uids of deleted messages. Match them against the uid of message
slowWait Your message was rejected because of slow mode [remaining milliseconds, content you tried to send]
pluginDataReplaced A channel saved value was replaced in a free room []. See Channel API and limits

Errors

data.code is the error number, and the message in the current language is in room.language.error[code].

U.chat('*').on('after.error', function (room, data) {
	console.warn(data.code, room.language.error[data.code]);
});
Code Meaning
101 Protocol error (invalid value, channel saved value too large, etc.)
201 Room does not exist
301 Nickname already in use
202 Member integration signature does not match
203 Connected to a member integration room without login info
205 Login info older than the time sync limit, or the same user_data more than 5 minutes after its first use
207 Room is full
208, 701 Wrong password, password required
402 No permission
404 Target not found
407 Command cannot be used on yourself
410 No chat permission (feature condition)
412 Invalid command arguments
415 You are banned
416 Command not available in this room
421 Already has that role
422 Wrong password
413 You are muted
418 Room is frozen
424 Cannot send because you are refusing whispers
425 Sending too often
428 Channel sending limit exceeded
429 Too many login attempts
430 Sending the same content repeatedly
501, 502 No private channel; cannot create a private channel inside a private channel

For causes and fixes of member integration errors, see Member integration.

data examples

These are the actual shapes of data for each event. Values are examples, and keys the server did not send may be undefined. To check for yourself, log it to the console, for example U.chat('*').on('after.message', function (room, data) { console.log(data); }).

// after.join
{ my: { nick: '홍길동', id: 'user1', level: 5, auth: 1, icons: '', nickcon: '',
        connected: '2026-10-17 12:00:00', time: 1792209600, mute: 0, session: 'a1b2c3d4e5f6', profileimg: '' } }

// before.join (room without member integration)
{ user: { nick: '홍길동' } }

// after.message
{ user: { nick: '홍길동', auth: 1, ... },
  content: 'Hello',
  style: { bold: false, italic: false, underline: false, size: '9pt', color: '#000000' },
  created: '2026-10-17 12:00:01', time: 1792209601, uid: '0a1b2c3d4e' }

// after.whisper
{ from: { nick: '홍길동', ... }, to: { nick: '김철수', ... }, style: '', content: 'One moment',
  created: '2026-10-17 12:00:02', time: 1792209602 }

// before.send
{ message: 'Hello', bold: undefined, italic: undefined, underline: undefined, color: undefined }

// before.command  (/w 김철수 One moment)
{ type: 'whisper', data: ['김철수', 'One moment'] }

// after.user.join / after.user.quit
{ user: { nick: '김철수', auth: 0, ... } }
{ user: { nick: '김철수' } }

// after.user.modi
{ target: '김철수', changed: { nick: '철수' } }
{ target: '김철수', changed: { auth: 2 } }
{ target: '김철수', changed: { mute: 1 } }

// after.user.count
{ count: 152 }

// after.update
{ slowSeconds: 5 }

// after.system
{ type: 'mute', data: ['Admin', '김철수', 10, 'Spamming'] } // admin, target, minutes, reason
{ type: 'from_call', data: ['김철수', '홍길동'] }       // sender, recipient

// after.control
{ type: 'removeChat', data: ['0a1b2c3d4e'] }

// after.plugin  (channel data received / !dice 1 6 entered)
{ user: { nick: '김철수', auth: 1, ... }, type: 0, data: [4] }
{ type: 'dice', data: ['1', '6'] }

// after.error
{ code: 402 }

Still not sure how it works? Ask your question on the contact board.

Business name UCHAT · CEO 김경욱 · Business registration no. 141-05-32832
Address 경기도 파주시 와석순환로 61 · Mail-order business registration no. 2013-경기파주-4485호
Phone 010-8677-8633 · Email support@uchat.io

ⓒ 2013–2026 UCHAT. All rights reserved.