React SDK for real-time AI avatar interactions with GWM-1.
- React 18+
- A Runway API secret (get one here)
- A server-side endpoint to create sessions (API secrets must not be exposed to the client)
npm install @runwayml/avatars-reactAdd an avatar call to your app with just a few lines:
import { AvatarCall } from '@runwayml/avatars-react';
function App() {
return (
<AvatarCall
avatarId="music-superstar"
connectUrl="/api/avatar/connect"
/>
);
}That's it! The component handles session creation, WebRTC connection, and renders a default UI with the avatar video and controls.
You can use preset avatars like music-superstar, cat-character, fashion-designer, cooking-teacher, and more. See the Runway Developer Portal for the full list and creating custom avatars.
Import the optional stylesheet for a polished look out of the box:
import '@runwayml/avatars-react/styles.css';The styles use CSS custom properties for easy customization:
:root {
--avatar-bg: #a78bfa; /* Video background color */
--avatar-radius: 16px; /* Container border radius */
--avatar-control-size: 48px; /* Control button size */
--avatar-end-call-bg: #ef4444; /* End call button color */
}See examples/ for complete working examples:
nextjs- Next.js App Routernextjs-server-actions- Next.js with Server Actionsreact-router- React Router v7 framework modeexpress- Express + Vite
Scaffold an example with one command:
npx degit runwayml/avatars-sdk-react/examples/nextjs my-avatar-app
cd my-avatar-app
npm install- Client calls your server endpoint with the
avatarId - Server uses your Runway API secret to create a realtime session via
@runwayml/sdk - Server polls until the session is ready, then returns
sessionIdandsessionKeyto the client - Client establishes a WebRTC connection for real-time video/audio
This flow keeps your API secret secure on the server while enabling low-latency communication.
Your server endpoint receives the avatarId and returns session credentials. Use @runwayml/sdk to create and poll the session:
// /api/avatar/connect (Next.js App Router example)
import Runway from '@runwayml/sdk';
const client = new Runway(); // Uses RUNWAYML_API_SECRET env var
export async function POST(req: Request) {
const { avatarId } = await req.json();
const { id: sessionId } = await client.realtimeSessions.create({
model: 'gwm1_avatars',
avatar: { type: 'runway-preset', presetId: avatarId },
});
// Poll until the session is ready
const deadline = Date.now() + 30_000;
while (Date.now() < deadline) {
const session = await client.realtimeSessions.retrieve(sessionId);
if (session.status === 'READY') {
return Response.json({ sessionId, sessionKey: session.sessionKey });
}
await new Promise((resolve) => setTimeout(resolve, 1_000));
}
return Response.json({ error: 'Session creation timed out' }, { status: 504 });
}For more control over the connection flow:
<AvatarCall
avatarId="music-superstar"
connect={async (avatarId) => {
const res = await fetch('/api/avatar/connect', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ avatarId }),
});
return res.json();
}}
/>Use the built-in components for custom layouts:
import { AvatarCall, AvatarVideo, ControlBar, UserVideo } from '@runwayml/avatars-react';
<AvatarCall avatarId="music-superstar" connectUrl="/api/avatar/connect">
<div className="call-layout">
<AvatarVideo className="avatar" />
<UserVideo className="self-view" />
<ControlBar className="controls" />
</div>
</AvatarCall>All components support render props for complete control:
<AvatarVideo>
{({ hasVideo, isConnecting, trackRef }) => (
<div>
{isConnecting && <Spinner />}
{hasVideo && <VideoTrack trackRef={trackRef} />}
</div>
)}
</AvatarVideo>Style connection states with CSS:
<AvatarCall avatarId="music-superstar" connectUrl="/api/avatar/connect" className="my-avatar" />.my-avatar[data-state="connecting"] {
opacity: 0.5;
}
.my-avatar[data-state="error"] {
border: 2px solid red;
}
.my-avatar[data-state="connected"] {
border: 2px solid green;
}<AvatarCall
avatarId="music-superstar"
connectUrl="/api/avatar/connect"
onEnd={() => console.log('Call ended')}
onError={(error) => console.error('Error:', error)}
/>The avatar can see your webcam feed or screen share, enabling visual interactions — show a plant for identification, hold up a Pokémon card for trivia, get real-time coaching while you play a game, walk through a presentation, or ask for feedback on a design you're working on.
Compatibility: Webcam and screen sharing are supported by all preset avatars and custom avatars that use a preset voice. Custom avatars with a custom voice do not support webcam or screen sharing.
The webcam is enabled by default. The video prop controls whether the camera activates on connect, and the <UserVideo> component renders the local camera feed:
<AvatarCall avatarId="music-superstar" connectUrl="/api/avatar/connect">
<AvatarVideo />
<UserVideo />
<ControlBar />
</AvatarCall>To disable the webcam, set video={false}:
<AvatarCall avatarId="music-superstar" connectUrl="/api/avatar/connect" video={false} />Enable the screen share button by passing showScreenShare to ControlBar, and use <ScreenShareVideo> to display the shared content:
<AvatarCall avatarId="music-superstar" connectUrl="/api/avatar/connect">
<AvatarVideo />
<ScreenShareVideo />
<ControlBar showScreenShare />
</AvatarCall>You can also start screen sharing automatically by passing a pre-captured stream via initialScreenStream. This is useful when you want to prompt the user for screen share permission before the session connects:
function ScreenShareCall() {
const [stream, setStream] = useState<MediaStream | null>(null);
async function startWithScreenShare() {
const mediaStream = await navigator.mediaDevices.getDisplayMedia({ video: true });
setStream(mediaStream);
}
if (!stream) {
return <button onClick={startWithScreenShare}>Share Screen & Start Call</button>;
}
return (
<AvatarCall
avatarId="music-superstar"
connectUrl="/api/avatar/connect"
initialScreenStream={stream}
>
<AvatarVideo />
<ScreenShareVideo />
<ControlBar showScreenShare />
</AvatarCall>
);
}Use the useLocalMedia hook for full programmatic control over camera and screen sharing:
function MediaControls() {
const {
isCameraEnabled,
isScreenShareEnabled,
toggleCamera,
toggleScreenShare,
} = useLocalMedia();
return (
<div>
<button onClick={toggleCamera}>{isCameraEnabled ? 'Hide Camera' : 'Show Camera'}</button>
<button onClick={toggleScreenShare}>{isScreenShareEnabled ? 'Stop Sharing' : 'Share Screen'}</button>
</div>
);
}Use hooks for custom components within an AvatarCall or AvatarSession:
Access session state and controls:
function MyComponent() {
const { state, sessionId, error, end } = useAvatarSession();
if (state === 'connecting') return <Loading />;
if (state === 'error') return <Error message={error.message} />;
return <button onClick={end}>End Call</button>;
}Access the remote avatar's video:
function CustomAvatar() {
const { videoTrackRef, hasVideo } = useAvatar();
return (
<div>
{hasVideo && <VideoTrack trackRef={videoTrackRef} />}
</div>
);
}Control local camera, microphone, and screen sharing:
function MediaControls() {
const {
isMicEnabled,
isCameraEnabled,
isScreenShareEnabled,
toggleMic,
toggleCamera,
toggleScreenShare,
} = useLocalMedia();
return (
<div>
<button onClick={toggleMic}>{isMicEnabled ? 'Mute' : 'Unmute'}</button>
<button onClick={toggleCamera}>{isCameraEnabled ? 'Hide' : 'Show'}</button>
<button onClick={toggleScreenShare}>{isScreenShareEnabled ? 'Stop Sharing' : 'Share Screen'}</button>
</div>
);
}For full control over session management, use AvatarSession directly with pre-fetched credentials:
import { AvatarSession, AvatarVideo, ControlBar } from '@runwayml/avatars-react';
function AdvancedUsage({ credentials }) {
return (
<AvatarSession
credentials={credentials}
audio={true}
video={true}
onEnd={() => console.log('Ended')}
onError={(err) => console.error(err)}
>
<AvatarVideo />
<ControlBar />
</AvatarSession>
);
}| Component | Description |
|---|---|
AvatarCall |
High-level component that handles session creation |
AvatarSession |
Low-level wrapper that requires credentials |
AvatarVideo |
Renders the remote avatar video |
UserVideo |
Renders the local user's camera |
ControlBar |
Media control buttons (mic, camera, screen share, end call) |
ScreenShareVideo |
Renders screen share content |
AudioRenderer |
Handles avatar audio playback |
All components and hooks are fully typed:
import type {
AvatarCallProps,
SessionCredentials,
SessionState,
} from '@runwayml/avatars-react';This SDK uses WebRTC for real-time communication. Supported browsers:
- Chrome 74+
- Firefox 78+
- Safari 14.1+
- Edge 79+
Users must grant camera and microphone permissions when prompted.
"Failed to connect" or timeout errors
- Verify your server endpoint is returning the correct credential format
- Check that
RUNWAYML_API_SECRETis set correctly on your server
No video/audio
- Ensure the user has granted camera/microphone permissions
- Check browser console for WebRTC errors
- Verify the device has a working camera/microphone
CORS errors
- Your server endpoint must accept requests from your client's origin
- For local development, ensure both client and server are on compatible origins
This SDK includes an Agent Skill that teaches AI agents how to use the Runway Avatar SDK effectively. Install it with:
npx skills add runwayml/avatars-sdk-reactOnce installed, AI coding assistants like Claude Code, Cursor, Cline, and others will have access to SDK documentation, patterns, and best practices when helping you build avatar-powered applications.
MIT