Gltf loader error when upgrading to BabylonJS 8.51.2 from 8.24.0

While attempting to upgrade my project from BabylonJS 8.24 to the latest version, I ran into this rather ugly exception:

Uncaught _RuntimeError RuntimeError: Unable to load from ./models/PlayerVisual.glb: Unsupported version: 2.0
    at errorHandler (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Loading\sceneLoader.ts:896:42)
    at <anonymous> (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Loading\sceneLoader.ts:969:25)
    --- Promise.catch ---
    at <anonymous> (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Loading\sceneLoader.ts:968:22)
    at dataCallback (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Loading\sceneLoader.ts:683:13)
    at <anonymous> (c:\Dev\BabylonJS\kitchen-chaos\dev\loaders\src\glTF\glTFFileLoader.ts:877:29)
    --- Promise.then ---
    at <anonymous> (c:\Dev\BabylonJS\kitchen-chaos\dev\loaders\src\glTF\glTFFileLoader.ts:875:23)
    at <anonymous> (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Misc\fileTools.ts:539:13)
    at onReadyStateChange (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Misc\fileTools.ts:697:37)
    --- XMLHttpRequest.send ---
    at send (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Misc\webRequest.ts:185:19)
    at retryLoop (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Misc\fileTools.ts:728:21)
    at requestFile (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Misc\fileTools.ts:731:9)
    at RequestFile (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Misc\fileTools.ts:774:9)
    at LoadFile (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Misc\fileTools.ts:536:12)
    at _loadFile (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\scene.ts:6289:25)
    at _loadFile (c:\Dev\BabylonJS\kitchen-chaos\dev\loaders\src\glTF\glTFFileLoader.ts:1116:31)
    at loadFile (c:\Dev\BabylonJS\kitchen-chaos\dev\loaders\src\glTF\glTFFileLoader.ts:865:25)
    at manifestChecked (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Loading\sceneLoader.ts:714:26)
    at _Database (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Offline\database.ts:69:13)
    at AbstractEngine.OfflineProviderFactory (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Offline\database.ts:11:12)
    at <anonymous> (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Loading\sceneLoader.ts:735:52)
    at getPluginInstance (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Loading\sceneLoader.ts:634:17)
    at loadDataAsync (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Loading\sceneLoader.ts:643:12)
    at importMeshAsync (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Loading\sceneLoader.ts:929:18)
    at <anonymous> (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Loading\sceneLoader.ts:994:13)
    at importMeshAsyncCoreAsync (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Loading\sceneLoader.ts:992:18)
    at ImportMeshAsync (c:\Dev\BabylonJS\kitchen-chaos\dev\core\src\Loading\sceneLoader.ts:860:18)
    at loadActors (c:\Dev\BabylonJS\kitchen-chaos\src\stages\GameStage.ts:94:28)
    at createScene (c:\Dev\BabylonJS\kitchen-chaos\src\stages\GameStage.ts:37:14)
    at load (c:\Dev\BabylonJS\kitchen-chaos\src\framework\Stage.ts:29:20)
    at setActiveStage (c:\Dev\BabylonJS\kitchen-chaos\src\framework\StageManager.ts:255:29)
    at start (c:\Dev\BabylonJS\kitchen-chaos\src\main.ts:18:28)
    at <anonymous> (c:\Dev\BabylonJS\kitchen-chaos\src\main.ts:53:12)

Inspecting the GLB’s strings, I can see the JSON is properly set to “2.0”.

