Building a custom GLB import pipeline in Godot 4 puts you in full control — automatic LOD generation, collision meshes, shadow proxies, and material overrides applied the moment a file lands in your project. EditorImportPlugin is the hook that makes it possible.
What EditorImportPlugin does and when to use it
Godot's built-in GLB importer handles most assets well, but it runs identically on every file. When you have 747 assets from a source like the BitSoul marketplace and need consistent treatment — stripped UV2 channels, forced double-sided materials, or named collision nodes — writing a custom importer pays for itself on the first batch import.
`EditorImportPlugin` is a GDScript (or C#) class that registers itself with the editor. It declares the file extensions it handles, a priority value that overrides the default importer, and a `_import()` method that receives the source path and writes to a `.godot`-internal resource file. The four methods you must implement: `_get_importer_name()` returns a unique reverse-domain string; `_get_recognized_extensions()` returns `["glb"]`; `_get_priority()` returns a float above 1.0 to supersede the built-in GLB importer; `_get_import_order()` returns 0 for most cases.
```gdscript
@tool
extends EditorImportPlugin
func _get_importer_name(): return "com.mystudio.glb_game_ready"
func _get_recognized_extensions(): return ["glb"]
func _get_priority() -> float: return 2.0 # > 1.0 overrides the default importer
func _get_import_order() -> int: return 0
```
Place this script in `addons/glb_importer/` alongside a `plugin.cfg` and enable it in Project → Project Settings → Plugins.
![]()
Implementing LOD generation in _import()
The `_import()` method receives the source GLB path and an `options` dictionary from `_get_import_options()`. Load the scene via `GLTFDocument`, walk every `MeshInstance3D` with a recursive helper (`_get_all_children(scene)`), then call `generate_lods()` on each `ImporterMesh`. Always call `scene.queue_free()` after packing — the editor does not garbage-collect the generated scene automatically.
```gdscript
func _import(source_file, save_path, options, platform_variants, gen_files) -> Error:
var doc := GLTFDocument.new()
var state := GLTFState.new()
var err := doc.append_from_file(source_file, state)
if err != OK: return err
var scene := doc.generate_scene(state)
for node in _get_all_children(scene):
if node is MeshInstance3D:
var im := node.mesh.get_importable_mesh()
if im:
im.generate_lods(options.get("lod_normal_merge_angle", 25.0),
options.get("lod_normal_split_angle", 60.0), [])
node.mesh = im.get_mesh()
var packed := PackedScene.new()
packed.pack(scene)
scene.queue_free()
return ResourceSaver.save(packed, "%s.%s" % [save_path, _get_save_extension()])
```
`generate_lods()` takes merge angle, split angle, and an optional LOD ratio array — pass `[]` to let Godot choose automatically (typically 0.25, 0.1, 0.04). `get_importable_mesh()` returns null for meshes that weren't imported as `ImporterMesh`, so always check before calling.
Adding automatic collision meshes
After the LOD loop, add `StaticBody3D` collision for each prop mesh: create a `StaticBody3D`, add a child `CollisionShape3D` with `shape = node.mesh.create_convex_shape(true, true)` (both flags clean and simplify the hull), set `.owner = scene` on both nodes, then call `node.add_child(body)`. For concave meshes — stairs, hollow props — use `create_trimesh_shape()` instead; it's more accurate but more expensive at runtime.
Declaring import options for per-asset control
Return import options from `_get_import_options(_path, _preset_index)` as an `Array[Dictionary]`, each entry with `name` and `default_value` keys. They appear as editable fields in the Import dock, letting artists override settings per file without touching code. Commit `.import` sidecar files to version control so teammates get identical results.
| Option | Default | Effect |
|------|--------|--------|
| `generate_collision` | `true` | Adds `StaticBody3D` + hull shape |
| `lod_normal_merge_angle` | `25.0` | Merge angle for LOD generation |
| `lod_normal_split_angle` | `60.0` | Split angle for LOD generation |
| `force_double_sided` | `false` | Disables back-face culling on all surfaces |
| `shadow_proxy_lod` | `2` | Which LOD level is used as shadow caster |
![]()
Workflow checklist for shipping a custom importer
| Step | Action | Gotcha |
|------|--------|--------|
| Register plugin | Add `plugin.cfg`, enable in Project Settings | Priority must be > 1.0 to override default GLB importer |
| Define options | Return typed dicts from `_get_import_options()` | Missing `type` key causes silent failures in older 4.x builds |
| LOD generation | Call `generate_lods()` on `ImporterMesh`, reassign `.mesh` | Only works if the original mesh was imported as `ImporterMesh`; check with `get_importable_mesh()` |
| Collision | Use `create_convex_shape(true, true)` for props | Concave meshes need `create_trimesh_shape()` — more expensive at runtime |
| Re-import trigger | Edit `.import` file or call `EditorFileSystem.reimport_files()` | Godot does not auto-reimport after plugin code changes |
| Version control | Commit `.import` sidecars | Without them, collaborators fall back to default importer settings |
The BitSoul marketplace ships assets as clean GLB with consistent naming — ideal input for a pipeline like this, since node names are predictable and the plugin can branch on them (e.g., nodes prefixed `UCX_` get collision-only treatment, matching Unreal conventions).
Automating import with `EditorImportPlugin` turns asset integration from a per-file chore into a one-time engineering task. Write it once, import 500 assets the right way every time.
---
*This post is part of the Ultimate Guide to Free 3D Game Assets — BitSoul's complete reference for formats, texturing, rigging, optimization, and engine integration.*