> ## 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.

# Events

> Subscribe to Daily call events using on(), once(), and off() with full TypeScript type narrowing.

Daily uses a typed event system built on Node.js `EventEmitter`. Every state change — participant joins, track goes live, recording starts, network degrades — is surfaced as a typed event object.

## Subscribing to Events

```typescript theme={null}
// Subscribe — fires on every occurrence
call.on(event, handler);

// Subscribe once — auto-removes after first invocation
call.once(event, handler);

// Unsubscribe
call.off(event, handler);
```

All three methods return the `DailyCall` instance for chaining.

## Base Event Shape

Every event object extends `DailyEventObjectBase`:

```typescript theme={null}
type DailyEventObjectBase = {
  action: DailyEvent;   // the event name string
  callClientId: string; // ID of the DailyIframe instance that emitted it
};
```

## TypeScript Type Narrowing

The `DailyEventObject<T>` generic type maps event name strings to their specific payload types. TypeScript will narrow the handler argument automatically:

```typescript theme={null}
import type { DailyEventObject } from '@daily-co/daily-js';

// TypeScript knows `event` is DailyEventObjectParticipant
call.on('participant-joined', (event: DailyEventObject<'participant-joined'>) => {
  const { participant } = event; // fully typed DailyParticipant
  console.log(participant.user_name);
});

// TypeScript knows `event` is DailyEventObjectTrack
call.on('track-started', (event: DailyEventObject<'track-started'>) => {
  const { track, type, participant } = event;
  console.log(`${type} track started by ${participant?.user_name}`);
});

// TypeScript knows `event` is DailyEventObjectFatalError
call.on('error', (event: DailyEventObject<'error'>) => {
  console.error(event.errorMsg, event.error);
});
```

## Event Reference

### Lifecycle Events

