Client & Authentication

Set up StreamVideoClient to connect users and join calls.

Best Practices

  • Create StreamVideoClient once at app root; use tokenProvider for auto-refresh.
  • Always clean up with client.disconnectUser() in useEffect return.
  • Use short-lived tokens with a tokenProvider for production.
  • Handle connection errors with try/catch around connectUser().

Basic setup

import { StreamVideoClient, User } from "@stream-io/video-react-sdk";

const user: User = { id: "sara" };
const apiKey = "my-stream-api-key"; // from dashboard.getstream.io
const tokenProvider = async () => fetchTokenFromYourServer(user.id);
const client = new StreamVideoClient({ apiKey, user, tokenProvider });
  • The API Key can be found in your dashboard.
  • The user can be either authenticated, anonymous or guest.
  • You can store custom data on the user object if required.

Typically, initialize the client when your application loads and use a context provider to make it available throughout your app.

Generating a token

Tokens must be generated server-side. Use our server-side SDKs to add support for this - typically integrated with your login/registration flow.

Here are credentials to try out the app with:

PropertyValue
API Keymmhfdzb5evj2
TokeneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3Byb250by5nZXRzdHJlYW0uaW8iLCJzdWIiOiJ1c2VyL1F1aXJreV9Sb3F1ZWZvcnQiLCJ1c2VyX2lkIjoiUXVpcmt5X1JvcXVlZm9ydCIsInZhbGlkaXR5X2luX3NlY29uZHMiOjYwNDgwMCwiaWF0IjoxNzczMTQwMTE2LCJleHAiOjE3NzM3NDQ5MTZ9.eoiJRzOxdAg5lKe8wiCcpD3c_P3CERsdB9xGYpIo898
User IDQuirky_Roquefort
Call IDX55kiDoO8OYIqLhuxu8h6

For development purposes, you can use our Token Generator.

User types

  • Authenticated users - users with an account in your app.
  • Guest users - temporary accounts with a name and image.
  • Anonymous users - unauthenticated users (common for livestream viewers).

For authenticated users (default user.type), pass either token or tokenProvider. For guest users, do not pass token or tokenProvider. For anonymous users, token and tokenProvider are optional.

Guest users

Example setup for a guest user:

import { StreamVideoClient, User } from "@stream-io/video-react-sdk";

const user: User = { id: "jack-guest", type: "guest" };
const apiKey = "my-stream-api-key";
const client = new StreamVideoClient({ apiKey, user });

Anonymous users

Example for an anonymous user:

import { StreamVideoClient, User } from "@stream-io/video-react-sdk";

const user: User = { type: "anonymous" };
const apiKey = "my-stream-api-key";
const client = new StreamVideoClient({ apiKey, user });

Anonymous users don't establish a WebSocket connection, so they won't receive events. They can only watch livestreams or join calls.

You can also provide a call-scoped token (or token provider) for anonymous users:

import { StreamVideoClient, User } from "@stream-io/video-react-sdk";

const user: User = { type: "anonymous" };
const apiKey = "my-stream-api-key";
const token = "<anonymous-token-with-call_cids>";
const client = new StreamVideoClient({ apiKey, user, token });

The token for anonymous users should include call_cids - an array of call CIDs they're allowed to join:

{
  "iss": "@stream-io/dashboard",
  "iat": 1726406693,
  "exp": 1726493093,
  "user_id": "!anon",
  "role": "viewer",
  "call_cids": ["livestream:123"]
}

Connecting a user and error handling

Connect users in two ways:

Automatic connection - when creating the client:

const client = new StreamVideoClient({
  apiKey,
  user,
  token,
  options: {
    maxConnectUserRetries: 3,
    onConnectUserError: (err: Error, allErrors: Error[]) => {
      console.error("Failed to connect user", err, allErrors);
      // handle the connect error, i.e. ask the user to retry
      // later when they have better connection or show an error message
    },
  },
});

Manual connection - using client.connectUser():

const client = new StreamVideoClient({ apiKey });
try {
  await client.connectUser(user, token);
} catch (err) {
  console.error("Failed to connect user", err);
  // handle the connect error
}

Disconnecting a user

Discard a client instance or disconnect a user with client.disconnectUser():

await client.disconnectUser();

Client options

token or tokenProvider

For authenticated users, pass a string token or a tokenProvider function that returns Promise<string>. You can also provide both together. Anonymous users can also pass token or tokenProvider when needed.

When using `tokenProvider`, the SDK automatically refreshes expired tokens.
import { StreamVideoClient, User } from "@stream-io/video-react-sdk";

