bevyengine/bevy · critical

unhandled extension: {ext}

Error message

unhandled extension: {ext}

What it means

Panic in ShaderLoader::load. The loader only knows how to build a Shader for the extensions 'spv', 'wgsl', and 'wesl'; any other extension routed to this loader reaches the catch-all arm and panics with 'unhandled extension: {ext}'. Since this runs inside the asset loading task, it typically surfaces as a failed/panicking load.

Source

Thrown at crates/bevy_shader/src/shader.rs:302

        // TODO: remove this once AssetPath forces cross-platform "slash" consistency. See #10511
        let path = path.replace(std::path::MAIN_SEPARATOR, "/");
        let mut bytes = Vec::new();
        reader.read_to_end(&mut bytes).await?;
        if ext != "wesl" && !settings.shader_defs.is_empty() {
            tracing::warn!(
                "Tried to load a non-wesl shader with shader defs, this isn't supported: \
                    The shader defs will be ignored."
            );
        }
        let mut shader = match ext.as_str() {
            "spv" => Shader::from_spirv(bytes, load_context.path().path().to_string_lossy()),
            "wgsl" => Shader::from_wgsl(String::from_utf8(bytes)?, path),
            "wesl" => {
                let mut shader = Shader::from_wesl(String::from_utf8(bytes)?, path);
                shader.shader_defs = settings.shader_defs.clone();
                shader
            }
            _ => panic!("unhandled extension: {ext}"),
        };

        // collect and store file dependencies
        match ext.as_str() {
            "wesl" => {
                let candidates: Vec<String> = shader
                    .imports
                    .iter()
                    .filter_map(|import| match import {
                        ShaderImport::AssetPath(asset_path) => {
                            Some(format!("{}.{ext}", asset_path.trim_start_matches('/')))
                        }
                        ShaderImport::Custom(_) => None,
                    })
                    .collect();
                for file_path in candidates {
                    if load_context
                        .read_asset_bytes(AssetPath::from(file_path.clone()))

View on GitHub (pinned to 396ca72708)

Solutions

  1. Use one of the supported extensions: .wgsl, .wesl, or .spv
  2. If you need a custom extension, register a dedicated AssetLoader for it instead of routing through ShaderLoader
  3. Verify which loader claims the asset via the AssetServer's loader resolution

Example fix

// before
let handle = asset_server.load("shaders/pipeline.glsl"); // panics: unhandled extension

// after
let handle = asset_server.load("shaders/pipeline.wgsl");
Defensive patterns

Strategy: validation

Validate before calling

const SUPPORTED: [&str; 3] = ["spv", "wgsl", "wesl"];
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
if !SUPPORTED.contains(&ext) {
    // route to a custom loader or reject before hitting ShaderLoader
}

Prevention

When it happens

Trigger: Requesting an asset with an extension ShaderLoader claims but does not handle (loader registration vs. match mismatch), or forcing a custom shader-like extension through the shader loader. The match on ext.as_str() falls through to the panic.

Common situations: Adding a new shader format extension to the loader's supported list without extending the match; loading .glsl/.hlsl files expecting automatic transpilation; custom asset pipeline misrouting files.

Related errors


AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20). Data as JSON: /api/errors/67a4415cd2ad6c08. Report an issue: GitHub.