| Event                                                                                            | Payload type                   | Description                                                             |
| ------------------------------------------------------------------------------------------------ | ------------------------------ | ----------------------------------------------------------------------- |
| [`loading`](/reference/daily-js/events/lifecycle-events#loading)                                 | `DailyEventObjectNoPayload`    | The Daily call bundle has started loading (iframe mode).                |
| [`load-attempt-failed`](/reference/daily-js/events/lifecycle-events#load-attempt-failed)         | `DailyEventObjectGenericError` | The call bundle failed to load. `errorMsg` has details.                 |
| [`loaded`](/reference/daily-js/events/lifecycle-events#loaded)                                   | `DailyEventObjectNoPayload`    | The call bundle finished loading.                                       |
| [`started-camera`](/reference/daily-js/events/settings-events#started-camera)                    | —                              | Camera/mic acquisition completed (before joining).                      |
| [`camera-error`](/reference/daily-js/events/settings-events#camera-error)                        | `DailyEventObjectCameraError`  | Camera or mic acquisition failed.                                       |
| [`joining-meeting`](/reference/daily-js/events/lifecycle-events#joining-meeting)                 | `DailyEventObjectNoPayload`    | `join()` was called; connection in progress.                            |
| [`joined-meeting`](/reference/daily-js/events/lifecycle-events#joined-meeting)                   | `DailyEventObjectParticipants` | Successfully joined. `participants` contains the full initial snapshot. |
| [`left-meeting`](/reference/daily-js/events/lifecycle-events#left-meeting)                       | `DailyEventObjectNoPayload`    | `leave()` or ejection completed.                                        |
| [`call-instance-destroyed`](/reference/daily-js/events/lifecycle-events#call-instance-destroyed) | `DailyEventObjectNoPayload`    | `destroy()` was called.                                                 |
| [`access-state-updated`](/reference/daily-js/events/lifecycle-events#access-state-updated)       | `DailyEventObjectAccessState`  | Room access level changed (e.g. knock-to-join lobby admission).         |

```typescript theme={null}
call.on('joined-meeting', ({ participants }) => {
  console.log('All participants at join time:', participants);
});

call.on('left-meeting', () => {
  console.log('Left the call');
});

call.on('camera-error', ({ error }) => {
  if (error.type === 'permissions') {
    console.error('Camera/mic permissions denied');
  }
});
```

### Participant Events

| Event                                                                                                      | Payload type                          | Description                                                                                                              |
| ---------------------------------------------------------------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| [`participant-joined`](/reference/daily-js/events/participant-events#participant-joined)                   | `DailyEventObjectParticipant`         | A remote participant entered the room.                                                                                   |
| [`participant-updated`](/reference/daily-js/events/participant-events#participant-updated)                 | `DailyEventObjectParticipant`         | Any field on a participant changed.                                                                                      |
| [`participant-left`](/reference/daily-js/events/participant-events#participant-left)                       | `DailyEventObjectParticipantLeft`     | A remote participant left or was ejected. `reason` is `'hidden'` if they remain on the call but no longer have presence. |
| [`participant-counts-updated`](/reference/daily-js/events/participant-events#participant-counts-updated)   | `DailyEventObjectParticipantCounts`   | Aggregate `present`/`hidden` counts changed.                                                                             |
| [`active-speaker-change`](/reference/daily-js/events/participant-events#active-speaker-change)             | `DailyEventObjectActiveSpeakerChange` | The dominant speaker changed. `activeSpeaker.peerId` is the new speaker's `session_id`.                                  |
| [`waiting-participant-added`](/reference/daily-js/events/participant-events#waiting-participant-added)     | `DailyEventObjectWaitingParticipant`  | A participant entered the knock-to-join lobby.                                                                           |
| [`waiting-participant-updated`](/reference/daily-js/events/participant-events#waiting-participant-updated) | `DailyEventObjectWaitingParticipant`  | A waiting participant's state changed.                                                                                   |
| [`waiting-participant-removed`](/reference/daily-js/events/participant-events#waiting-participant-removed) | `DailyEventObjectWaitingParticipant`  | A waiting participant was admitted, denied, or left.                                                                     |

```typescript theme={null}
call.on('active-speaker-change', ({ activeSpeaker }) => {
  highlightTile(activeSpeaker.peerId);
});

call.on('waiting-participant-added', ({ participant }) => {
  showKnockNotification(participant.name);
});
```

### Media Events

| Event                                                                                                        | Payload type                                   | Description                                                                                                       |
| ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| [`track-started`](/reference/daily-js/events/media-events#track-started)                                     | `DailyEventObjectTrack`                        | A track became `playable`. `type` is `'video'`, `'audio'`, `'screenVideo'`, `'screenAudio'`, or a custom name.    |
| [`track-stopped`](/reference/daily-js/events/media-events#track-stopped)                                     | `DailyEventObjectTrack`                        | A track left `playable` state.                                                                                    |
| [`local-screen-share-started`](/reference/daily-js/events/media-events#local-screen-share-started)           | —                                              | The local participant's screen share started.                                                                     |
| [`local-screen-share-stopped`](/reference/daily-js/events/media-events#local-screen-share-stopped)           | —                                              | The local screen share stopped normally.                                                                          |
| [`local-screen-share-canceled`](/reference/daily-js/events/media-events#local-screen-share-canceled)         | —                                              | The user dismissed the browser's screen picker.                                                                   |
| [`local-audio-level`](/reference/daily-js/events/media-events#local-audio-level)                             | `DailyEventObjectLocalAudioLevel`              | Local audio level update. `audioLevel` is `0.0–1.0`. Requires `startLocalAudioLevelObserver()`.                   |
| [`remote-participants-audio-level`](/reference/daily-js/events/media-events#remote-participants-audio-level) | `DailyEventObjectRemoteParticipantsAudioLevel` | Map of `session_id → audioLevel` for remote participants. Requires `startRemoteParticipantsAudioLevelObserver()`. |
| [`face-counts-updated`](/reference/daily-js/events/media-events#face-counts-updated)                         | `DailyEventObjectFaceCounts`                   | Number of faces detected in local video changed.                                                                  |

```typescript theme={null}
const AUDIO_LEVEL_THRESHOLD = 0.05;

await call.startLocalAudioLevelObserver(100); // poll every 100ms

call.on('local-audio-level', ({ audioLevel }) => {
  setIsSpeaking(audioLevel > AUDIO_LEVEL_THRESHOLD);
});
```

### Recording Events

<Badge color="blue">Paid plans only</Badge>

| Event                                                                                | Payload type                       | Description                                                         |
| ------------------------------------------------------------------------------------ | ---------------------------------- | ------------------------------------------------------------------- |
| [`recording-started`](/reference/daily-js/events/recording-events#recording-started) | `DailyEventObjectRecordingStarted` | A recording started. `local` is `true` for browser-based recording. |
| [`recording-stopped`](/reference/daily-js/events/recording-events#recording-stopped) | `DailyEventObjectRecordingStopped` | A recording stopped.                                                |
| [`recording-error`](/reference/daily-js/events/recording-events#recording-error)     | `DailyEventObjectRecordingError`   | Recording encountered an error.                                     |

```typescript theme={null}
call.on('recording-started', ({ local, recordingId, type }) => {
  console.log(`Recording started: type=${type}, local=${local}, id=${recordingId}`);
});

call.on('recording-data', ({ data, finished }) => {
  recordingChunks.push(data);
  if (finished) {
    const blob = new Blob(recordingChunks, { type: 'video/mp4' });
    saveRecording(blob);
  }
});
```

### Live Streaming Events

<Badge color="blue">Paid plans only</Badge>

| Event                                                                                               | Payload type                           | Description                                                                    |
| --------------------------------------------------------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------ |
| [`live-streaming-started`](/reference/daily-js/events/live-streaming-events#live-streaming-started) | `DailyEventObjectLiveStreamingStarted` | Live stream started.                                                           |
| [`live-streaming-updated`](/reference/daily-js/events/live-streaming-events#live-streaming-updated) | `DailyEventObjectLiveStreamingUpdated` | Stream state or endpoint changed. `state` is `'connected'` or `'interrupted'`. |
| [`live-streaming-stopped`](/reference/daily-js/events/live-streaming-events#live-streaming-stopped) | `DailyEventObjectLiveStreamingStopped` | Live stream stopped.                                                           |
| [`live-streaming-error`](/reference/daily-js/events/live-streaming-events#live-streaming-error)     | `DailyEventObjectLiveStreamingError`   | Live streaming error.                                                          |

### Transcription Events

<Badge color="blue">Paid plans only</Badge>

| Event                                                                                            | Payload type                           | Description                                                                               |
| ------------------------------------------------------------------------------------------------ | -------------------------------------- | ----------------------------------------------------------------------------------------- |
| [`transcription-started`](/reference/daily-js/events/transcription-events#transcription-started) | `DailyEventObjectTranscriptionStarted` | Transcription started. Contains model, language, and configuration details.               |
| [`transcription-stopped`](/reference/daily-js/events/transcription-events#transcription-stopped) | `DailyEventObjectTranscriptionStopped` | Transcription stopped.                                                                    |
| [`transcription-error`](/reference/daily-js/events/transcription-events#transcription-error)     | `DailyEventObjectTranscriptionError`   | Transcription error.                                                                      |
| [`transcription-message`](/reference/daily-js/events/transcription-events#transcription-message) | `DailyEventObjectTranscriptionMessage` | A transcription result. `text` is the transcript; `participantId` identifies the speaker. |

```typescript theme={null}
call.on('transcription-message', ({ participantId, text, timestamp }) => {
  addCaption({ participantId, text, timestamp });
});
```

### Network Events

| Event                                                                                        | Payload type                             | Description                                                                                |
| -------------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------ |
| [`network-quality-change`](/reference/daily-js/events/network-events#network-quality-change) | `DailyEventObjectNetworkQualityEvent`    | Network quality changed. `networkState` is `'good'`, `'warning'`, `'bad'`, or `'unknown'`. |
| [`network-connection`](/reference/daily-js/events/network-events#network-connection)         | `DailyEventObjectNetworkConnectionEvent` | Low-level connection event. `type` is `'signaling'`, `'peer-to-peer'`, or `'sfu'`.         |
| [`cpu-load-change`](/reference/daily-js/events/network-events#cpu-load-change)               | `DailyEventObjectCpuLoadEvent`           | CPU load state changed. `cpuLoadState` is `'low'` or `'high'`.                             |
| [`test-completed`](/reference/daily-js/events/network-events#test-completed)                 | `DailyEventObjectTestCompleted`          | A quality or connectivity test finished.                                                   |

```typescript theme={null}
call.on('network-quality-change', ({ networkState, networkStateReasons }) => {
  if (networkState === 'bad') {
    showNetworkWarning(networkStateReasons);
  }
});
```

### Settings & Device Events

| Event                                                                                               | Payload type                              | Description                                                         |
| --------------------------------------------------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------- |
| [`available-devices-updated`](/reference/daily-js/events/settings-events#available-devices-updated) | `DailyEventObjectAvailableDevicesUpdated` | The list of available input/output devices changed.                 |
| [`selected-devices-updated`](/reference/daily-js/events/settings-events#selected-devices-updated)   | `DailyEventObjectSelectedDevicesUpdated`  | The active camera, mic, or speaker selection changed.               |
| [`input-settings-updated`](/reference/daily-js/events/settings-events#input-settings-updated)       | `DailyEventObjectInputSettingsUpdated`    | Input settings (noise cancellation, background blur, etc.) changed. |
| [`send-settings-updated`](/reference/daily-js/events/settings-events#send-settings-updated)         | `DailyEventObjectSendSettingsUpdated`     | Video send settings (bitrate, simulcast) changed.                   |
| [`receive-settings-updated`](/reference/daily-js/events/settings-events#receive-settings-updated)   | `DailyEventObjectReceiveSettingsUpdated`  | Receive settings (simulcast layer) changed (call object mode).      |

### Messaging Events

| Event                                                                                                            | Payload type                                   | Description                                                                                          |
| ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| [`app-message`](/reference/daily-js/events/messaging-events#app-message)                                         | `DailyEventObjectAppMessage`                   | A message sent via `sendAppMessage()`. `data` is the payload; `fromId` is the sender's `session_id`. |
| [`meeting-session-updated`](/reference/daily-js/events/messaging-events#meeting-session-updated)                 | (deprecated)                                   | Use `meeting-session-summary-updated` instead.                                                       |
| [`meeting-session-summary-updated`](/reference/daily-js/events/messaging-events#meeting-session-summary-updated) | `DailyEventObjectMeetingSessionSummaryUpdated` | The meeting session summary changed.                                                                 |
| [`meeting-session-state-updated`](/reference/daily-js/events/messaging-events#meeting-session-state-updated)     | `DailyEventObjectMeetingSessionStateUpdated`   | Shared meeting session state changed.                                                                |

```typescript theme={null}
// Broadcast to all participants
call.sendAppMessage({ type: 'reaction', emoji: '👋' }, '*');

// Receive messages
call.on('app-message', ({ data, fromId }) => {
  console.log(`Message from ${fromId}:`, data);
});
```

### Telephony Events (SIP/PSTN)

<Badge color="blue">Paid plans only</Badge>

| Event                                                                                | Payload type                       | Description                                                                  |
| ------------------------------------------------------------------------------------ | ---------------------------------- | ---------------------------------------------------------------------------- |
| [`dialin-ready`](/reference/daily-js/events/telephony-events#dialin-ready)           | `DailyEventObjectDialinReady`      | SIP endpoint is ready to accept dial-in calls. `sipEndpoint` is the address. |
| [`dialin-connected`](/reference/daily-js/events/telephony-events#dialin-connected)   | `DailyEventObjectDialinConnected`  | A dial-in call connected.                                                    |
| [`dialin-error`](/reference/daily-js/events/telephony-events#dialin-error)           | `DailyEventObjectDialinError`      | Dial-in error.                                                               |
| [`dialin-stopped`](/reference/daily-js/events/telephony-events#dialin-stopped)       | `DailyEventObjectDialinStopped`    | Dial-in call stopped.                                                        |
| [`dialin-warning`](/reference/daily-js/events/telephony-events#dialin-warning)       | `DailyEventObjectDialinWarning`    | Non-fatal dial-in warning.                                                   |
| [`dialout-connected`](/reference/daily-js/events/telephony-events#dialout-connected) | `DailyEventObjectDialOutConnected` | An outbound dial-out call connected.                                         |
| [`dialout-answered`](/reference/daily-js/events/telephony-events#dialout-answered)   | `DailyEventObjectDialOutAnswered`  | An outbound call was answered.                                               |
| [`dialout-error`](/reference/daily-js/events/telephony-events#dialout-error)         | `DailyEventObjectDialOutError`     | Dial-out error.                                                              |
| [`dialout-stopped`](/reference/daily-js/events/telephony-events#dialout-stopped)     | `DailyEventObjectDialOutStopped`   | Dial-out call stopped.                                                       |
| [`dialout-warning`](/reference/daily-js/events/telephony-events#dialout-warning)     | `DailyEventObjectDialOutWarning`   | Non-fatal dial-out warning.                                                  |

### Error Events

| Event                                                                      | Payload type                    | Description                                                                                        |
| -------------------------------------------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------- |
| [`error`](/reference/daily-js/events/error-events#error)                   | `DailyEventObjectFatalError`    | A fatal error occurred. `error.type` is one of `DailyFatalErrorType`. The call ends automatically. |
| [`nonfatal-error`](/reference/daily-js/events/error-events#nonfatal-error) | `DailyEventObjectNonFatalError` | A recoverable error. `type` is one of `DailyNonFatalErrorType`. The call continues.                |

```typescript theme={null}
call.on('error', ({ error, errorMsg }) => {
  switch (error?.type) {
    case 'ejected':
      showMessage('You were removed from the call.');
      break;
    case 'meeting-full':
      showMessage('The room is at capacity.');
      break;
    case 'exp-room':
    case 'exp-token':
      showMessage('This room or your access token has expired.');
      break;
    case 'connection-error':
      showMessage('Connection failed. Please check your network.');
      break;
    default:
      console.error('Fatal error:', errorMsg);
  }
});

call.on('nonfatal-error', ({ type, errorMsg, details }) => {
  console.warn(`Non-fatal error [${type}]:`, errorMsg, details);
});
```

### Iframe UI Events

These events are only emitted in iframe mode:

| Event                                                                                                  | Description                                      |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------ |
| [`fullscreen`](/reference/daily-js/events/iframe-ui-events#fullscreen)                                 | The iframe entered fullscreen.                   |
| [`exited-fullscreen`](/reference/daily-js/events/iframe-ui-events#exited-fullscreen)                   | The iframe exited fullscreen.                    |
| [`pip-started`](/reference/daily-js/events/iframe-ui-events#pip-started)                               | Picture-in-picture started.                      |
| [`pip-stopped`](/reference/daily-js/events/iframe-ui-events#pip-stopped)                               | Picture-in-picture stopped.                      |
| [`sidebar-view-changed`](/reference/daily-js/events/iframe-ui-events#sidebar-view-changed)             | The active sidebar panel changed.                |
| [`custom-button-click`](/reference/daily-js/events/iframe-ui-events#custom-button-click)               | A custom tray button was clicked.                |
| [`active-speaker-mode-change`](/reference/daily-js/events/iframe-ui-events#active-speaker-mode-change) | Active speaker mode was toggled.                 |
| [`show-local-video-changed`](/reference/daily-js/events/iframe-ui-events#show-local-video-changed)     | Whether the local video tile is visible changed. |
| [`lang-updated`](/reference/daily-js/events/iframe-ui-events#lang-updated)                             | The UI language changed.                         |
| [`theme-updated`](/reference/daily-js/events/iframe-ui-events#theme-updated)                           | The UI theme changed.                            |

## Removing Listeners

Use `call.off()` to remove a specific listener:

```typescript theme={null}
// Remove a specific listener
call.off('participant-joined', myHandler);
```

`DailyCall` (which extends Node.js `EventEmitter` at runtime) also inherits `removeAllListeners()`, though this method is not part of the typed `DailyCall` interface:

```typescript theme={null}
// Remove all listeners for one event (EventEmitter runtime method)
(call as any).removeAllListeners('participant-joined');

// Remove all listeners for all events (EventEmitter runtime method)
(call as any).removeAllListeners();
```

<Warning>
  Always remove event listeners before calling `call.destroy()` to prevent memory leaks, especially in single-page applications that re-render frequently.
</Warning>
