familiar-avatar/1

Make a companion.

A .vrm is a body. It carries a rig, expressions, spring bones and a fixed set of meshes, and once it is exported none of that is negotiable — if you want the top in red you export a second model with a red top.

This format is the alternative, and it is optional and additive. An avatar arrives as a folder with an avatar.json in it, the .vrm sits inside unchanged, and the JSON describes what may be swapped on it. A bare .vrm dropped on the window keeps working exactly as it does today; a folder is recognised by the presence of that one file and nothing else.

2

things a .vrm cannot tell you

A wardrobe — which meshes are choices rather than facts — and an identity. Both are things the author knows and has, until now, had no way to say.

0

lines of geometry in it

No skinning, no animation, no rig data. The .vrm goes through the same loader either way. Name nothing the loader can find and you get the model exactly as if the folder had been a bare file.

1

line to add a companion

Adding the second bundled companion was one line and nothing else. So was the third. So was the fourth, who is a man — which the format had nothing to say about either.

The directory

Sakura/               ← the folder is the avatar
├── avatar.json       ← required. Its presence is what makes this one.
├── sakura.vrm        ← required. The body, named by `Model`.
├── portrait.png      ← optional. The cast picker tile.
├── textures/         ← optional, by convention. Any layout works.
│   ├── top_red.png
│   └── hair_pink.png
└── thumbs/           ← optional. Small squares for the chips.
    └── top_red.png
  • avatar.json must be at the top level of the folder. The loader does not search subdirectories for it.
  • Everything the manifest references is relative to the folder root, using forward slashes, and must resolve inside it. ../, a leading /, a drive letter, a URL scheme and a NUL byte are all rejected at parse time rather than 403’d at load time.
  • Subdirectory layout is entirely yours. textures/ and thumbs/ above are convention, not schema.
  • Filenames are matched case-sensitively.

Why a folder rather than an extension to the .vrm

Three reasons, and the cost is real and accepted. Alternate textures have to live somewhere — five shirt colours at 2K each is 20MB that would otherwise be embedded in the glTF and decoded in full on every load, whether or not anyone picks the fourth. Authors edit JSON and do not edit glTF. And round-tripping destroys it: anyone who opens the model in VRoid Studio, Blender or UniVRM and re-exports drops unknown extensions on the floor, where a sibling JSON file survives all three.

Key spelling

Keys are matched case-insensitively, ignoring spaces, underscores and hyphens. MeshGroups, meshGroups, "Mesh Groups" and mesh_groups are the same key. The canonical spelling is PascalCase with no separator.

This is not a stylistic indulgence. The file is hand-authored by people who are not reading the schema next to it, and a manifest that silently ignores "Mesh Groups" produces an avatar with no wardrobe and no error anyone can see. Values are not normalised — item ids, group names, material names and file paths are taken exactly as written.

Identity fields

FieldTypeWhat it setsIf absent
Formatstringassumed familiar-avatar/1
Namestring, ≤ 24 charswhat they are called, everywhererequired
Modelrelative path to a .vrmthe file that is loadedrequired
Blurbstring, ≤ 60 charsone line under their name in the picker“your own model”
Portraitrelative path to an imagethe cast picker tilethe model renders its own
Pronounsshe/her, he/him, they/themwhat the app calls themthey/them
Personalitystring, ≤ 4000 charstheir half of the system prompta light default persona
Moodsarray of mood idsmoods that play under the user’sno mood of their own
Greetingsarray of stringstheir own hellosthe default greetings
DefaultVoicestring, or object keyed by familywhich voice speaks themthe family’s own default
MeshGroupsarray of groupsthe wardrobe — see belowthe .vrm exactly as it shipped

Four edges worth spelling out

Personality is prose, Moods are chips. They are different layers and the collision in the word is unfortunate. Personality becomes the character’s half of the system prompt — it answers who is this, written in second person, present tense, with a few concrete behavioural rules. Moods names the stackable moods the user dials in on top. Your moods ride on the persona and play under whatever is on the user’s board; the board names them rather than showing them pressed, and clearing it does not take them off — because “this character is written hype” and “I have put this character in a hype mood” are two different statements and only the second is the user’s to take back.

Pronouns is a pronoun and not a gender. Nothing in the app ever needs to branch on gender — the body is the .vrm, the voice is DefaultVoice, the wardrobe is MeshGroups. The only thing ever missing was what to put in the sentences the app writes about a companion. The reader is generous about how you write it: he, he/him, He / Him / His and male are one answer, and Gender is accepted as the key.

The default is they/them, and it is not a hedge. A manifest that does not say has not said, and singular they is the only thing that can be stated about somebody unknown without asserting something that may be false.

DefaultVoice is per speech family or it means nothing. The service’s voice names belong to whichever model is loaded. A bare string is accepted and means try this name on every family, checked against what the loaded model actually accepts and quietly dropped when it is not there. The object form is the honest one, and this is a default — a voice the user picks in the studio wins forever after.

Personality does not get to say anything about the user. It is concatenated into a system prompt, so a manifest is a prompt-injection vector in exactly the way a downloaded avatar folder should not be. The mitigation is not filtering — it is that this text lands in the same slot the bundled cast occupies, under the app’s own framing, with the memory and tool instructions built around it rather than after it. Treat a third-party Personality the way you would treat a third-party system prompt, because that is what it is.

The smallest manifest that works

Name and Model are the only required fields. Everything else has a defined absence.

{
  "Format": "familiar-avatar/1",
  "Name": "Sakura",
  "Model": "sakura.vrm",
  "Blurb": "quiet, until she isn't",
  "Portrait": "portrait.png",
  "Pronouns": "she/her",
  "Personality": "You are Sakura. You speak carefully and mean what you say...",
  "Greetings": ["Oh — you're back.", "Hello. Sit, if you like."],
  "DefaultVoice": { "f5": "YoungWomanWellSpoken", "kitten": "Kiki" }
}

Mesh groups — the wardrobe

A VRM has no concept of “these three hair meshes are alternatives”. It has three meshes, and without a manifest the app can only offer three independent checkboxes over them — which lets someone wear all three at once, or none. A group says which of the model’s parts are alternatives for each other, and what to call them.

"MeshGroups": [
  {
    "Id": "top",
    "GroupName": "Top",
    "IsExclusive": true,     // one at a time
    "CanBeOff": true,        // ...or none
    "OffLabel": "None",
    "Default": "crop",
    "Items": [
      { "Id": "crop",   "Label": "Cami",   "Type": "MeshToggle", "Mesh": ["SK_TOP_CROP"] },
      { "Id": "hoodie", "Label": "Hoodie", "Type": "MeshToggle", "Mesh": ["SK_TOP_HOODIE"] }
    ]
  }
]
  • IsExclusive — one item at a time, rendered as a radio row.
  • CanBeOff — adds an off state, labelled by OffLabel.
  • Default — the item that is on before anybody has chosen. Vessa’s model ships three tops, two shorts, a hoodie and thirteen body pieces all at once, so she is one of the models that is wrong without her manifest applied rather than merely plainer.
  • Actions — the long form, when one item has to toggle several meshes or combine a mesh toggle with a texture or colour change. A single-action item can use the shorthand above instead.

Colour targets materials and shape targets meshes. Both are matched against what the loaded model actually carries, and anything that matches nothing is listed as a problem rather than silently doing nothing — the format’s posture throughout is that a manifest which is wrong should say so, once, where the author will see it.

Publishing

Zip the folder and it works anywhere. Put it in the mods directory — avatars/, alongside voices/ — and they are in the cast list every launch, with their outfits, voice and hidden meshes remembered like anybody else’s. Publish it to the Workshop and somebody else gets all of the above without unzipping anything.

Your own voice goes in voices/: a .wav of two to fifteen seconds of clean speech with a transcript beside it, matching exactly what is said in the clip. It is registered, not installed — held in memory for the life of the process and written nowhere, so the folder is the only copy and deleting the clip is all it takes to be rid of the voice.

Licence metadata

The .vrm carries its own author, licence and permitted-use fields, and the app reads and respects them rather than restating them. If the model says it may not be redistributed, do not publish it to the Workshop — the manifest beside it does not change that, and it does not grant you rights the body does not.

The studio walkthrough lands with Early Access.

An in-app editor that writes this file for you — pick the groups on the model, name them, and export the folder. The spec above is what it writes, and it stays hand-authorable on purpose.