Joining & Creating Calls

This guide covers creating, joining, leaving, and ending calls, including ring calls.

Best Practices

  • Handle call.join() errors with try/catch - network issues can cause failures.
  • Always call call.leave() when unmounting to prevent memory leaks.
  • Use call.getOrCreate() when you want to create if not exists, or just load if it does.
  • Set create: true in call.join() only when you want to create the call on join.
  • For recurring meetings, reuse the same call ID (e.g., your appointment ID).

Call

Call is the main building block of the SDK, abstracting user actions, join flows, and call state.

Create call

Specify callType and callId to create a call. The Call Type controls permissions and features. Call IDs can be reused for recurring meetings.

const callType = "default";
const callId = "test-call";

const call = client.call(callType, callId);
await call.getOrCreate();

// or create it with options:
await call.getOrCreate({
  data: {
    /* call creation options */
  },
});

See all options at the Call creation options section.

Join call

const callType = "default";
const callId = "test-call";

const call = client.call(callType, callId);
await call.join();

Create and join a call

Set create: true to create a new call if it doesn't exist. Set it to false to only join an existing call.

await call.join({
  create: true,
  data: {
    /* call creation options */
  },
});

Join with mic and camera on or off

Configure devices before joining (typically in a lobby view):

const call = client.call("default", "test-call");

// enable mic and camera
await call.camera.enable();
await call.microphone.enable();

// alternatively, you can disable them
await call.camera.disable();
await call.microphone.disable();

// and then join the call
await call.join();

Leave call

await call.leave();

End call

Ending terminates the call for everyone. Requires a special permission.

await call.endCall();

Only users with OwnCapability.JOIN_ENDED_CALL can join an ended call.

Load call

const call = client.call(type, id);
await call.get(); // just load

await call.getOrCreate(); // create if not present and load it

These operations initialize call.state and subscribe to real-time updates.

More: Call & Participant State

Update call

import { RecordSettingsRequestModeEnum } from "@stream-io/video-react-sdk";

await call.update({
  custom: { color: "green" },
  settings_override: {
    recording: {
      mode: RecordSettingsRequestModeEnum.DISABLED,
    },
  },
});

Call creation options

OptionDescriptionDefault
membersA list of members to add to this call. You can specify the role and custom data on these members-
customAny custom data you want to store-
settingsYou can overwrite certain call settings for this specific call. This overwrites the call type standard settings-
startsAtWhen the call will start. Used for calls scheduled in the future, livestreams, audio rooms etc-
teamRestrict the access to this call to a specific team-
ringIf you want the call to ring for each memberfalse
notifyIf you want the call to notify each member by sending push notification.false
videoWhen ringing, the notification will indicate whether it’s a video call or an audio-only call, depending on whether you set the video parameter to true or false-

Set call members

const call = client.call(type, id);
await call.getOrCreate({
  data: {
    members: [{ user_id: "alice", role: "admin" }, { user_id: "bob" }],
  },
});

Update call members

await call.updateCallMembers({
  update_members: [{ user_id: "charlie", role: "admin" }],
  remove_members: ["alice"],
});

Multi-tenant & teams

Users in a team must specify the same team when creating calls:

const call = client.call(type, id);
await call.getOrCreate({
  data: { team: "red" },
});

Ensure call IDs are unique - use UUIDs or prefix with team name.

Custom call data

await call.getOrCreate({
  data: {
    custom: { color: "blue" },
  },
});

Settings override

Override call type settings for a specific call:

// at creation time
await call.getOrCreate({
  data: {
    settings_override: {
      audio: { mic_default_on: false },
      video: { camera_default_on: false },
    },
  },
});

// or later
await call.update({
  settings_override: {
    video: { camera_default_on: true },
  },
});

Backstage setup

The backstage feature lets you and your co-hosts set up before going live. Only after call.goLive() will regular users be allowed to join.

You can also specify join_ahead_time_seconds to allow users to join before the call goes live:

await call.getOrCreate({
  data: {
    starts_at: new Date(Date.now() + 500 * 1000), // 500 seconds from now
    settings_override: {
      backstage: {
        enabled: true,
        join_ahead_time_seconds: 300,
      },
    },
  },
});

In this example, the call starts 500 seconds from now with backstage enabled and join_ahead_time_seconds of 300. Regular users can join 200 seconds from now (500 - 300).

Restricting access

Control access by configuring Call Type permissions.

Step 1: Set up the roles and permissions

On the dashboard, navigate to Video & Audio -> Roles & Permissions and select the appropriate role and scope.

By default, all users have the user role. Start by removing the JoinCall permission from the user role for your call type scope. This prevents regular users from joining calls of this type.

Revoke JoinCall for user role

Next, ensure the call_member role has the JoinCall permission. This allows users with the call_member role to join calls of this type.

Grant JoinCall for call_member role

Step 2: Set up the call

const call = client.call("my-call-type", "my-call-id");
await call.getOrCreate({
  data: {
    members: [
      // please note the `role` property
      { user_id: "alice", role: "call_member" },
      { user_id: "bob", role: "call_member" },
    ],
  },
});

// Grant access to more users
await call.updateCallMembers({
  update_members: [{ user_id: "charlie", role: "call_member" }],
});

// Remove access
await call.updateCallMembers({
  remove_members: ["charlie"],
});