Skip to main content
Daily provides three mechanisms for sharing custom data between participants. They have different persistence, scope, and delivery characteristics — picking the right one for a given use case matters.

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.
Receive messages by listening for 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.
Any participant can read any other participant’s userData:
Changes trigger 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.
Good for: per-person state that all participants need to see, including new joiners — raised hand status, custom role, display name supplements, avatar URL, speaking queue position. Not suitable for: high-frequency updates (e.g. cursor position), or state that belongs to the room rather than a specific person.

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.
Read the current state synchronously at any time:
Or reactively via event:
Updates are batched and synced at most once per second. If multiple participants write concurrently, precise ordering is not guaranteed. This makes setMeetingSessionData() unsuitable for high-frequency updates where sub-second ordering matters — use sendAppMessage() for those instead.
Good for: room-level state shared by all participants — the current scene or layout, whether a poll is active, feature flags, a shared queue, any configuration that needs to survive participant churn. Not suitable for: per-participant state (use 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 messagessendAppMessage(). Messages are ephemeral by nature; you likely want your own backend for history anyway. Emoji reactionssendAppMessage(). One-shot, ephemeral, addressed to everyone. Raised hand indicatorsetUserData(). It’s per-person state, and new joiners should see who has their hand up. Active speaker layout / scenesetMeetingSessionData(). It’s room-level state, and late joiners should start in the right scene. Server-triggered notificationsendAppMessage() via the REST API. Your backend sends it directly without needing a participant to relay it. Shared poll statesetMeetingSessionData(). 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).
Why each mechanism was chosen here:
  • setUserData for handRaised — it’s per-person, and participants who join mid-session should immediately see who has their hand up.
  • setMeetingSessionData for currentSpeaker — it’s room-level state with one authoritative value, and late joiners need to know who’s on stage.
  • sendAppMessage for 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.

See also