const tokenProvider = async () => {
  const response = await fetch("/api/token");
  const data = await response.json();
  return data.token;
};

const user: User = { id: "sara" };
const apiKey = "my-stream-api-key";
const client = new StreamVideoClient({ apiKey, tokenProvider, user });

Or initialize with a static token:

import { StreamVideoClient, User } from "@stream-io/video-react-sdk";

const user: User = { id: "sara" };
const apiKey = "my-stream-api-key";
const token = "<token-from-your-server>";
const client = new StreamVideoClient({ apiKey, user, token });

When both are provided, the SDK uses token first and then tokenProvider for subsequent token refreshes:

import { StreamVideoClient, User } from "@stream-io/video-react-sdk";

const user: User = { id: "sara" };
const apiKey = "my-stream-api-key";
const token = "<token-from-your-server>";
const tokenProvider = async () => fetchTokenFromYourServer(user.id);
const client = new StreamVideoClient({ apiKey, user, token, tokenProvider });

Device persistence

Device preferences are persisted by default on web. Configure or disable this with options.devicePersistence when creating the client. See the Camera & Microphone guide.

Reject incoming call when busy

Auto-reject incoming ringing calls when the user is already in a call:

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

const client = new StreamVideoClient({
  apiKey,
  tokenProvider,
  user,
  options: { rejectCallWhenBusy: true },
});

Logging

The SDK uses scoped logging with configurable levels per module. Log levels ordered by severity:

LevelSeverity
trace0
debug1
info2
warn3
error4
The SDK only logs errors that aren't re-thrown. API errors should be handled in your integration code.
import { StreamVideoClient, Logger } from "@stream-io/video-react-sdk";

const client = new StreamVideoClient({
  apiKey,
  token,
  user,
  options: {
    logOptions: {
      default: {
        level: "info",
      },
      coordinator: {
        level: "warn",
        sink: (logLevel, message, ...rest) => {
          switch (logLevel) {
            case "warn": {
              console.warn(message, ...rest);
              break;
            }
            case "error": {
              SuperLogger.error(message, ...rest);
            }
          }
        },
      },
    },
  },
});

Configure the default scope to apply settings globally:

import { videoLoggerSystem } from "@stream-io/video-client";

import SuperLogger from "./SuperLogger";

videoLoggerSystem.configureLoggers({
  default: {
    level: "info",
    sink: (logLevel, message, ...rest) => {
      SuperLogger[logLevel](message, ...rest);
    },
  },
});

Reset specific scopes to defaults:

import { videoLoggerSystem } from "@stream-io/video-client";

videoLoggerSystem.configureLoggers({
  "event-dispatcher": {
    level: null,
    sink: null,
  },
});

Reset all scopes to defaults:

import { videoLoggerSystem } from "@stream-io/video-client";

videoLoggerSystem.restoreDefaults();

Sentry integration

Example Sentry integration:

import { LogLevel, Sink, logToConsole } from "@stream-io/video-client";
import * as Sentry from "@sentry/nextjs";

const logLevelMapping = new Map<LogLevel, Sentry.SeverityLevel>();
logLevelMapping.set("debug", "debug");
logLevelMapping.set("info", "info");
logLevelMapping.set("warn", "warning");
logLevelMapping.set("error", "error");

export const customSentryLogger: Sink = (
  logLevel: LogLevel,
  message: string,
  ...args: unknown[]
) => {
  if (logLevel === "warn" || logLevel === "error") {
    Sentry.captureEvent({
      level: logLevelMapping.get(logLevel),
      extra: args,
    });
  }

  // Call the SDK's default log method
  logToConsole(logLevel, message, { data: "some data" });
};

StreamVideo context provider

The StreamVideo provider makes the client available throughout your app:

import { useEffect, useState } from "react";
import {
  StreamVideo,
  StreamVideoClient,
  User,
} from "@stream-io/video-react-sdk";

const apiKey = "my-stream-api-key";
const user: User = { id: "sara", name: "Sara" };

export const MyApp = () => {
  const [client, setClient] = useState<StreamVideoClient>();
  useEffect(() => {
    const tokenProvider = () => Promise.resolve("<token>");
    const myClient = new StreamVideoClient({ apiKey, user, tokenProvider });
    setClient(myClient);
    return () => {
      myClient.disconnectUser();
      setClient(undefined);
    };
  }, []);

  if (!client) return null;

  return (
    <StreamVideo client={client}>
      <MyVideoApp />
    </StreamVideo>
  );
};