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
- Use one of the supported extensions: .wgsl, .wesl, or .spv
- If you need a custom extension, register a dedicated AssetLoader for it instead of routing through ShaderLoader
- 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
- Restrict shader assets to .spv/.wgsl/.wesl
- Register dedicated loaders for custom shader formats instead of extending ShaderLoader's claimed extensions
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
- Enable feature "shader_format_spirv" to use SPIR-V shaders
- spirv not yet implemented
- Could not load shader: {0}
- Could not parse shader: {0}
- Field name should exist
AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20).
Data as JSON: /api/errors/67a4415cd2ad6c08.
Report an issue: GitHub.