This tutorial teaches you how to build a Zoom/Whatsapp-style video calling app.
- All calls run on Stream's global edge network for optimal latency & reliability.
- Configuring permissions gives you fine-grained control over who can do what.
- Video quality and codecs are automatically optimized.
- The calling support is powered by Stream's Video Calling API.

This page works for both humans and AI coding agents: every step is a file operation or a shell command, every step has a verification checkpoint, and the few steps that need a human are explicitly marked.
Before you start. Emulators have no camera, so use a physical device; the first iOS device build also needs a signing team. You won't need a second phone - Step 6 lets you join the call from a browser tab.
Choose your path
This tutorial has two paths. In Path A you let your AI agent build the app for you. In Path B you build it yourself, step by step. Both paths end with the same working video call, so pick the one you prefer instead of working through both.
Path A - Let your AI agent build it (recommended)
Install the Stream CLI once, then add the skills. This gives Claude Code, Cursor, or Codex the React Native integration patterns and current SDK APIs, so it builds against real docs instead of stale training data.
1234567# Install the getstream CLI curl -fsSL https://getstream.io/cli.sh | bash # Install the skills (router, docs, and builder). The React Native pack # installs on demand the first time it's needed, or add it explicitly: getstream skills getstream skills stream-react-native
Then ask your agent:
12/stream-react-native Build a React Native video calling app with the prebuilt call UI and call controls.
Where you come in. The agent handles credentials and code. It stops three times: getstream init opening your browser to sign in and pick an app (new accounts get an organization in that flow), the build to a device, and the camera and microphone prompt.
Want to get the sign-in out of the way first? Run getstream init before you prompt the agent - it picks up the initialized project from there.
Human checkpoint: build the app to a device if the agent hasn't, then allow camera and microphone access. You should join the call and see your own video.
Skim Important Building Blocks if you want to understand what the agent wrote before you extend it.
Path B - Build it manually
Choose this path if you'd rather write the code yourself, or you want to understand what the SDK is doing before you hand it to an agent. Follow the steps below.
For AI assistants reading this page: append
.mdto any Stream docs URL for a clean Markdown version. A condensed index for the React Native Video SDK is athttps://getstream.io/video/docs/react-native/llms.txt. Prefer these over parsing HTML.
Important Building Blocks
Four pieces, one mental model:
StreamVideoClient- the low-level client. Holds the API key, the user, the token and the websocket connection to the Stream Video service.Call- a single call, created withclient.call(type, id).call.join()is what actually sets up audio and video.- Providers -
<StreamVideo client={client} />puts the client in React context,<StreamCall call={call} />puts one call in context. Everything below them reads from those. - Call state hooks -
useCallStateHooks()hands you the hooks (useParticipantCount,useDominantSpeaker, and the rest) that drive the UI.
End to end, that looks like this:
12345678910111213141516171819202122232425262728293031import { StreamVideoClient } from "@stream-io/video-react-native-sdk"; const apiKey = "REPLACE_WITH_API_KEY"; const user = { id: "REPLACE_WITH_USER_ID", name: "REPLACE_WITH_USER_NAME", image: "REPLACE_WITH_USER_IMAGE", }; const token = "REPLACE_WITH_USER_TOKEN"; const callId = "REPLACE_WITH_CALL_ID"; // create a client instance const client = new StreamVideoClient({ apiKey, user, token }); // Alternatively, you can also choose to separate client creation and user connection: // const client = new StreamVideoClient({ apiKey }); // await client.connectUser(user, token); // create a call instance const call = client.call("default", callId); await call.join({ create: true, // create the call if it doesn't exist data: { members: [{ user_id: "john_smith" }, { user_id: "jane_doe" }], // custom data set on call custom: { title: "React Native test", description: "Conducting a test of React Native video calls", }, }, });
The client is created once, at sign-in. The call is created per call screen. Both are handed to the rest of your app through the two providers:
1234567891011import { StreamVideo, StreamCall } from "@stream-io/video-react-native-sdk"; <StreamVideo client={client}> ... <StreamCall call={call}> <View> <Text>Video Call UI</Text> </View> </StreamCall> ... </StreamVideo>;
You'll build a bare call screen first, confirm it connects, then swap in the prebuilt UI.
Step 1 - Create a New React Native App
First, set up your React Native environment. Then generate a project called "VideoCallExample" with the React Native Community CLI:
12npx @react-native-community/cli@latest init VideoCallExample --pm yarn cd VideoCallExample
The React Native CLI installs the iOS CocoaPods dependencies during
init. If you skipped that prompt, or a later iOS build fails to find a pod, (re)install them:
cd iosto navigate to theiosfolderbundle installto install Bundlerbundle exec pod installto install the iOS dependencies managed by CocoaPods
Checkpoint: you have a VideoCallExample directory and yarn ios or yarn android builds the React Native starter screen.
Step 2 - Install the SDK and Declare Permissions
To install the Stream Video React Native SDK, run the following command in your terminal of choice:
1yarn add @stream-io/video-react-native-sdk @stream-io/react-native-webrtc
The SDK requires installing some peer dependencies. You can run the following command to install them:
12345yarn add react-native-svg yarn add @react-native-community/netinfo # Install pods for iOS npx pod-install
Declare Permissions
The video calling app we built in this tutorial requires permission to access the users' camera, microphone, and network state.
In AndroidManifest.xml add the following permissions before the application section.
1234567891011121314151617<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <uses-feature android:name="android.hardware.audio.output" /> <uses-feature android:name="android.hardware.microphone" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.INTERNET" /> <!-- ... --> <application> <!-- ... --> </application> </manifest>
In android/build.gradle, confirm minSdkVersion is at least 24 inside the buildscript section. React Native 0.86 templates already set this, so there is usually nothing to change:
1234567buildscript { ext { // ... minSdkVersion = 24 } // ... }
Add the following keys and values to the Info.plist file, under the dict tag.
123456789101112<plist version="1.0"> <dict> ... <key>CFBundleName</key> <string>$(PRODUCT_NAME)</string> <key>NSCameraUsageDescription</key> <string>$(PRODUCT_NAME) would like to use your camera</string> <key>NSMicrophoneUsageDescription</key> <string>$(PRODUCT_NAME) would like to use your microphone</string> ... </dict> </plist>
Run the App
Emulators have no camera, so run on a physical device (how to). The first iOS device build needs a signing team: in Xcode, under Signing & Capabilities, select your Team and change the bundle identifier if the default is taken.
Human checkpoint: the app builds and launches on your device. Agents: run the build, then ask the human to confirm it launched.
Step 3 - Get Your Credentials
The code in Step 4 needs four values:
apiKeyidentifies your Stream Video application to our servers.useris the user object, for example{ id: "john_smith", name: "John Smith" }.tokenauthorizes that user. Your server-side API mints it on sign-in; the Client & Authentication guide covers the production shape.callIdis anything URL-safe. Calls are created the first time somebody joins withcreate: true, so there's nothing to provision up front.
There are two ways to get them.
Option 1 - Your own Stream app, via the Stream CLI
The getstream CLI provisions all of it in one flow.
1. Install the CLI (skip if you did this in Path A). This installs globally, so it doesn't matter where you run it:
1curl -fsSL https://getstream.io/cli.sh | bash
2. Initialize the project. Run this and the remaining commands from the VideoCallExample directory you created in Step 1 - the CLI stores project credentials there. This one command authenticates you, lets you create or select an organization and app, and writes those credentials. New to Stream? The sign-in flow creates your organization. Already have an org or an app? It lets you pick them.
1getstream init
Human checkpoint: getstream init opens a browser to authenticate. Agents: run it, then ask the human to finish signing in before continuing. It's required first - token fails with "project credentials missing; run getstream init first" until it runs.
3. Mint a user token for a user in your app (never expiring by default; add a TTL for production-like testing):
12getstream token john_smith getstream token john_smith --ttl 1d
The CLI prints your API key alongside the token. Paste both into the constants in Step 4. (getstream env --target ios and --target android can write the key into the native projects instead, but the snippets here read a plain JavaScript constant, so pasting it is the shortest path.)
Checkpoint: you have an API key, a token, the user id you minted it for, and a call id you chose. All four belong to the same app.
Option 2 - Pre-filled tutorial credentials, no account
Want to skip account setup entirely? Every code block below marked with the lock icon is filled in for you with working credentials against Stream's shared tutorial environment. Copy the block as-is and it runs.
Swap in your own credentials from Option 1 before building anything real. Tutorial credentials are shared and short lived.
Step 4 - Setup a Starter UI
Start with a basic UI. A real app would use a navigation library like React Navigation; this tutorial mocks navigation with an activeScreen state variable to stay focused on the SDK.
Create a folder named src and add these files to it:
src/HomeScreen.tsxsrc/CallScreen.tsx(takescallIdas a prop)
Now copy the following content into the respective files (as mentioned in the header):
12345678910111213141516171819202122232425262728293031323334import React, { useState } from "react"; import { StyleSheet } from "react-native"; import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; import { HomeScreen } from "./src/HomeScreen"; import { CallScreen } from "./src/CallScreen"; const apiKey = "REPLACE_WITH_API_KEY"; const token = "REPLACE_WITH_TOKEN"; const userId = "REPLACE_WITH_USER_ID"; const callId = "REPLACE_WITH_CALL_ID"; export default function App() { const [activeScreen, setActiveScreen] = useState("home"); const goToCallScreen = () => setActiveScreen("call-screen"); const goToHomeScreen = () => setActiveScreen("home"); return ( <SafeAreaProvider> <SafeAreaView style={styles.container}> {activeScreen === "call-screen" ? ( <CallScreen goToHomeScreen={goToHomeScreen} callId={callId} /> ) : ( <HomeScreen goToCallScreen={goToCallScreen} /> )} </SafeAreaView> </SafeAreaProvider> ); } const styles = StyleSheet.create({ container: { flex: 1, }, });
12345678910111213141516171819202122232425262728import React from "react"; import { Button, StyleSheet, Text, View } from "react-native"; type Props = { goToHomeScreen: () => void; callId: string }; export const CallScreen = ({ goToHomeScreen, callId }: Props) => { return ( <View style={styles.container}> <Text style={styles.text}>Here we will add Video Calling UI</Text> <Button title="Go back" onPress={goToHomeScreen} /> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: "center", backgroundColor: "white", }, text: { color: "black", fontSize: 20, fontWeight: "bold", marginBottom: 20, textAlign: "center", }, });
123456789101112131415161718192021222324252627282930import React from "react"; import { Text, Button, StyleSheet, View } from "react-native"; type Props = { goToCallScreen: () => void; }; export const HomeScreen = ({ goToCallScreen }: Props) => { return ( <View style={styles.container}> <Text style={styles.text}>Welcome to Video Calling Tutorial</Text> <Button title="Join Video Call" onPress={goToCallScreen} /> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: "center", backgroundColor: "white", }, text: { color: "black", fontSize: 20, fontWeight: "bold", marginBottom: 20, textAlign: "center", }, });
Fill in apiKey, userId, token and callId from Step 3. They're hard-coded to keep the tutorial short; in production your backend returns the token at sign-in and the API key comes from environment config.
You should see on your device the following UI.


