The 3D Avatar SDK
your app deserves.
Drop-in web component, React bindings, iframe creator, and cloud upload — a complete open avatar SDK, self-hostable, with no vendor lock-in.
<three-ws-viewer src="…">
Everything in one package
Four building blocks — use what you need, tree-shake the rest.
Web Component
Drop <agent-3d> or the lightweight <three-ws-viewer> into any HTML page or framework. Self-registers on import.
React Bindings
First-class React 18+ components — <Avatar>, <AgentAvatar>, <AvatarCreator> and a useAvatar(id) hook.
Creator Iframe
Open the three.ws Avatar Studio or Selfie Studio in a modal. Resolves with a GLB Blob via postMessage — no polling, no storage tokens required.
AvatarCreator classCloud Upload
One-call saveBlob() presigns to R2, uploads, registers the avatar, auto-generates USDZ for iOS and a half-body variant for VR.
Try it live
Configure the viewer, see the result in real time, copy the code.
Up in 60 seconds
Three steps from zero to a live 3D avatar in your page.
Install
Install the package and its peer dependency from npm. React is optional.
$ npm install @three-ws/avatar three
Add the web component
Import once to register the element. Then use <agent-3d> anywhere in your HTML — it handles the avatar by UUID or direct GLB URL.
<!-- load once from CDN / your bundler --> <script type="module" src="/avatar-sdk/dist/index.mjs"></script> <!-- by avatar UUID (resolved on the three.ws CDN) --> <agent-3d avatar-id="YOUR_AVATAR_UUID"></agent-3d> <!-- or by direct GLB URL --> <agent-3d src="https://example.com/avatar.glb"></agent-3d>
import { AgentAvatar } from '@three-ws/avatar/react'; export function MyPage() { return ( <AgentAvatar avatarId="YOUR_AVATAR_UUID" style={{ width: '400px', height: '600px' }} /> ); }
// Registers <agent-3d> as a side effect import '@three-ws/avatar'; // Or lazy-load only when you need it import { ensureAgent3D } from '@three-ws/avatar/agent'; await ensureAgent3D(); // All set — create it programmatically const el = document.createElement('agent-3d'); el.setAttribute('avatar-id', 'YOUR_AVATAR_UUID'); document.body.appendChild(el);
Let users build their avatar
Open the Creator modal — it wraps the Character Builder iframe. The onExport callback receives a GLB Blob you can pass straight to saveBlob().
import { AvatarCreator, saveBlob } from '@three-ws/avatar/creator'; const creator = new AvatarCreator({ onExport: async (glbBlob) => { const avatar = await saveBlob(glbBlob, { bearerToken: 'YOUR_THREE_WS_TOKEN', name: 'My Avatar', visibility: 'public', }); console.log('Saved at', avatar.url); }, }); creator.open();
<three-ws-viewer>
When you don't need the full agent runtime — chat, voice, lipsync — use the lightweight viewer. It only loads Three.js and OrbitControls. No WebSocket. No AI.
<script type="module"> import '@three-ws/avatar/viewer'; </script> <three-ws-viewer src="/avatars/hero.glb" background="#0e0e0e" alt="Hero avatar" style="width:400px;height:560px" ></three-ws-viewer>
Live — drag to orbit · scroll to zoom
AvatarCreator
Opens the three.ws Avatar Studio in a fullscreen modal. The user builds their avatar and clicks Export — you receive a GLB Blob in the callback. No OAuth, no API keys needed for the modal itself.
Click the button to open the Avatar Studio right here. After you export an avatar the file size will appear below.
import { AvatarCreator } from '@three-ws/avatar/creator'; const creator = new AvatarCreator({ // Defaults to https://three.ws/avatar-studio/ // Override to self-host. studioUrl: 'https://three.ws/avatar-studio/', onExport(glbBlob) { // glbBlob is a Blob of type model/gltf-binary const url = URL.createObjectURL(glbBlob); document .querySelector('agent-3d') .setAttribute('src', url); }, onClose() { console.log('user dismissed'); }, }); await creator.open();
React 18+ bindings
All four exports are typed and tree-shakeable. Marked 'use client' for Next.js App Router.
import { Avatar } from '@three-ws/avatar/react'; // Pure visual — wraps <three-ws-viewer>, no AI export function ProfileCard({ glbUrl }) { return ( <Avatar src={glbUrl} alt="User avatar" background="transparent" style={{ width: '240px', height: '320px' }} onLoad={({ url }) => console.log('loaded', url)} /> ); }
import { AgentAvatar } from '@three-ws/avatar/react'; // Full runtime — chat, voice, lipsync, skills export function AgentWidget({ avatarId }) { return ( <AgentAvatar avatarId={avatarId} kiosk // hides debug UI style={{ width: '100%', height: '600px' }} /> ); }
import { AvatarCreator } from '@three-ws/avatar/react'; export function OnboardingStep() { const [open, setOpen] = useState(false); return ( <> <button onClick={() => setOpen(true)}> Create my avatar </button> <AvatarCreator open={open} onExport={(blob) => handleBlob(blob)} onClose={() => setOpen(false)} /> </> ); }
import { useAvatar } from '@three-ws/avatar/react'; export function AvatarCard({ id }) { const { avatar, loading, error } = useAvatar(id); if (loading) return <Spinner />; if (error) return <p>Error: {error.message}</p>; return <img src={avatar.thumbnail_url} alt={avatar.name} />; }
Exports
Everything the package surfaces.
Full AI avatar runtime — chat loop, voice, lipsync, animation, skills. 3.5 MB bundle, lazy-loaded.
- avatar-idthree.ws avatar UUID — resolved via /api/avatars/:id
- srcDirect GLB URL
- ios-srcUSDZ URL for iOS AR Quick Look
- kioskBoolean — hides the debug GUI
Lightweight pure-visual viewer. Three.js + OrbitControls only. No AI, no WebSocket.
- srcGLB/GLTF URL — reacts to attribute changes
- backgroundCSS color or "transparent"
- altAccessibility label + caption
- load / errorCustom events with detail.url
Imperative iframe modal. Resolves with a GLB Blob via postMessage. Works with Character Builder and Selfie Studio.
- studioUrlOverride the iframe origin (default: three.ws)
- onExport(blob)Called with the GLB Blob on export
- onClose()Called on user dismiss without export
- open() / close()Lifecycle methods
Presigns to R2, uploads the GLB, registers the avatar record. Returns { id, url, slug }.
- bearerTokenRequired — avatars:write scope
- nameAvatar display name
- visibility"public" | "unlisted" | "private"
- apiOriginOverride API host (for self-hosting)
vs. legacy avatar SDKs
Legacy avatar SDKs got acquired and closed their free tiers. Here's what you get when you migrate.
| Feature | Legacy SDKs | @three-ws/avatar |
|---|---|---|
| Photo → 3D avatar | ✕ Closed | ✓ Selfie pipeline |
| In-browser avatar editor | ✕ Closed to new apps | ✓ Character Builder |
| Hosted CDN storage | Paid only | ✓ three.ws R2 (free public tier) |
| ARKit viseme morphs | ✓ | ✓ Loaded automatically |
| Mixamo-compatible rig | ✓ | ✓ Same naming conventions |
| Animation library | ~30 clips | ✓ 50+ Mixamo clips bundled |
| iOS Quick Look USDZ | ✕ | ✓ Auto-generated server-side |
| WebXR / VR | ✕ | ✓ Half-body auto-generated |
| Web component | ✓ | ✓ <agent-3d> |
| React bindings | Community only | ✓ Official, typed |
| Self-hostable | ✕ | ✓ Apache-2.0 open source |