glTF
JSON{
"asset": {
"generator": "FBX2glTF v0.9.7",
"version": "2.0"
"scene": 0,
"buffers": [

The code block I have to load the model is:

        let result = await ImportMeshAsync("./models/PlayerVisual.glb", super.scene);

        const PlayerVisual = result.meshes[0];

        PlayerVisual.position = Vector3.Zero();;

        PlayerVisual.rotation = Vector3.Zero();;

        PlayerVisual.receiveShadows = true;

        this._shadowGenerator?.addShadowCaster(PlayerVisual);

I tried to recreate this on the playground, but it is using a deprecated call.

I’ve debugged the source, which is as follows:

    private _getLoader(loaderData: IGLTFLoaderData): IGLTFLoader {
        const asset = (<any>loaderData.json).asset || {};

        this._log(`Asset version: ${asset.version}`);
        asset.minVersion && this._log(`Asset minimum version: ${asset.minVersion}`);
        asset.generator && this._log(`Asset generator: ${asset.generator}`);

        const version = GLTFFileLoader._parseVersion(asset.version);
        if (!version) {
            throw new Error("Invalid version: " + asset.version);
        }

        if (asset.minVersion !== undefined) {
            const minVersion = GLTFFileLoader._parseVersion(asset.minVersion);
            if (!minVersion) {
                throw new Error("Invalid minimum version: " + asset.minVersion);
            }

            if (GLTFFileLoader._compareVersion(minVersion, { major: 2, minor: 0 }) > 0) {
                throw new Error("Incompatible minimum version: " + asset.minVersion);
            }
        }

        const createLoaders: { [key: number]: (parent: GLTFFileLoader) => IGLTFLoader } = {
            1: GLTFFileLoader._CreateGLTF1Loader,
            2: GLTFFileLoader._CreateGLTF2Loader,
        };

        const createLoader = createLoaders[version.major];
        if (!createLoader) {
            throw new Error("Unsupported version: " + asset.version);

and inspecting version.major I can see that 2 is passed in. I’m somewhat stuck at this point.

1 Like

The full source for my project, using 8.24.0, is at GitHub - Corysia/kitchen-chaos

cc @bghgary, but it looks like the loader for glTF v2 can’t be found. Make sure the v2 loader is packaged in your project.

You should look at the createLoaders object and see if there’s a “2” entry inside: if not, the v2 loader has not been found.

[EDIT]

Package.json:

You should have an entry for @babylonjs/loaders here.

So, is there a change requiring loaders to be included differently than in version 8.24? I am only upgrading the version of BabylonJS I’m using from 8.24 to 8.51. This is working fine in 8.24, crashing in 8.51.

When I inspect the array, there are two undefined entries.

I’ve also updated package.json to include loaders in my local branch, but it did not help:

It appears that now the latest version of BabylonJS, you must import the loaders to initialize them. The change in package.json isn’t necessary, but an import statement is.

1 Like

Adding @ryantrem to confirm.

Scene loaders (e.g. the gltf loader) must be registered for them to be picked up by the general scene loading APIs (e.g. ImportMeshAsync). There are currently two ways for them to be registered:

  1. Side effects: this is basically the legacy way, and happens when you import "@babylonjs/loaders" (or for gltf specifically, it could be just @babylonjs/loaders/glTF/2.0).
  2. Explicit and dynamic: this is the newer way, and happens when you import { registerBuiltInLoaders } from "@babylonjs/loaders/dynamic" and then call registerBuiltInLoaders(). This does dynamic imports of loaders and glTF extensions until they are needed.

If you previously weren’t doing either of these, then my best guess is that the switch from 8.24.0 to 8.51.2 is also going from Inspector v1 to v2, and v1 probably had side effects that v2 doesn’t. Whether Inspector has side effects is an implementation detail, so you shouldn’t rely on what is happening inside Inspector to get loaders registered. I would say the robust solution is to use one of the options above to explicitly register loaders, such that if you removed the Inspector package (a diagnostic tool that you typically wouldn’t ship with an app), your app still works.

So basically the change you made (to explicitly import "@babylonjs/loaders") is correct, and you could also improve that by using the newer registerBuiltInLoaders.

4 Likes

Unfortunately, I’m still stuck. The application is running fine locally, but failing when I deploy to GitHub Pages. And that really surprises me.

I’ve made the suggested changes from above. I’ve also turned off WebGPU and now force WebGL. I’ve disabled minify.

I’ve successfully upgraded to 8.37.3, still failing with 8.51.2. I will continue to make iterative upgrades until it breaks; maybe something will jump out at me. Right now, my console is showing:

index-Crai-6FJ.js:3365 [2/18/2026, 12:15:20 PM] Main::constructor()
index-Crai-6FJ.js:3365 [2/18/2026, 12:15:20 PM] Application initialized.
index-Crai-6FJ.js:3365  [2/18/2026, 12:15:20 PM] this is a warning
warn @ index-Crai-6FJ.js:3365
index-Crai-6FJ.js:3365  [2/18/2026, 12:15:20 PM] this is an error
error @ index-Crai-6FJ.js:3365
index-Crai-6FJ.js:3365 this is a trace message
trace @ index-Crai-6FJ.js:3365
addEventListeners @ index-Crai-6FJ.js:28694
UEt @ index-Crai-6FJ.js:28694
(anonymous) @ index-Crai-6FJ.js:28694
index-Crai-6FJ.js:3365 [2/18/2026, 12:15:20 PM] this is a debug message
index-Crai-6FJ.js:3365 [2/18/2026, 12:15:20 PM] Main::start()
index-Crai-6FJ.js:3365 [2/18/2026, 12:15:20 PM] StageManager::addStage():  GameStage
index-Crai-6FJ.js:3365 [2/18/2026, 12:15:20 PM] StageManager::constructor()
index-Crai-6FJ.js:3365 [2/18/2026, 12:15:20 PM] StageManager: Creating stable WebGL engine (WebGPU disabled)
index-Crai-6FJ.js:23 BJS - [12:15:20]: Babylon.js v8.51.2 - WebGL2 - Parallel shader compilation
index-Crai-6FJ.js:3365 [2/18/2026, 12:15:20 PM] StageManager: WebGL engine created successfully
index-Crai-6FJ.js:3365 [2/18/2026, 12:15:20 PM] StageManager::findStage():  GameStage
index-Crai-6FJ.js:3365 [2/18/2026, 12:15:20 PM] Starting to load PlayerVisual.glb

I should be receiving a log message after it’s loaded and set up, but I’m not reaching it.

        try {
            Logger.debug('Starting to load PlayerVisual.glb');
            let result = await ImportMeshAsync('./models/PlayerVisual.glb', super.scene);
            const PlayerVisual = result.meshes[0];
            PlayerVisual.position = Vector3.Zero();;
            PlayerVisual.rotation = Vector3.Zero();;
            PlayerVisual.receiveShadows = true;
            this._shadowGenerator?.addShadowCaster(PlayerVisual);
            Logger.debug("PlayerVisual.glb loaded successfully");

I’ve determined that:

  1. It fails only on a production build. i.e., tsc && vite build
  2. It works in GitHub tag 8.48.0 and fails in 8.49.0. There are no other builds of 8.48 that are tagged on GitHub.
  3. It does not fail on a test build in 8.51.2. i.e., vite –-host

This morning I upgraded to 8.52.0 to see if anything changed. It didn’t, sadly. So I started tracing the code produced in dist and I make it to this point where it freezes:

I lose the thread from here.

So in the debugger, you can get to that point where it is trying to dynamically import pbrMaterialLoadingAdapter-gktrNUpG.js and you never get to the next line (e.g. the promise never completes)?

If that is the case, the only way I could imagine that happening is if that bundle chunk didn’t get deployed with your webapp. Are there any errors in the console or in the network tab?

Also can you verify that file actually exists in your deployed web app? It kind of sounds like it is missing.

That’s correct, I never get beyond that point. If I do a single step, the debugger does not pause again. I have verified that the file is there; I could see it in the browser’s list of assets. I opened it and could view the contents.

I decided to try to simplifly the problem and create a smaller sample project. And in this new project, I’m unable to replicate the issue. So, I have to conclude this is my own error somewhere in my project and not an issue with the loader.

import "@babylonjs/inspector";
import { Scene, Engine, FreeCamera, HemisphericLight, MeshBuilder, Vector3, ImportMeshAsync } from "@babylonjs/core";
import { registerBuiltInLoaders } from "@babylonjs/loaders/dynamic";

class Main {

    constructor() {
        let canvas = document.createElement("canvas");
        canvas.style.width = '100%';
        canvas.style.height = '100%';
        document.body.appendChild(canvas);
        let engine = new Engine(canvas, true);

        registerBuiltInLoaders();

        this.initializeScene(engine, canvas);
    }

    private async initializeScene(engine: Engine, canvas: HTMLCanvasElement): Promise<void> {
        let scene = await this.createScene(engine, canvas);

        window.addEventListener("resize", function () {
            engine.resize();
        });

        window.addEventListener("keydown", (ev) => {
            // Shift+Ctrl+Alt+F
            if (ev.shiftKey && ev.ctrlKey && ev.altKey && ev.code === "KeyF") {
                engine.switchFullscreen(false);
            }
            // Shift+Ctrl+Alt+I
            if (ev.shiftKey && ev.ctrlKey && ev.altKey && ev.code === "KeyI") {
                if (scene.debugLayer.isVisible()) {
                    scene.debugLayer.hide();
                } else {
                    scene.debugLayer.show();
                }
            }
        });
        engine.runRenderLoop(function () {
            scene.render();
        })
    }

    public async createScene(engine: Engine, canvas: HTMLCanvasElement): Promise<Scene> {
        // This creates a basic Babylon Scene object (non-mesh)
        let scene = new Scene(engine);

        // This creates and positions a free camera (non-mesh)
        let camera = new FreeCamera("camera1", new Vector3(0, 5, -10), scene);

        // This targets the camera to scene origin
        camera.setTarget(Vector3.Zero());

        // This attaches the camera to the canvas
        camera.attachControl(canvas, true);

        // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
        let light = new HemisphericLight("light1", new Vector3(0, 1, 0), scene);

        // Default intensity is 1. Let's dim the light a small amount
        light.intensity = 0.7;

        console.log('Loading model...');
        let result = await ImportMeshAsync('./models/PlayerVisual.glb', scene);
        console.log(`Loaded ${result.meshes.length} meshes`);
        const PlayerVisual = result.meshes[0];
        PlayerVisual.position = Vector3.Zero();
        PlayerVisual.rotation = Vector3.Zero();

        // Our built-in 'ground' shape. Params: name, options, scene
        let ground = MeshBuilder.CreateGround("ground", { width: 6, height: 6 }, scene);
        console.log(`Created ground: ${ground.name}`);

        return scene;
    }
}

new Main();

Thank you for taking the time to look.

1 Like