For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Log inSign up
Support
GuidesReferenceClick-to-Call
GuidesReferenceClick-to-Call
  • Getting Started
    • Overview
    • Authentication
    • RxJS Primer
    • Migrate from v3
  • Web Components
    • Overview
    • Click-to-Call
    • Theming
    • Customization
  • Build Voice & Video Apps
    • Overview
    • Outbound Calls
    • Inbound Calls
    • Device Management
    • Screen Sharing
    • Call Controls
    • Layouts
    • Messaging & Chat
  • Manage Resources
    • Overview
    • Users
    • Address Book
    • Client Preferences
    • Capabilities
  • Deploy
    • Overview
    • Framework Integration
    • SSR & Next.js
    • Troubleshooting
LogoLogoSignalWire Docs
Log inSign up
Support
On this page
  • When capabilities are populated
  • Mid-call changes
  • The shape of SelfCapabilities
  • Driving UI from capabilities
  • Reading the full state
  • Why not just gate on token type?
  • Server-side enforcement
  • Presence
  • Reference
Manage Resources

Capabilities

|View as Markdown|Open in Claude|
Was this page helpful?
Edit this page
Previous

Overview

Next
Built with

Capabilities are the permissions the server granted this participant in this call. Different across rooms, roles, and token scopes — and they’re the single source of truth for what your UI should let the user do. The SDK surfaces them as call.self.capabilities, a SelfCapabilities instance with both synchronous getters and observable streams.

Use capability flags to decide which UI affordances to render. Don’t guess from token type or hard-code “agents can lock rooms” — the server already knows, and the capability stream reflects what it decided for this specific call.

When capabilities are populated

Once a call reaches joined, the server sends a call.joined event carrying the participant’s capability flags. The SDK decodes them into a structured SelfCapabilities object on call.self:

1const call = await client.dial("/private/team", { audio: true, video: true });
2
3call.self$.subscribe((self) => {
4 if (!self) return;
5
6 // Synchronous read — current state at this moment
7 console.log(self.capabilities.end); // can end the call?
8 console.log(self.capabilities.self.muteAudio.on); // can mute my own audio?
9 console.log(self.capabilities.member.remove); // can remove others?
10});

SelfCapabilities exposes both synchronous getters (end, screenshare, setLayout, …) and $-suffixed observables that re-emit when capabilities change. State updates are full replacements — a new call.joined swaps the entire state object, not a partial merge.

Mid-call changes

Capabilities only re-emit when the server sends a fresh call.joined event. If your application supports role promotions (guest → host, attendee → moderator) and you need the UI to react, the server has to re-emit call.joined for that participant. The SDK supports nested call.joined events and will update the capability state when one arrives — but it won’t synthesize updates on its own.

The shape of SelfCapabilities

SelfCapabilities groups flags into two families. Each capability is exposed in two forms — an observable (e.g. end$) for reactive bindings and a synchronous getter (e.g. end) for snapshot reads.

  • Member-level: what can be done to a member. The same eleven fields (MemberCapabilities) apply twice:

    • self$ / self — what I can do to myself.
    • member$ / member — what I can do to other members.

    On self, flags like remove and position read as self-actions (can I leave the call, can I move my own tile); on member the same flags read as moderation (can I kick others, can I move their tile).

  • Call-level: end$, setLayout$, sendDigit$, screenshare$, device$, plus on/off-split lock$ and vmutedHide$.

The eleven member-level fields:

FieldTypeWhat it gates
muteAudioOnOffCapabilityMute / unmute the member’s audio.
muteVideoOnOffCapabilityMute / unmute the member’s video.
deafOnOffCapabilityDeafen / un-deafen the member (stop receiving audio from others).
raisehandOnOffCapabilityRaise / lower the member’s hand.
microphoneVolumebooleanAdjust the member’s microphone volume.
microphoneSensitivitybooleanAdjust the member’s microphone sensitivity.
speakerVolumebooleanAdjust the member’s speaker volume.
positionbooleanChange the member’s position in the layout.
metabooleanSet arbitrary metadata on the member.
removebooleanRemove the member from the call.
audioFlagsbooleanChange audio-related flags (mute, deaf) for the member.

Each member flag is either a boolean (the action is allowed or not) or an OnOffCapability — which separates “can turn this on” from “can turn this off” because some roles can do one but not both (e.g. a moderator who can lock a room while only the host can unlock it).

Driving UI from capabilities

The pattern: subscribe once to the observable you care about, toggle the affordance, let the stream update it forever.

1const self = call.self; // SelfParticipant
2
3self.capabilities.end$.subscribe((canEnd) => {
4 endCallButton.hidden = !canEnd;
5});
6
7self.capabilities.screenshare$.subscribe((canShare) => {
8 shareScreenButton.disabled = !canShare;
9});
10
11self.capabilities.setLayout$.subscribe((canLayout) => {
12 layoutMenu.hidden = !canLayout;
13});
14
15// Self-mute flags split on/off
16self.capabilities.self$.subscribe((self) => {
17 muteAudioButton.disabled = !self.muteAudio.on && !self.muteAudio.off;
18});
19
20// Moderation actions on other members
21self.capabilities.member$.subscribe((member) => {
22 kickButton.hidden = !member.remove;
23 moveButton.hidden = !member.position;
24});

If you’re using the web components, <sw-call-controls> already does this internally — buttons hide themselves when the corresponding capability isn’t granted. You only need the manual wiring when building a custom UI.

Reading the full state

state$ emits the entire CallCapabilitiesState on every change. Useful if you serialize the capability set into your own store:

1self.capabilities.state$.subscribe((state) => {
2 uiStore.setCapabilities(state);
3});

Why not just gate on token type?

It’s tempting to skip the capability stream and say “guests can’t end calls” in the UI. Two reasons not to:

  1. The same token can have different capabilities in different rooms. Rooms can override permissions per-resource. The capability stream reflects the resolved permission for this call.
  2. Capabilities can be re-evaluated mid-call. When the server re-emits call.joined after a permission change, the capability stream reflects the new state. Token-based gating would be stuck on the value the token was minted with.

The local capability stream and the server are always in sync because they come from the same call.joined event. Trust it.

Server-side enforcement

Capabilities you see locally are exactly what the platform enforces — calling participant.remove() without the member.remove capability will fail server-side. The local checks are UX, not security: the server is the authority, the flags exist so your UI doesn’t show buttons that would error out.

Presence

Per-user presence isn’t currently exposed through the SDK. Derive online state from your own application telemetry — a last-seen heartbeat from your backend, or “currently in a call” tracked from your own call lifecycle webhooks.

Reference

  • SelfCapabilities — the class
  • self$ / self — self capabilities
  • member$ / member — other-member capabilities
  • end$, setLayout$, sendDigit$, screenshare$, device$, lock$, vmutedHide$ — call-level capabilities
  • MemberCapabilities, OnOffCapability, CallCapabilitiesState — the data shapes