Web Export Guide
Grid Building supports web export for all three demo types: top-down, platformer, and isometric. This guide covers what you need to know to get your project running in a browser.
Requirements
- Godot 4.4+ with Web export template installed
- GDScript builds only (C# / Mono does not support web export in Godot 4.x)
export_filter="all_resources"or explicit inclusion of rules and template directories
How Resource Loading Differs on Web
The main issue: embedded SubResources fail to load on web
Resources that are embedded as SubResource(...) entries inside a .tres file — rather than saved as standalone .tres files on disk — fail to load in web exports. The parent resource loads, but any nested SubResource fields remain null.
This affects any @export resource field that Godot saves as an inline [sub_resource] block instead of an [ext_resource] reference. For example, a CollisionsCheckRule with its messages field saved as an embedded subresource will have messages = null on web.
The fix: Save every resource that needs to exist at runtime as its own .tres file on disk, and reference it via ExtResource(...):
| |
As of Godot 4.6.2 stable, all three Grid Building demos (top-down, platformer, isometric) export correctly to web because their settings resources use external .tres references. The _ensure_messages() lazy-loading safeguard in CollisionsCheckRule compensates for any embedded SubResources that may fail to deserialize in exported builds.
Lazy-loading safety nets
Some rules include _ensure_*() methods as defensive guards against null resources. For example, CollisionsCheckRule has _ensure_messages():
| |
This runs in validate_placement() and get_editor_issues(), so exports do not crash on null messages regardless of why it is null. In practice on Godot 4.6.2 with external .tres files, _init() initializes these fields correctly, but the safety net remains for robustness.
Best Practices for Web-Compatible Resources
Save base rules as standalone .tres files
Base placement_rules in GBSettings should reference external .tres files, not embedded SubResource(...) entries:
Good — external rule resource:
| |
Avoid — embedded subresource:
| |
Array syntax for placement_rules
Both plain arrays and typed-array wrapper syntax work in web exports. All three Grid Building demos use typed-array syntax and export correctly.
Plain array (used by platformer demo):
| |
Typed array (used by top-down and isometric demos):
| |
Both formats are valid. If you encounter empty arrays after export, see Godot issue #97782 and try the plain array format as a workaround.
Externalize GBSettings from GBConfig
Avoid embedding GBSettings as a subresource inside GBConfig. Save it as its own .tres file:
| |
Serialize nested resources explicitly
Save important nested resources directly in the .tres file rather than relying on _init() to create them:
CollisionsCheckRule.messagesCollisionsCheckRule.fail_visual_settings- Tile rule
fail_visual_settings
Verify indicator scenes serialize collision flags
RuleCheckIndicator scenes should explicitly set collision flags based on your needs:
| |
Most placement rules only need collide_with_areas = true since they detect other RuleCheckIndicator instances or Area2D objects. Set collide_with_bodies = true only if your validation needs to detect StaticBody2D objects directly.
Collision Masks by Demo Type
Different demos use different physics layers. Do not copy collision masks between demos blindly:
| Demo | Rule collision_mask | Rule apply_to_objects_mask |
||-|***************|
| Top-down | 1 or project-specific | 1 |
| Platformer | 1 or project-specific | 1 |
| Isometric | 2560 | 2561 |
Verification
Automated tests
Run the web export compatibility test suite:
| |
This validates:
.tres-loaded rules lazy-load messages on first usevalidate_placement()does not crash when messages is null- Demo config chains load rules correctly
Pre-export checklist
- Base rules in
GBSettingsare external.tresfiles -
GBConfig.settingsreferences an externalGBSettings.tres - Placeable
packed_scenereferences a real.tscnfile - Collision rules serialize
messagesandfail_visual_settings - Indicator scenes serialize
collide_with_bodiesandcollide_with_areas -
placement_rulesarrays load correctly (both plain and typed-array syntax are supported) - Collision masks match your project’s physics layer setup
- Export filter includes
res://templates/and any custom rules directories
Troubleshooting
Placement indicators do not appear
- Check browser console for
Failed to load resourceerrors - Verify
placement_rulesis non-empty by adding a temporary print inGBCompositionContainer.get_placement_rules() - Ensure base rules are external
.tresfiles, not embedded subresources
Potential issues from earlier Godot versions
The following issues have been reported in earlier Godot versions but do not reproduce on Godot 4.6.2 stable. If you are on an older version and experiencing problems, check these:
_init()not called during.tresdeserialization —@exportvariables may be set after_init()runs, leaving_init()-initialized fields at null (Godot issue #70575). Workaround: ensure rules use_ensure_*()lazy-loading or serialize fields explicitly in the.tres.Typed array syntax deserializing as empty —
Array[ExtResource("...")]([...])may deserialize as an empty array in exported builds (Godot issues #97782 and #72489). Workaround: switch to plain array syntax (placement_rules = [ExtResource("1_rule")]).
Placement always succeeds (no red tiles)
- Verify
CollisionsCheckRule.collision_maskoverlaps your target objects’collision_layer - Check that indicator scenes have
collide_with_bodies = true - For isometric projects, verify masks are
2560/2561, not top-down defaults
Rules load but validation is wrong
- Check
messagesis serialized in the rule.tresor that_ensure_messages()is being triggered - Verify
IndicatorFactoryreceives a valid logger (it falls back to a defaultGBLoggerautomatically)
References
- Godot issue #97782 —
@export Array[ResourceCustom]empty in exported builds (typed array deserialization bug) - Godot issue #72489 — Typed arrays break resource deserialization when some elements are
null - Godot issue #70575 —
@exportvariables set after_init()runs (deserialization order) - Godot Docs: Exporting for the Web
Last updated: 2026-05-03