Checkpoint: the app shows the home screen with a "Join Video Call" button, and tapping it switches to the placeholder call screen.
Step 5 - Setup the Video Client
Now create the StreamVideoClient and connect the user. For brevity this tutorial creates the client at module scope; in a real app create it inside a useEffect (or memoize it with useState) and call client.disconnectUser() on unmount, so you don't open multiple websockets.
Pass the client to StreamVideo, which shares it with every child component through React Context. It goes at the top of the component tree.
1234567891011121314151617181920212223242526... import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; import { StreamVideo, StreamVideoClient, } from '@stream-io/video-react-native-sdk'; ... const user = { id: userId, name: 'John Malkovich', image: 'https://robohash.org/John', }; const client = new StreamVideoClient({ apiKey, user, token }); export default function App() { ... return ( <SafeAreaProvider> <StreamVideo client={client}> <SafeAreaView style={styles.container}> ... </SafeAreaView> </StreamVideo> </SafeAreaProvider> ); }
The UI won't change yet, because you haven't joined a call. To confirm the user connected, drop this probe component anywhere inside StreamVideo (it renders the user's id - rendering the object itself throws "Objects are not valid as a React child"):
1234567import { Text } from 'react-native'; import { useConnectedUser } from '@stream-io/video-react-native-sdk'; const ConnectedUserProbe = () => { const connectedUser = useConnectedUser(); return <Text>Connected as: {connectedUser?.id ?? 'nobody yet'}</Text>; };
Checkpoint: the probe shows your user id rather than nobody yet. Delete it once you've seen it.
Step 6 - Create & Join a Call
Now create and join a call. The call goes into a state variable and gets passed to StreamCall, which provides the hooks that drive the call UI.
Update src/CallScreen.tsx as follows. As in the other steps, ... marks unchanged code - this is a merge, not a whole-file replacement:
1234567891011121314151617181920212223... import { Call, StreamCall } from '@stream-io/video-react-native-sdk'; ... export const CallScreen = ({ goToHomeScreen, callId }: Props) => { const [call, setCall] = React.useState<Call | null>(null); if (!call) { return ( <View style={styles.container}> <Text style={styles.text}>Joining call...</Text> </View> ); } return ( <StreamCall call={call}> <View style={styles.container}> <Text style={styles.text}>Here we will add Video Calling UI</Text> <Button title="Go back" onPress={goToHomeScreen} /> </View> </StreamCall> ); };
As covered in Important Building Blocks, a call is created or accessed with client.call(...), so CallScreen needs the client. The useStreamVideoClient hook returns it.
Put the joining logic in a useEffect so the call is joined automatically when the user lands on CallScreen.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354import React, {useEffect} from 'react'; import { ... useStreamVideoClient, useCallStateHooks, CallingState, } from '@stream-io/video-react-native-sdk'; export const CallScreen = ({goToHomeScreen, callId}: Props) => { const [call, setCall] = React.useState<Call | null>(null); const client = useStreamVideoClient(); useEffect(() => { const _call = client?.call('default', callId); _call?.join({ create: true }) .then(() => setCall(_call)) .catch((error) => console.error('Failed to join the call', error)); }, [client, callId]); useEffect(() => { return () => { // cleanup the call on unmount if the call was not left already if (call?.state.callingState !== CallingState.LEFT) { call?.leave(); } }; }, [call]); if (!call) { return ( <View style={styles.container}> <Text style={styles.text}>Joining call...</Text> </View> ); } return ( <StreamCall call={call}> <View style={styles.container}> <Text style={styles.text}>Here we will add Video Calling UI</Text> <Button title="Go back" onPress={goToHomeScreen} /> <ParticipantCountText /> </View> </StreamCall> ); }; const ParticipantCountText = () => { const {useParticipantCount} = useCallStateHooks(); const participantCount = useParticipantCount(); return ( <Text style={styles.text}>Call has {participantCount} participants</Text> ); };
StreamCall is what makes the call state available to everything below it. useCallStateHooks() returns the individual state hooks - useParticipantCount above, and many more - and each one re-renders your component when that piece of state changes.
You don't need a second phone to test this. Join the same call from your browser:
- Refresh the app and tap "Join Video Call".
- Click "Join Call" below to open the same call in a browser tab.
- The browser opens a lobby screen first - click Join there too. The count only moves after that second click, and the browser signs you in under a generated guest name rather than the user id in the link.
The mobile app updates to Call has 2 participants. Keep that browser tab open for the rest of the tutorial.
Checkpoint: the participant count reads 2. The button joins the shared tutorial call, so on your own credentials from Option 1, open a second browser profile against your own app with a different user id and the same call id.
Step 7 - Render the Video Calling UI
Now add the participant view, which renders each participant's video and audio along with the controls for muting, switching camera, and hanging up.
CallContent handles all of this for you:
- Indicators to visualize when someone is speaking.
- The quality of call participants' network.
- Layout support for multiple participants.
- Labels for the participants' names and the media stream's on/off status.
- A floating local video view.
- Buttons to toggle audio/video and to flip the camera.
- A button to end/terminate the call.
Update the CallScreen component as follows. The three red lines are being replaced by CallContent - delete them, along with the now-unused Button import and the ParticipantCountText component at the bottom of the file, or yarn lint will flag them later:
123456789101112-13-14-15+16+17+181920212223... import { ... CallContent, } from '@stream-io/video-react-native-sdk'; ... export const CallScreen = ({ goToHomeScreen, callId }: Props) => { ... return ( <StreamCall call={call}> <View style={styles.container}> <Text style={styles.text}>Here we will add Video Calling UI</Text> <Button title="Go back" onPress={goToHomeScreen} /> <ParticipantCountText /> <CallContent onHangupCallHandler={goToHomeScreen} /> </View> </StreamCall> ); }; ...
Run the app and you'll see your own video in a floating tile, the video from your browser tab behind it, and the control buttons along the bottom:

Human checkpoint: the device asks for camera and microphone access the first time - allow both. Agents: build and launch, then ask the human to grant access and confirm both video streams render.
This is the payoff: a working two-way video call. On the shared tutorial credentials? Create your own Stream app - the free maker plan covers hobby projects, and Step 3 swaps the credentials over in two commands.
Step 8 - Customize the Calling UI
Three ways to customize, in increasing order of effort: theming for colors and fonts, mixing your own components in alongside Stream's, or building the tiles and controls from scratch.
Start by passing your own component to CallContent:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455... import { ... StreamCall, CallControlProps, HangUpCallButton, ToggleAudioPublishingButton as ToggleMic, ToggleVideoPublishingButton as ToggleCamera, useCall, useStreamVideoClient, } from '@stream-io/video-react-native-sdk'; const CustomCallControls = (props: CallControlProps) => { const call = useCall(); return ( <View style={styles.customCallControlsContainer}> <ToggleMic onPressHandler={() => call?.microphone.toggle()} /> <ToggleCamera onPressHandler={() => call?.camera.toggle()} /> <HangUpCallButton onHangupCallHandler={props.onHangupCallHandler} /> </View> ); }; ... export const CallScreen = ({goToHomeScreen, callId}: Props) => { ... return ( <StreamCall call={call}> <View style={styles.container}> <CallContent onHangupCallHandler={goToHomeScreen} CallControls={CustomCallControls} /> </View> </StreamCall> ); }; const styles = StyleSheet.create({ ... customCallControlsContainer: { position: 'absolute', bottom: 40, paddingVertical: 10, width: '80%', marginHorizontal: 20, flexDirection: 'row', alignSelf: 'center', justifyContent: 'space-around', backgroundColor: 'orange', borderRadius: 10, borderColor: 'black', borderWidth: 5, zIndex: 5, }, });

Next, combine CallContent with a custom top bar that reads call state hooks to show:
- the participants currently in the call
- the name of the dominant speaker
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657import { ... useStreamVideoClient, useCallStateHooks, } from '@stream-io/video-react-native-sdk'; ... const CustomTopView = () => { const {useParticipants, useDominantSpeaker} = useCallStateHooks(); const participants = useParticipants(); const dominantSpeaker = useDominantSpeaker(); return ( <View style={styles.topContainer}> <Text ellipsizeMode="tail" numberOfLines={1} style={styles.topText}> Video Call between {participants.map(p => p.name).join(', ')} </Text> {dominantSpeaker?.name && ( <Text style={styles.topText}> Dominant Speaker: {dominantSpeaker?.name} </Text> )} </View> ); }; export const CallScreen = ({goToHomeScreen, callId}: Props) => { ... return ( <StreamCall call={call}> <View style={styles.container}> <CustomTopView/> <CallContent onHangupCallHandler={goToHomeScreen} CallControls={CustomCallControls} /> </View> </StreamCall> ); }; const styles = StyleSheet.create({ ... topContainer: { width: '100%', height: 50, backgroundColor: 'black', justifyContent: 'center', alignItems: 'center', }, topText: { color: 'white', }, });

For colors, fonts and icons, pass a theme to the style prop on StreamVideo. The theme file lists every property you can override.
12345678910111213141516171819202122232425262728293031323334import React, {useMemo, useState} from 'react'; ... export default function App() { ... // Avoid passing inline styles to the component, as it will cause unnecessary re-renders const theme = useMemo( () => ({ callControlsButton: { container: { borderRadius: 10, }, }, hangupCallButton: { container: { backgroundColor: 'blue', }, }, toggleAudioPublishingButton: { container: { backgroundColor: 'green', }, }, }), [], ); ... return ( <StreamVideo client={client} style={theme}> ... </StreamVideo> ); ... }

Checkpoint: the control bar sits in your orange container, the top bar shows participant names and the dominant speaker, and the buttons use your theme colors.
Verify the whole build
Build to your device and run the full loop: tap "Join Video Call", allow camera and microphone, join from a browser tab, and check both video tiles render. Toggle your mic and watch the indicator update in the browser. Hang up and confirm you land back on the home screen.
Customize further
The UI components reference lists everything the SDK ships with. Common next customizations:
- Call content and layouts - swap between grid and spotlight arrangements
- Participant view - change what renders inside each participant tile
- Call controls - replace the default button row
- Camera and microphone - device selection and speaker management
- Screen sharing and picture-in-picture
- Noise cancellation - reduce background noise from participants' microphones
Troubleshooting
useConnectedUser()stays undefined - the client never connected. Check the console for an auth error, which usually means the API key and token belong to different apps.token is invalid/ auth error - the token was minted for a different app, or it expired. Re-mint withgetstream token <user-id>and confirm the API key matches.project credentials missing- the CLI has not been initialized here. Rungetstream initin the project directory first.- No video, only a black tile - you're on an emulator. Emulators have no camera; build to a physical device.
- iOS build fails to find a pod - run
bundle installthenbundle exec pod installfrom theiosfolder, ornpx pod-installfrom the project root. - Android build fails on minSdkVersion - confirm
minSdkVersionis at least 24 in thebuildscript.extblock ofandroid/build.gradle(RN 0.86 templates already set it). - iOS device build fails to sign - open the project in Xcode, pick a Team under Signing & Capabilities, and change the bundle identifier if the default is taken.
- Camera and mic permission prompts never appear - the usage description keys are missing from
Info.plist, or the permissions are missing fromAndroidManifest.xml(Step 2). - Participant count stays at 1 - two sessions signed in as the same user count as one participant. Use a different user id in the second session.
- Stuck on "Joining call..." -
call.join()rejected. The.catch()in theuseEffectlogs the reason to the console.
Recap
Please let us know if you encounter issues building a video calling app with our React Native SDK. Our team is also happy to review your UI designs and advise you on how to integrate them with Stream.
To recap what we've learned about Stream's video calling service:
- You set up a call: (
const call = client.call("default", "your-call-id")). - The call type ("default" in the above case) controls which features are enabled and how permissions are set.
- When you join a call, real-time communication is set up for audio & video calling: (
call.join()). - State-related hooks such as
useParticipantCountanduseDominantSpeakermake it easy to build your UIs. - The
CallContentcomponent renders audio and video and adds buttons to control streaming options.
Using the Stream's Video Calling API, all calls run on a global edge network of video servers closer to all users. Being closer to your users improves call latency and reliability. The React Native SDK enables you to build in-app video calling, audio rooms, and live streaming in days.
We hope you've enjoyed this tutorial. Please feel free to reach out if you have any suggestions or questions.
Next steps
- Ringing and push notifications - make calls ring on the recipient's device, even when the app is closed: React Native Ringing tutorial
- Livestreaming - broadcast to an unlimited audience: React Native Livestreaming tutorial
- Audio rooms - a Clubhouse-style experience with backstage and raised hands: React Native Audio Room tutorial
- AI voice assistant - connect a realtime voice agent to a call: React Native Voice Assistant tutorial
- Chat in your call - the Chat React Native SDK drops into the same app
- Recording and HLS - recording and broadcasting
Pricing is flexible with multiple tiers, including a free maker plan for hobby projects and small companies.
Final Thoughts
In this video calling app tutorial we built a fully functioning React Native video app with our React Native SDK component library. We also showed how easy it is to customize the behavior and the style of the React Native video app components with minimal code changes.
Both the video SDK for React Native and the API have plenty more features available to support more advanced use cases.
Machine-readable resources
For AI agents and coding assistants working with this SDK:
- CLI + skills:
curl -fsSL https://getstream.io/cli.sh | bash, thengetstream skillsandgetstream skills stream-react-nativefor the React Native pack. (Alternative:npx skills add GetStream/agent-skills -s stream.) - Provisioning:
getstream init(auth + create/select org & app) ->getstream token <user-id>(mint a token).getstream env --target <expo|ios|android>writes keys into a project file where the platform has a convention for it. - Data & config from the CLI:
getstream api <Endpoint> --request '{...}'for users, calls, and call members - React Native integration skill: invoke
/stream-react-nativein your agent for React Native and Expo setup patterns;/stream-docssearches live SDK docs with citations - Docs index for LLMs:
https://getstream.io/video/docs/react-native/llms.txt - Markdown endpoints: append
.mdto any docs URL for a clean, token-efficient version - Source of truth for APIs: the SDK repository - check the installed version's source rather than assuming APIs from training data

