> ## Documentation Index
> Fetch the complete documentation index at: https://docs.daily.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom messages and shared data

> Send real-time messages between participants, attach state to participants, and maintain room-wide shared data using daily-js.

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()`  | `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()`](/reference/daily-js/instance-methods/send-app-message) 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.

```javascript theme={null}
// Broadcast to everyone in the room
call.sendAppMessage({ type: 'reaction', emoji: '🎉' });

// Send to a specific participant
call.sendAppMessage({ type: 'dm', text: 'Can you hear me?' }, participantSessionId);

// Send to a subset of participants
call.sendAppMessage({ type: 'alert', text: 'You are now on stage' }, [id1, id2]);
```

<Note>
  Use `session_id` to address recipients, not `user_id`. Broadcast messages (`'*'`) are not delivered to the sender.
</Note>

Receive messages by listening for `app-message`:

```javascript theme={null}
call.on('app-message', ({ data, fromId }) => {
  if (data.type === 'reaction') {
    showReaction(fromId, data.emoji);
  }
});
```

`sendAppMessage` is also available server-side via the [REST API](/reference/rest-api/rooms/session/send-app-message), 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()`](/reference/daily-js/instance-methods/set-user-data) 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.

```javascript theme={null}
// Set on join
await call.join({
  url: roomUrl,
  userData: { role: 'moderator', handRaised: false },
});

// Update during the call
await call.setUserData({ role: 'moderator', handRaised: true });
```

Any participant can read any other participant's `userData`:

```javascript theme={null}
// Read your own
const { userData } = call.participants().local;

// Read a remote participant's
const { userData } = call.participants()[sessionId];
```

Changes trigger `participant-updated` for all participants:

```javascript theme={null}
call.on('participant-updated', ({ participant }) => {
  if (participant.userData?.handRaised) {
    showHandRaisedIndicator(participant.session_id);
  }
});
```

<Note>
  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.
</Note>

**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()`](/reference/daily-js/instance-methods/set-meeting-session-data) 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.

<Note>
  `setMeetingSessionData()` is available in custom call object mode only, not in Daily Prebuilt.
</Note>

```javascript theme={null}
// Replace the entire session data object
call.setMeetingSessionData({ activeScene: 'lobby', pollActive: false });

// Shallow-merge: only update specific top-level keys
call.setMeetingSessionData({ pollActive: true }, 'shallow-merge');
```

Read the current state synchronously at any time:

```javascript theme={null}
const { data } = call.meetingSessionState();
console.log('Current scene:', data.activeScene);
```

Or reactively via event:

```javascript theme={null}
call.on('meeting-session-state-updated', ({ meetingSessionState }) => {
  const { activeScene, pollActive } = meetingSessionState.data;
  updateRoomUI(activeScene, pollActive);
});
```

<Warning>
  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.
</Warning>

**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 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).

```javascript theme={null}
// On join — each participant declares their role
await call.join({
  url: roomUrl,
  userData: { role: 'audience', handRaised: false },
});

// Host initializes room-wide state
call.setMeetingSessionData(
  { currentSpeaker: null, queueOpen: true },
  'replace'
);

// --- Audience: raise a hand ---

async function raiseHand() {
  const { userData } = call.participants().local;
  await call.setUserData({ ...userData, handRaised: true });
}

// --- Host: watch for raised hands ---

call.on('participant-updated', ({ participant }) => {
  if (participant.userData?.handRaised) {
    addToSpeakerQueue(participant.session_id, participant.user_name);
  } else {
    removeFromSpeakerQueue(participant.session_id);
  }
});

// --- Host: bring someone on stage ---

function bringOnStage(sessionId) {
  // Update room-wide state so everyone's UI reflects the current speaker
  call.setMeetingSessionData({ currentSpeaker: sessionId }, 'shallow-merge');

  // Send a private cue so the participant knows to unmute
  call.sendAppMessage({ type: 'you-have-the-floor' }, sessionId);
}

// --- Participant: receive the cue and clear their hand raise ---

call.on('app-message', async ({ data }) => {
  if (data.type === 'you-have-the-floor') {
    call.setLocalAudio(true);
    const { userData } = call.participants().local;
    await call.setUserData({ ...userData, handRaised: false });
  }
});

// --- Everyone: highlight the current speaker ---

call.on('meeting-session-state-updated', ({ meetingSessionState }) => {
  const { currentSpeaker } = meetingSessionState.data;
  highlightActiveSpeaker(currentSpeaker);
});
```

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

<CardGroup>
  <Card title="Methods" icon="code" iconType="solid">
    * [sendAppMessage()](/reference/daily-js/instance-methods/send-app-message)
    * [setUserData()](/reference/daily-js/instance-methods/set-user-data)
    * [setMeetingSessionData()](/reference/daily-js/instance-methods/set-meeting-session-data)
    * [meetingSessionState()](/reference/daily-js/instance-methods/meeting-session-state)
    * [participants()](/reference/daily-js/instance-methods/participants)
  </Card>

  <Card title="Events" icon="bolt" iconType="solid">
    * [app-message](/reference/daily-js/events/messaging-events#app-message)
    * [participant-updated](/reference/daily-js/events/participant-events#participant-updated)
    * [meeting-session-state-updated](/reference/daily-js/events/messaging-events#meeting-session-state-updated)
  </Card>

  <Card title="REST API" icon="server" iconType="solid">
    * [Send app message](/reference/rest-api/rooms/session/send-app-message)
  </Card>
</CardGroup>
