sendAppMessage() | setUserData() | setMeetingSessionData() | |
|---|---|---|---|
| Scope | Per message | Per participant | Entire room |
| Persists for late joiners | No | Yes | Yes |
| Size limit | 4KB | 4KB | 100KB |
| Rate limiting | None | Eventual consistency | ~1 update/sec |
| Custom mode only | No | No | Yes |
| Received via | app-message event | participant-updated event | meeting-session-state-updated event |
sendAppMessage() — ephemeral real-time messages
sendAppMessage() delivers a JSON payload to one or more participants currently in the call. Messages are ephemeral — they are not stored and participants who join after a message is sent will never see it.
Use
session_id to address recipients, not user_id. Broadcast messages ('*') are not delivered to the sender.app-message:
sendAppMessage is also available server-side via the REST API, which is useful for injecting messages from your backend — for example, sending a system notification when a server-side event occurs.
Good for: chat, emoji reactions, hand raise notifications, one-time alerts, any event that only matters to participants currently in the room.
Not suitable for: state that late joiners need to see, or anything requiring persistence.
setUserData() — per-participant state
setUserData() attaches arbitrary data to the local participant’s entry in the participants map. It is automatically synced to all other participants and is visible to anyone who joins later — they receive it as part of the participants snapshot when they join.
userData:
participant-updated for all participants:
The local copy of
userData is updated immediately, but propagation to other participants is throttled. All participants are guaranteed to converge on the same final value.setMeetingSessionData() — room-wide shared state
setMeetingSessionData() writes to a single shared data object scoped to the meeting session. All participants receive updates in near real-time, and the data persists as participants join and leave — new joiners receive the current state immediately.
setMeetingSessionData() is available in custom call object mode only, not in Daily Prebuilt.setUserData()), high-frequency real-time events (use sendAppMessage()), or data that needs to outlive the session (use your own backend).
Choosing the right mechanism
A few common scenarios: Chat messages →sendAppMessage(). Messages are ephemeral by nature; you likely want your own backend for history anyway.
Emoji reactions → sendAppMessage(). One-shot, ephemeral, addressed to everyone.
Raised hand indicator → setUserData(). It’s per-person state, and new joiners should see who has their hand up.
Active speaker layout / scene → setMeetingSessionData(). It’s room-level state, and late joiners should start in the right scene.
Server-triggered notification → sendAppMessage() via the REST API. Your backend sends it directly without needing a participant to relay it.
Shared poll state → setMeetingSessionData(). The poll and its results belong to the room, not a person.
Example: combining all three
A live Q&A session is a natural fit for all three mechanisms. Participants raise their hand (per-person state), the host brings someone on stage (room-wide state), and the selected participant gets a private cue to unmute (ephemeral targeted message).setUserDataforhandRaised— it’s per-person, and participants who join mid-session should immediately see who has their hand up.setMeetingSessionDataforcurrentSpeaker— it’s room-level state with one authoritative value, and late joiners need to know who’s on stage.sendAppMessagefor the floor cue — it’s a private one-time signal to a single participant, not state that belongs on the room or the participant object.