Skip to documentation
svelte/elevenlabs Docs

Docs Sessions

Sessions

Connection lifecycle, reactive session state, transcripts, typed turns, feedback, and the cleanup contract.

Lifecycle

A session moves through three states: disconnected, connecting, and connected. startConversation() does all of the preparation in a fixed order, then resolves after the server sends its metadata or after a start failure.

  1. Validate the target and read the microphone and signed URL settings. Obvious mistakes fail before a permission prompt appears.

  2. Create the audio graph inside the user gesture. This includes the microphone track, the input context, and the playback context. Text only sessions skip this step.

  3. Resolve the WebSocket URL. A public id is converted to a ConvAI URL, an existing wss:// target is used directly, and signed mode calls your server route.

  4. Open the socket, send the initiation frame, and wait for conversation_initiation_metadata. The controller has a 15 second handshake timeout.

Connection and audio failures populate error and errorCode instead of rejecting the start promise. Read them after it settles, or pass onError when the controller is created. Calling start while a session is active is a no-op.

You can also end a session with the alias endSession().

Session state

Controller fields
FieldTypeNotes
statusConversationStatusConnection lifecycle.
modeConversationModeSingle label: disconnected, connecting, ready, listening, or speaking.
canStartbooleanTrue when the controller is disconnected.
isSpeakingbooleanAgent playback is scheduled or audible.
isListeningbooleanMicrophone is live and not muted.
isUserSpeakingbooleanLocal voice activity detection hears speech.
isMutedbooleanOutgoing audio is stopped.
isTextOnlyboolean (getter)Current session or next session runs without audio devices.
volumenumberLoudest level from 0 to 1.
inputVolumenumberNormalized microphone level.
outputVolumenumberNormalized agent playback level.
transcriptsTranscriptMessage[]Streaming transcript entries.
messageCountnumberDerived from the transcript length.
messageTranscriptMessage | nullMost recently created or updated entry.
errorstring | nullSafe to render.
errorCodeConversationErrorCode | nullStable category for UI branches.
latencynumber | nullLatest time to first audio, in milliseconds.
latencyHistorynumber[]Recent measurements, oldest first. Keeps 12.
conversationIdstring | nullElevenLabs conversation id after negotiation.
inputAudioFormatAudioFormatId | nullNegotiated microphone format.
outputAudioFormatAudioFormatId | nullNegotiated agent format.
queueStatusQueueStatus | nullConcurrency queue state.
interruptionsnumberBarge-ins, deduplicated within one second.
canSendFeedbackbooleanFeedback is available for the current response.
vadScorenumberLatest server voice activity score.
contextUsageContextUsageServerEvent['context_usage_event'] | nullLLM context window usage.
outputGainnumberPlayback gain from 0 to 1.
inputDeviceIdstring | nullCurrent microphone device id.
outputDeviceIdstring | nullCurrent output device id.

Transcripts

Entries have a stable id. Partial agent text updates the same entry as it streams, and corrections replace the text in place. Check isFinal when you want to style streaming content differently.

SVELTE
{#each conversation.transcripts as message (message.id)}  <article data-role={message.role}>    <strong>{message.role === 'user' ? 'You' : 'Agent'}</strong>    <p>{message.text}</p>    {#if !message.isFinal}      <span aria-label="Still streaming">...</span>    {/if}  </article>{/each}

The transcript is capped by maxTranscripts, which defaults to 250 and is clamped to at least 20. Starting a new session clears the transcript unless resetTranscriptsOnStart is set to false. clearTranscripts() clears it without touching the server conversation.

Agent entries can include attachments and a responseId. Files are plain objects with url, name, and an optional mime_type.

Text turns

Text only sessions use the same socket and protocol without opening a microphone. You can also send typed turns during a voice session.

TS
await conversation.startConversation('agent_your_public_id', {  textOnly: true});conversation.sendUserMessage('Show my last order.');conversation.sendContextualUpdate('The customer is on the checkout page.', 'checkout');conversation.sendUserActivity();
  • sendUserMessage(text) adds a typed user turn and returns false when the socket is not open or the text is empty.
  • sendContextualUpdate(text, contextId?) injects context for the next turn without interrupting speech. Reusing a context id replaces older context.
  • sendUserActivity() signals that the user is present. Calls are throttled to one frame per second.

Set textOnly on the controller when the whole page is a chat, or pass it per start call when the same controller needs both modes. A per-start value wins for that session.

Mute and playback

TS
conversation.toggleMute();conversation.setMuted(true);conversation.setVolume(0.8);// Object form is accepted for API familiarity.conversation.setVolume({ volume: 0.65 });

Muting stops outgoing chunks but keeps the microphone track and audio context alive, so unmuting is instant. setVolume clamps the value to the 0 to 1 range and updates outputGain.

Barge-in is local and server assisted. When local voice activity crosses vadThreshold and interruptOnUserSpeech is enabled, queued playback is flushed immediately. A server interruption event does the same. The controller counts one interruption when both signals arrive together.

Feedback

Feedback becomes available after an agent response supplies an event id. Render controls only when canSendFeedback is true.

SVELTE
{#if conversation.canSendFeedback}  <button onclick={() => conversation.sendFeedback(true)}>Helpful</button>  <button onclick={() => conversation.sendFeedback(false)}>Not helpful</button>{/if}

sendFeedback(like, eventId?) defaults to the latest agent response. It returns false when feedback is not available or the socket is closed.

Cleanup

useConversation registers an $effect teardown. On unmount it closes the socket, stops media tracks, disconnects the audio graph, aborts signed URL requests, clears timers, and closes both audio contexts. Set autoCleanup: false only when you intend to call destroy() yourself.

TS
import { createConversation } from 'svelte-elevenlabs';const conversation = createConversation({  agentId: 'agent_your_public_id'});await conversation.startConversation();// Later, or from a store teardown:conversation.destroy();

stopConversation() ends the call but keeps the transcript for a post-call view. destroy() is idempotent. The controller also stops itself on pagehide so a navigation does not leave a socket running.

Edit this page on GitHub