Skip to main content

Recording overview

New to Daily recording? Start here — covers recording types, when to use each, how to enable recording on a room or meeting token, and how to retrieve recordings.
Recording must be enabled on the room or meeting token (via enable_recording) before startRecording() will work. See the overview guide above for setup.

Starting a recording

startRecording() begins a recording with optional configuration:
// Start a cloud recording with defaults
call.startRecording();

// Start with explicit options
call.startRecording({
  type: 'cloud',
  width: 1920,
  height: 1080,
  fps: 30,
  videoBitrate: 4000,
  audioBitrate: 128,
  backgroundColor: '#000000',
});

Recording options

type
'cloud' | 'raw-tracks' | 'local' | 'cloud-audio-only'
The recording mode. Defaults to whichever type is enabled on the room or token. See the recording overview for a description of each type and when to use it.
width
number
Output video width in pixels. Default: 1280.
height
number
Output video height in pixels. Default: 720.
fps
number
Frames per second. Default: 30.
videoBitrate
number
Video bitrate in kbps.
audioBitrate
number
Audio bitrate in kbps.
minIdleTimeOut
number
Seconds of idle time (no active streams) before the recording automatically stops. Default: 60.
maxDuration
number
Maximum recording duration in seconds.
backgroundColor
string
Background color for the composed output when participant tiles do not fill the frame. Accepts a CSS hex color string, e.g. '#1a1a2e'.
instanceId
string
Identifier for this recording instance. Required when running multiple simultaneous recordings. See multiple simultaneous recordings.
layout
DailyStreamingLayoutConfig
Controls how participant tiles are arranged in the composed output. See DailyStreamingLayoutConfig for the full type reference, or layout presets below for usage examples.

Layout presets

The layout option controls the visual composition of cloud recordings. See DailyStreamingLayoutConfig for field-level documentation on each preset. For screenshots, see the recording overview.

default

call.startRecording({
  layout: {
    preset: 'default',
    max_cam_streams: 9,         // maximum cameras shown
    participants: {
      video: ['session-id-1'],  // specific participant always shown
      sort: 'active',           // sort by speaking activity
    },
  },
});

single-participant

call.startRecording({
  layout: {
    preset: 'single-participant',
    session_id: 'abc123',  // required: which participant to show
  },
});

active-participant

call.startRecording({
  layout: { preset: 'active-participant' },
});

portrait

call.startRecording({
  layout: {
    preset: 'portrait',
    variant: 'inset',  // 'vertical' (default) or 'inset'
    max_cam_streams: 4,
  },
});

audio-only

call.startRecording({
  type: 'cloud-audio-only',
  layout: { preset: 'audio-only' },
});

custom

Use Daily’s VCS baseline composition to fully control the layout programmatically — modes, overlays, labels, participant ordering, and more. The startRecording() reference documents all available composition_params.
call.startRecording({
  layout: {
    preset: 'custom',
    composition_id: 'daily:baseline',
    composition_params: {
      mode: 'dominant',
      'videoSettings.showParticipantLabels': true,
    },
    session_assets: {
      'images/logo': 'https://example.com/logo.png',
    },
  },
});

participants

Several presets accept a participants field to filter which participants’ video and audio are included in the recording. See DailyStreamingLayoutConfig for the full field list.
You must resend participants on every updateRecording() call — it is not persisted from the previous call. If omitted on update, all participants will be included.

Updating layout during recording

Call updateRecording() to switch layouts while a recording is in progress, without stopping and restarting:
// Start in default grid layout
call.startRecording({
  layout: { preset: 'default' },
});

// Later, switch to follow the active speaker
call.updateRecording({
  layout: { preset: 'active-participant' },
});

Stopping a recording

stopRecording() ends the active recording:
call.stopRecording();

Multiple simultaneous recordings

Pass a unique instanceId to run more than one recording at the same time — for example, two cloud recordings with different layouts for different audiences:
// Portrait layout for mobile
call.startRecording({
  instanceId: 'portrait',
  type: 'cloud',
  layout: { preset: 'portrait', variant: 'vertical' },
});

// Landscape layout for desktop
call.startRecording({
  instanceId: 'landscape',
  type: 'cloud',
  layout: { preset: 'active-participant' },
});

// Update and stop each independently
call.updateRecording({ instanceId: 'landscape', layout: { preset: 'default' } });
call.stopRecording({ instanceId: 'portrait' });
call.stopRecording({ instanceId: 'landscape' });
Multiple instances work best when each runs the same recording type. Mixing types (e.g., cloud + raw-tracks simultaneously) is possible but not a recommended pattern — if you need both, contact Daily support to discuss your use case. For full details on instance limits and billing, see the multi-instance guide.

Recording events

Full payload details for all recording events are in the recording events reference.

recording-started

call.on('recording-started', (event) => {
  console.log('Recording started:', event.recordingId);
  console.log('Type:', event.type);           // 'cloud' | 'raw-tracks' | ...
  console.log('Started by:', event.startedBy); // session ID
  console.log('Layout:', event.layout);
});

recording-stopped

call.on('recording-stopped', () => {
  console.log('Recording stopped.');
});

recording-error

call.on('recording-error', ({ errorMsg }) => {
  console.error('Recording error:', errorMsg);
});

Complete cloud recording example

const call = Daily.createCallObject();
await call.join({ url: 'https://your-domain.daily.co/room' });

// Start a 1080p cloud recording with active-speaker layout
call.startRecording({
  type: 'cloud',
  width: 1920,
  height: 1080,
  fps: 30,
  layout: { preset: 'active-participant' },
});

call.on('recording-started', ({ recordingId }) => {
  console.log('Recording ID:', recordingId);
  recordingBadge.hidden = false;
});

call.on('recording-stopped', () => {
  recordingBadge.hidden = true;
});

call.on('recording-error', ({ errorMsg }) => {
  console.error('Recording failed:', errorMsg);
});

// Switch layout mid-recording when a screen share starts
call.on('local-screen-share-started', () => {
  call.updateRecording({
    layout: {
      preset: 'single-participant',
      session_id: call.participants().local.session_id,
    },
  });
});

call.on('local-screen-share-stopped', () => {
  call.updateRecording({
    layout: { preset: 'active-participant' },
  });
});

document.getElementById('stop-recording').onclick = () =>
  call.stopRecording();