Header Logo

How to Pass Avatar Appearance Settings from React to an Agora Streaming Session

How to Pass Avatar Appearance Settings from React to an Agora Streaming Session

Pass avatar appearance settings from React to Agora sessions via validated session config and backend bootstrap.

Introduction


If you’re streaming an avatar from React into a realtime session, the first thing that tends to go wrong is state ownership. The UI has a nice set of appearance controls, but the streaming session is running elsewhere: in a voice agent, a browser iframe, or a server-side media pipeline. If you don’t define a clean handoff, you end up with duplicated state, stale props, or an avatar that reconnects with the wrong look.


This post shows a practical pattern for passing avatar appearance settings from React to an Agora-backed streaming session: keep appearance as a serializable config object, validate it at the boundary, send it to your session bootstrap path, and make sure the streaming side treats it as immutable session metadata. By the end, you should know how to wire the React form, how to forward the settings into the session creation step, and what to watch for when the session is already live.


Model the appearance as session configuration, not component state


The main mistake is treating avatar appearance like local UI state only. In React, you might have controls for hairstyle, clothing, background, or lighting. But the streaming session needs a single source of truth that can be passed across process boundaries. For that reason, model the appearance as a plain JSON object and treat React as the authoring surface.


A good shape is something like this:


{
}
{
}
{
}


Keep this structure stable. Avoid passing derived UI state such as “selectedHairLabel” or “isThemeDark”. Those are presentation details. The streaming session should receive only the canonical values it needs to render the avatar consistently.


In practice, this means the React app does two jobs:


  1. Collect and validate appearance settings.

  2. Send those settings when creating or updating the streaming session.


That separation matters because the session may be initialized on the server, handed off to a WebRTC pipeline, and then run independently of the browser lifecycle.


Pass the settings from React at session creation time


The cleanest integration point is the moment you create the session. If you’re using Agora for streaming, your backend typically provisions the session or token, then the client connects with the returned session details. The appearance settings should be included in that initial server call, not shoved into the Agora client object later.


Here’s a minimal React-side example that gathers the config and sends it to your backend:


async function startSession(avatarId, appearance) {

}
async function startSession(avatarId, appearance) {

}
async function startSession(avatarId, appearance) {

}


The backend then becomes the boundary where you normalize and forward the settings into the streaming provider’s session bootstrap. That is where you should validate keys, map UI values to whatever the avatar service expects, and reject unsupported combinations early.


A few practical rules:


  • Use stable identifiers for option values, not display labels.

  • Serialize only data that should survive a reconnect.

  • Do not expose API keys in the browser.

  • Assume the session may outlive the React component that started it.


If you want the user to preview appearance changes before starting the stream, keep that entirely client-side. The preview can update instantaneously in React, but the “commit” to the actual session should be a single request with the final config.


Handle live updates deliberately, not implicitly


There are two different kinds of changes you might want to support:


  1. Pre-session changes: appearance changes before the stream starts.

  2. In-session changes: appearance changes while the stream is live.


Pre-session changes are easy: just update React state and submit the final config when starting the session. In-session changes are more delicate, because a realtime avatar pipeline may not support arbitrary visual mutation on the fly. Even when it does, those changes often trigger a refresh or a partial reinitialize of the media layer.


The safest pattern is to treat appearance as immutable for the lifetime of a session unless the API explicitly supports updates. If you need live switching, build it as a controlled operation:


  1. Send the new appearance config to your backend.

  2. Apply it through the session API or restart the session if required.

  3. Confirm the stream has acknowledged the update before showing the new state in UI.


That sounds conservative, but it avoids a class of bugs where the React UI says one thing while the streamed avatar is still rendering another. For realtime media, consistency beats cleverness.


Validate at the boundary and keep the contract explicit


Because the appearance object crosses from browser code into a server-controlled realtime session, validate it as early as possible. Don’t wait for the avatar provider to reject it with a vague error. Use a schema on the React side if you want fast feedback, then repeat the check on the backend as a security and correctness measure.


For example, if you’re using TypeScript, define a strict interface and only allow known values:


type Appearance = {
};
type Appearance = {
};
type Appearance = {
};


If you later add new appearance presets, your backend contract remains explicit. That matters when your session creation path fans out to a streaming service, a voice agent, and possibly analytics or billing. A small mismatch in a field name can become a hard-to-debug media issue.


Also consider versioning the contract if your app will evolve. A simple schema_version field can save you from backwards-compatibility problems when a saved avatar preset from three months ago no longer matches your current options.


How Protoface fits into this pattern


This is exactly the kind of boundary Protoface is designed to sit behind. You keep the appearance settings in React, send them to your backend, and create or manage the avatar session through the REST API or SDK rather than from the browser directly. The API keys stay server-side, the session metadata stays explicit, and the browser only handles the user-facing editor and session trigger.


If you prefer Python for the session orchestration layer, the SDK gives you a straightforward way to create sessions programmatically. The exact request fields depend on the avatar/session object model in the docs, but the flow looks like this:


from protoface import ProtofaceClient

)
from protoface import ProtofaceClient

)
from protoface import ProtofaceClient

)


If you’re integrating a voice agent, the LiveKit plugin path is similar in spirit: the agent owns the conversation loop, and the plugin attaches the avatar rendering layer so the speaker gains a synchronized face. For implementation details and current examples, see the public docs and the relevant quickstarts in the GitHub organization and docs.


One useful mental model: React owns the editor, your backend owns the contract, and Protoface owns the realtime avatar session. Each layer does one thing. That keeps the appearance settings portable whether you’re starting an Agora stream, a voice-agent session, or an embedded web experience.


Agora-specific gotchas


When Agora is the streaming transport, a few implementation details matter:


  • Token/session timing: generate or fetch the Agora session credentials after you’ve already validated the appearance config, so failures happen before media setup.

  • Reconnect behavior: if the client reconnects, make sure the server can reconstruct the session with the same appearance metadata.

  • Race conditions: don’t let the React component fire multiple start requests from rapid control changes; debounce or require an explicit “Start” action.

  • Media readiness: only mark the UI as “live” after both the Agora connection and the avatar session are ready.


A small state machine helps here. For example: editing → starting → live → updating → ended. The key is that appearance changes should move through the same backend-controlled path as session creation. Don’t couple them to transient UI events like mouse movement or slider drag unless you’re intentionally building a live preview.


If you’re orchestrating the session from Python, the same boundary applies on the server side, regardless of whether Agora is the last mile or one step in a broader pipeline.


Conclusion


Passing avatar appearance settings from React to a realtime streaming session is mostly an exercise in boundary design. Keep appearance as a plain, versionable config object; validate it before session creation; send it through your backend; and treat the live session as the authority once media starts flowing. That pattern works whether the avatar ends up in Agora, a voice-agent pipeline, or another realtime transport.


For concrete request shapes, session fields, and current integration examples, check the documentation. If you’re wiring this into a production app, start by implementing the session bootstrap path first, then add live-update support only if your use case truly needs it.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.