everything you need to know to rig and export an avatar or cosmetic for the portals shop. the same standard applies to both — there's one canonical rig and every wearable binds to it.
every avatar and wearable in portals uses a single 90-bone mixamo skeleton: joints are prefixed mixamorig:, structured hierarchically from hips through head, and named identically across all rigs. the engine validates uploads against this exact set of bones at create time — if anything's missing the upload is rejected with a list of which bones to add.
canonical rig template
download the bare-skeleton glb to import into your DCC tool, then bind your mesh to it.
90 bones total, grouped by region
Hips through Neck/Head. Every wearable must include these — they're the structural backbone every garment hangs off.
Four bones per finger (palm → tip) plus four for the thumb. Gloves and rings need these to deform naturally.
Eyes, eyelids, eyebrows, jaw, lips, nose. Required even for non-face wearables so the avatar can animate facial expressions while wearing the item. The auto-migrator adds these bones if your DCC export missed them.
back capes / wings / particle props
cosmetics in the back category with the animated flag play the first animation embedded in their GLB, on loop, for as long as they're worn — on the avatar page, in the lobby and in games on Portals avatar SDK 0.47 or later. the flag is also written into the stored URL as a query parameter, so every client that loads the GLB gets it:
<your-glb-url>?animatedWearable=true
you don't need to do anything manual — flip the animated toggle on a back-category item and the create / update endpoints append (or strip) the parameter automatically. if you change the item's category later, the parameter is rewritten to match.
keep the animated part self-contained: rig it to its own small skeleton (a fish on a bone called Bone is fine) and attach it rigidly under a portals rig bone, as described below. the animation only moves nodes inside the wearable, never the avatar. only the first clip in the file plays.
dances, waves and flourishes on the same rig
an emote is motion on this rig, played over whatever the avatar is doing and faded back to it — in the lobby, on the avatar page and in every game. export the armature on its own, no mesh, with a clip named Emote and, optionally, Emote_Enter and Emote_Exit. one file plays on both bodies; the upload form writes its mode and expression into the file for you.
reference emotes
a one-shot with a held smile, and a loop with an entry, an exit and a hips bob. both pass every upload rule.
every emote says where its motion comes from before review — made by me, my own motion capture, licensed — with the licensor and licence for licensed motion, and you agree to:
I own this emote's motion, and any prop and sound it carries, or hold a licence that lets me sell them on Portals as a standalone item. None of it is copied from a game, film, performance, recording, song or asset pack whose terms forbid that, and I'll provide the licence if Portals asks.
motion from game rips, films, other people's performances or asset packs that forbid reselling animations on their own can't be sold. read the full emote standard.
and how to fix them
exported a .gltf instead of .glb, or the file is corrupted in transit. re-export from your DCC with format GLB (binary) selected. don't rename .gltf → .glb; the binary header is different.
the GLB is attached to the portals rig in neither of the two accepted ways. every wearable ships the full rig template, and then either:
import the rig template, attach your mesh one of those two ways, and re-export.
some required bones are missing from the GLB's skeleton. the create endpoint returns the exact list of names you're missing — add them by binding to the canonical template rather than a custom skeleton.
the auto-migrator catches one specific case: an old-rig (65-bone) export that's missing only the 25 face bones. uploads matching that fingerprint are silently upgraded server-side. anything else is rejected so creators don't ship broken wearables.
GLB uploads are capped at 50 MB, and the server rejects anything larger even if it reached storage. most of that size is usually textures — downscale or compress the largest ones and re-export.
the JSON chunk of the GLB couldn't be decoded. usually a sign of a partial upload or a non-spec exporter. re-export from a recent version of blender (3.0+) or your tool of choice and try again.
recommended export settings
new-rig.glb as the parent armature.