Skip to main content
Paid plans only

Transcription overview

New to Daily transcription? Start here — covers real-time vs. post-call transcription, pricing, permissions, and transcript storage.
Daily’s transcription feature streams speech-to-text output to all participants in a call. It is powered by Deepgram and supports a wide range of languages, models, and configuration options.

Transcription permissions

Only meeting owners and participants with canAdmin: 'transcription' can start or stop transcription. See Permissions in the overview for how to grant this.

Transcript storage

By default, transcripts are ephemeral — participants receive transcription-message events but nothing is persisted. To save transcripts as WebVTT files, enable enable_transcription_storage at the room or domain level. See Storage in the overview for setup details, including custom S3 configuration for HIPAA use cases.

Starting transcription

startTranscription() starts a transcription session. All options are optional — call it with no arguments to use Deepgram defaults.
// Start with Deepgram defaults
call.startTranscription();

// Start with explicit language and model
call.startTranscription({
  language: 'en',
  model: 'nova-2',
  punctuate: true,
  profanity_filter: false,
});
Check Deepgram’s language and model overview for the model/language combination that works best for your use case.
See startTranscription() for the full list of options.

Handling transcription messages

Listen to transcription-message to receive transcript segments in real time:
call.on('transcription-message', (event) => {
  console.log('[transcript]', event.text);
  console.log('  participant:', event.participantId);
  console.log('  timestamp:', event.timestamp);
  console.log('  track type:', event.trackType); // 'cam-audio' | 'screen-audio' | ...
  console.log('  instance:', event.instanceId);

  if (event.rawResponse) {
    // Full Deepgram response — available when includeRawResponse: true
    console.log('  raw:', event.rawResponse);
  }
});

Updating transcription

Use updateTranscription() to change which participants are being transcribed while transcription is running:
// Transcribe only the presenter
call.updateTranscription({
  participants: [presenterSessionId],
});

// Restore transcription for everyone
call.updateTranscription({
  participants: null,
});
Note that participants who join after transcription starts are not automatically added — use updateTranscription() to add them.

Stopping transcription

stopTranscription() ends a transcription session. If storage is enabled, the final transcript file is written at this point.
// Stop the default transcription instance
call.stopTranscription();

// Stop a named instance
call.stopTranscription({ instanceId: 'primary' });

Events

Full payload details are in the transcription events reference.
call.on('transcription-started', ({ instanceId, language, model, startedBy }) => {
  console.log(`Transcription started (${model}/${language})`);
  captionsIndicator.hidden = false;
});

call.on('transcription-stopped', ({ instanceId, updatedBy }) => {
  captionsIndicator.hidden = true;
});

call.on('transcription-error', ({ instanceId, errorMsg }) => {
  console.error('Transcription error:', errorMsg);
});

Post-call transcription

To transcribe a recording after a call ends, use the Batch Processor API. See Post-call transcription in the overview for details.

Complete example

1

Start transcription when joining

const call = Daily.createCallObject();

call.on('joined-meeting', () => {
  call.startTranscription({
    language: 'en',
    model: 'nova-2',
    punctuate: true,
  });
});

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

Display captions in real time

const captionsDiv = document.getElementById('captions');

call.on('transcription-message', ({ participantId, text }) => {
  const name =
    call.participants()[participantId]?.user_name ||
    participantId.slice(0, 8);

  const line = document.createElement('p');
  line.textContent = `${name}: ${text}`;
  captionsDiv.appendChild(line);
  captionsDiv.scrollTop = captionsDiv.scrollHeight;
});
3

Handle lifecycle events

call.on('transcription-started', ({ language, model }) => {
  document.getElementById('captions-status').textContent =
    `Live captions on (${model}/${language})`;
});

call.on('transcription-stopped', () => {
  document.getElementById('captions-status').textContent = '';
});

call.on('transcription-error', ({ errorMsg }) => {
  console.error('Transcription error:', errorMsg);
});
4

Stop transcription on leave

document.getElementById('leave').onclick = async () => {
  call.stopTranscription();
  await call.leave();
};