Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
FREEDOM251

Freedom251 › Blog › 3D avatars

Parsing VRM in Flutter Without a Package: Why vrm_dart Fails on VRM 1.0 and How to Read the GLB Chunks Yourself

By Freedom251 Team · · 8 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

You 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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • 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
Meta Quest 3S 128GB | Virtual Reality — VR Headset — Gorilla Tag Bundle
  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Meta Quest 3S 128GB | Virtual Reality — VR Headset (Renewed Premium)
  • 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

  1. Read extensions.VRM.meta.texture. This is a texture index, not an image index.
  2. Look up textures[texture].source to get the image index.
  3. Continue with that index in images[].

VRM 1.0 path

  1. Read extensions.VRMC_vrm.meta.thumbnailImage. This is already an image index.
  2. Continue with that index in images[].

Slice the image bytes

  1. Read images[index]. If it has no bufferView, it may reference an external URI, and a metadata-only reader should return no thumbnail.
  2. Look up bufferViews[bufferView]. Its buffer should be 0, which is the GLB BIN chunk. byteOffset defaults to 0 when omitted.
  3. Check that byteOffset + byteLength fits 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
Meta Quest 3 512GB, VR Without Wires, Gorilla Tag Cardboard Monkenaut Bundle, Amazon Exclusive, 3-Month Trial of Meta Horizon+ Included
  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.Support on Ko-Fi

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
Meta Quest Pro Headset with Virtual Reality Field Trips 1-Month Subscription
  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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
Meta Quest 3 512GB | Virtual Reality — VR Headset — Renewed Premium
  • 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.version that is not 2.x, and confirm that it is rejected.
  • Check the metadata and thumbnail for a file that declares both VRM and VRMC_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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3D avatarsDartFlutterGLBglTFVRMvrm_dart

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.