Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsYou can read VRM metadata in Flutter with plain Dart, and the VRM 1.0 failure reported with vrm_dart is a mismatch in extension names rather than a damaged file. In a DEV Community report dated 2026-09-22, the author, Ilan Kim, says parse() returned for a VRM 1.0 sample, but reading vrmMeta afterward raised a LateInitializationError. The author attributes this to the package reading only the VRM 0.x extension key, VRM, while VRM 1.0 stores its metadata under VRMC_vrm. The workable approach is to treat the file as a glTF 2.0 binary container, decode its JSON chunk, and branch on whichever VRM extension the file actually declares.
What a .vrm file is
A VRM avatar is a glTF 2.0 Binary (GLB) file that uses the .vrm extension. The VRMC_vrm 1.0 specification from the VRM Consortium (vrm-c) states: “Saved in .glb format and using .vrm as the extension.” The GLB’s JSON chunk holds the glTF document and its extension data, including the VRM metadata. An optional binary chunk holds the buffers that images, meshes and other data are read from. Your parser therefore has two jobs: read the container correctly, then read the VRM fields from the JSON.
As an Amazon Associate I earn from qualifying purchases.
Read the GLB container
Check the header and bounds first
A GLB file begins with a 12-byte header, and every multi-byte integer is a little-endian uint32:
- Bytes 0–3: magic, which must equal
0x46546C67(the ASCII text “glTF”). - Bytes 4–7: container version. glTF 2.0 files use 2; reject anything else.
- Bytes 8–11: declared total length of the file. It must not exceed the number of bytes you were given.
The GLB header version only describes the container. The Khronos glTF 2.0 Specification adds: “Client implementations that load GLB format MUST also check for the asset version properties in the JSON chunk, as the version specified in the GLB header only refers to the GLB container version.” So after decoding the JSON, check asset.version as well.
#1 Best Overall
- CARDBOARD MONKENAUT — Get our best Gorilla Tag bundle yet with this Amazon exclusive deal. Purchase Meta Quest 3S to get exclusive items, including the Gorilla Space Program Suit and Helmet, plus 2,000 SHINY ROCKS.
- NO WIRES, MORE FUN — Break free from cords. Game, play and explore immersive worlds — untethered and without limits.
- 2X GRAPHICAL PROCESSING POWER — Enjoy lightning-fast load times and next-gen graphics for smooth gaming powered by the Snapdragon XR2 Gen 2 processor.
- EXPERIENCE VIRTUAL REALITY — Take gaming to a new level and blend virtual objects with your physical space to experience two worlds at once in your VR headset.
- 2+ HOURS OF BATTERY LIFE — Charge less, play longer and stay in the action with an improved battery that keeps up. *Based on the graphic performance of the Qualcomm Snapdragon XR2 Gen 2 platform vs the Meta Quest 2 platform.
Walk the chunks
Each chunk starts with an 8-byte header: a uint32 payload length, then a uint32 chunk type. The payload follows, and chunk boundaries are four-byte aligned.
- JSON chunk: type
0x4E4F534A(“JSON”). It must be the first chunk. - BIN chunk: type
0x004E4942(“BIN” followed by a zero byte). It is the second chunk when present, and it may be absent entirely. - Unknown types: skip them. This keeps the reader compatible with later extensions of the container.
For every chunk, confirm that the payload end stays inside the declared length before reading it. A chunk that overruns the file is a malformed file, and your code should reject it with a clear error rather than reading past the buffer.
Choose the VRM generation from the JSON
Once the JSON is decoded, look at the root extensions object. The generation decides every later lookup, so make this the first branch in your code.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchRank #2
- NO WIRES, MORE FUN — Break free from cords. Game, play, exercise and explore immersive worlds — untethered and without limits.
- 2X GRAPHICAL PROCESSING POWER — Enjoy lightning-fast load times and next-gen graphics for smooth gaming powered by the SnapdragonTM XR2 Gen 2 processor.
- EXPERIENCE VIRTUAL REALITY — Take gaming to a new level and blend virtual objects with your physical space to experience two worlds at once.
- 2+ HOURS OF BATTERY LIFE — Charge less, play longer and stay in the action with an improved battery that keeps up.
- 33% MORE MEMORY — Elevate your play with 8GB of RAM. Upgraded memory delivers a next-level experience fueled by sharper graphics and more responsive performance.
| Axis | VRM 0.x | VRM 1.0 |
|---|---|---|
| Extension key | extensions.VRM |
extensions.VRMC_vrm, with specVersion set to 1.0 |
| Metadata fields (from the author’s comparison) | title, author, licenseName |
name, authors, licenseUrl |
| Thumbnail path | meta.texture → textures[].source → image |
meta.thumbnailImage → image |
| Expressions | blendShapeMaster.blendShapeGroups |
expressions.preset and expressions.custom |
The field names in this table come from the author’s comparison. Confirm exact property names and which properties are required against the versioned schema before you write code that treats any of them as mandatory. If a file contains both keys, do not assume which one wins; test that case explicitly and decide what your API should return.
Extract the thumbnail
Thumbnail lookup differs by generation, but both paths end at an entry in images[].
VRM 0.x path
- Read
extensions.VRM.meta.texture. This is a texture index, not an image index. - Look up
textures[texture].sourceto get the image index. - Continue with that index in
images[].
VRM 1.0 path
- Read
extensions.VRMC_vrm.meta.thumbnailImage. This is already an image index. - Continue with that index in
images[].
Slice the image bytes
- Read
images[index]. If it has nobufferView, it may reference an external URI, and a metadata-only reader should return no thumbnail. - Look up
bufferViews[bufferView]. Itsbuffershould be 0, which is the GLB BIN chunk.byteOffsetdefaults to 0 when omitted. - Check that
byteOffset + byteLengthfits inside the BIN chunk, then take that range.
Files may legitimately omit images, bufferViews or the BIN chunk. Return “no thumbnail” in those cases instead of throwing. Do not match images by name, because an image entry’s name is not a reliable signal of which image is the thumbnail.
Rank #3
- CARDBOARD MONKENAUT — Get our best Gorilla Tag bundle yet with this Amazon exclusive deal. Purchase Meta Quest 3 to get exclusive items, including the Gorilla Space Program Suit and Helmet, plus 2,000 SHINY ROCKS.
- NEARLY 30% LEAP IN RESOLUTION — Experience every thrill in breathtaking detail with sharp graphics and stunning 4K+ Infinite Display.
- NO WIRES, MORE FUN — Break free from cords. Game, play and explore in immersive worlds — untethered and without limits.
- 2X GRAPHICAL PROCESSING POWER — Enjoy lightning-fast load times and next-gen graphics for smooth gaming powered by the Snapdragon XR2 Gen 2 processor.
- EXPERIENCE VIRTUAL REALITY — Blend virtual objects with your physical space and experience two worlds at once in your VR headset.
A parser sketch in Dart
The following sketch uses ByteData and Uint8List from dart:typed_data and needs no third-party package. It is a starting point for your own fixtures, not a tested library.
import 'dart:convert';
import 'dart:typed_data';
const int _glbMagic = 0x46546C67; // "glTF"
const int _chunkJson = 0x4E4F534A; // "JSON"
const int _chunkBin = 0x004E4942; // "BIN "
class GlbContents {
GlbContents(this.json, this.bin);
final Map<String, dynamic> json;
final Uint8List? bin;
}
GlbContents readGlb(Uint8List bytes) {
if (bytes.lengthInBytes < 20) {
throw const FormatException('Too short for GLB header and JSON chunk');
}
final data = ByteData.sublistView(bytes);
if (data.getUint32(0, Endian.little) != _glbMagic) {
throw const FormatException('Not a GLB file');
}
final containerVersion = data.getUint32(4, Endian.little);
if (containerVersion != 2) {
throw FormatException('Unsupported GLB container version $containerVersion');
}
final declaredLength = data.getUint32(8, Endian.little);
if (declaredLength > bytes.lengthInBytes) {
throw const FormatException('Declared length exceeds file size');
}
Map<String, dynamic>? json;
Uint8List? bin;
var offset = 12;
while (offset + 8 <= declaredLength) {
final chunkLength = data.getUint32(offset, Endian.little);
final chunkType = data.getUint32(offset + 4, Endian.little);
final start = offset + 8;
final end = start + chunkLength;
if (offset == 12 && chunkType != _chunkJson) {
throw const FormatException('First chunk must be JSON');
}
if (end > declaredLength) {
throw const FormatException('Chunk overruns declared length');
}
if (chunkLength % 4 != 0) {
throw const FormatException('Chunk is not four-byte aligned');
}
if (chunkType == _chunkJson && json == null) {
json = jsonDecode(utf8.decode(Uint8List.sublistView(bytes, start, end)))
as Map<String, dynamic>;
} else if (chunkType == _chunkBin && bin == null) {
bin = Uint8List.sublistView(bytes, start, end);
}
offset = end;
}
if (json == null) {
throw const FormatException('Missing JSON chunk');
}
final asset = json['asset'] as Map<String, dynamic>?;
final version = asset?['version'];
if (version is! String || !version.startsWith('2.')) {
throw FormatException('Unsupported glTF asset version: $version');
}
return GlbContents(json, bin);
}
String vrmGeneration(Map<String, dynamic> json) {
final ext = (json['extensions'] as Map<String, dynamic>?) ?? <String, dynamic>{};
if (ext.containsKey('VRMC_vrm')) return '1.0';
if (ext.containsKey('VRM')) return '0.x';
return 'none';
}
int? thumbnailImageIndex(Map<String, dynamic> json, String generation) {
if (generation == 'none') return null;
final ext = (json['extensions'] as Map<String, dynamic>?) ?? <String, dynamic>{};
if (generation == '1.0') {
final v1 = ext['VRMC_vrm'] as Map<String, dynamic>?;
final meta = v1?['meta'] as Map<String, dynamic>?;
return meta?['thumbnailImage'] as int?;
}
final v0 = ext['VRM'] as Map<String, dynamic>?;
final meta = v0?['meta'] as Map<String, dynamic>?;
final textureIndex = meta?['texture'] as int?;
final textures = json['textures'] as List<dynamic>?;
if (textureIndex == null || textures == null) return null;
if (textureIndex < 0 || textureIndex >= textures.length) return null;
final texture = textures[textureIndex] as Map<String, dynamic>;
return texture['source'] as int?;
}
Uint8List? thumbnailBytes(Map<String, dynamic> json, Uint8List? bin, int? imageIndex) {
final images = json['images'] as List<dynamic>?;
final views = json['bufferViews'] as List<dynamic>?;
if (imageIndex == null || images == null || views == null || bin == null) {
return null;
}
if (imageIndex < 0 || imageIndex >= images.length) return null;
final image = images[imageIndex] as Map<String, dynamic>;
final viewIndex = image['bufferView'] as int?;
if (viewIndex == null || viewIndex < 0 || viewIndex >= views.length) {
return null;
}
final view = views[viewIndex] as Map<String, dynamic>;
if ((view['buffer'] as int? ?? 0) != 0) return null;
final offset = view['byteOffset'] as int? ?? 0;
final length = view['byteLength'] as int;
if (offset < 0 || length < 0 || offset + length > bin.lengthInBytes) {
return null;
}
return Uint8List.sublistView(bin, offset, offset + length);
}
The sketch deliberately stops at container, metadata and thumbnail extraction. It does not validate every glTF accessor, and it does not decide how to expose metadata to your UI. Keep those decisions in a separate layer so that renderer changes never touch the parser.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.What the vrm_dart failure does and does not show
The 2026-09-22 DEV Community report describes one author-run test on macOS on Apple Silicon, with Flutter 3.47.2 and Dart 3.13.2. The author compared five files, including one VRM 1.0 sample named Seed-san.vrm and several author-created VRM 0.x samples with thumbnails. In that test, parse() returned and vrmMeta then raised LateInitializationError. The author’s explanation is that the parser reads only VRM, so the late-initialized field is never assigned when the metadata sits under VRMC_vrm.
Rank #4
- Your purchase of this item includes a new Meta Quest Pro 256 GB VR headset and a 12-month subscription to Optima Academy Online (OAO) field trips.
- Optima Academy Online (OAO) harnesses the power of virtual reality to make previously impossible learning opportunities just a few clicks away. Our VR Field Trips provide powerful ways of engaging users on a whole new level while providing learning experiences. With our VR Field Trips, we deliver users directly into an immersive educational experience that engages them like never before. We offer a one-month subscription to our VR Field Trips. During your subscription, you can spend as much time in our uniquely created Metaverse environments as you like. Each environment has its own theme, learning experiences, and adventures.
- High resolution mixed reality passthrough uses full-color sensors to let you see and engage with the physical world around you, even as you connect, work and play in virtual spaces.
- Share your true emotions and reactions with real time natural avatar expressions. Meta Avatars translate your natural facial expressions into VR so you can bring your true personality to meetings and gatherings with friends.
- Meta Quest Touch Pro Controllers translate instinctive hand gestures and detailed finger actions directly into VR with self-tracking cameras and precision controls. Multi-point, advanced haptics make virtual interactions feel entirely real
That is a useful explanation, and it matches the extension naming difference in the official VRMC_vrm 1.0 specification. It is still one author’s small test, not broad compatibility testing, so avoid turning it into a claim that the package fails on every VRM 1.0 file. A more accurate statement is that the author’s VRM 1.0 reproduction failed because the parser did not initialize metadata for the VRMC_vrm extension.
The gatari/vrm_dart repository describes the package as a Dart/Flutter metadata loader and shows VrmFileParser(...).parse() usage. The repository material does not independently confirm the reported exception, so treat the error as the author’s observation.
Free tools Windows power users keep installed
One-click scans. No signup required.
The author concludes: “You do not need a package to read VRM metadata in Flutter.” This is the author’s advice rather than an official standard, and the chunk layout above is what makes it practical.
Best Value
- NEARLY 30% LEAP IN RESOLUTION — Experience every thrill in breathtaking detail with sharp graphics and stunning 4K Infinite Display.
- NO WIRES, MORE FUN — Break free from cords. Play, explore and exercise in immersive worlds — untethered and without limits.
- 2X GRAPHICAL PROCESSING POWER — Enjoy lightning-fast load times and next-gen graphics for smooth gaming powered by the Snapdragon XR2 Gen 2 processor.
- EXPERIENCE VIRTUAL REALITY — Blend virtual objects with your physical space and experience two worlds at once.
- 2+ HOURS OF BATTERY LIFE — Charge less, play longer and stay in the action with an improved battery that keeps up.
What a chunk reader does not do
A chunk reader extracts structure and metadata. It does not render an avatar. VRM 1.0 describes expressions, look-at, spring bones, constraints and materials as separate extensions, and reading the JSON does not apply any of them. If you need an avatar on screen, you still need a renderer that understands those extensions, and you need to decide how much of the VRM 0.x feature set your target supports.
Test your parser before you ship it
- Use one VRM 0.x and one VRM 1.0 fixture, and confirm that each returns the correct generation.
- Include fixtures with and without images and with and without the BIN chunk. Both should parse, and the thumbnail should return “no thumbnail” when image data is absent.
- Include an image entry whose name is not
Thumbnail, so your code cannot depend on names. - Include a file with malformed lengths, a chunk that overruns the declared length, and a misaligned chunk. Each should throw a clear error.
- Include a file with an unknown chunk type between the JSON and BIN chunks, and confirm that the reader skips it.
- Include a file with an
asset.versionthat is not 2.x, and confirm that it is rejected. - Check the metadata and thumbnail for a file that declares both
VRMandVRMC_vrm, and decide and document the result.
Once these pass, the same code path works for both generations without the package, and a failure in a future file will point to a specific chunk or index rather than a late-initialized field.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →

