NEVER Do
- NEVER save without a version field โ When you update your game's data structure, old saves will break. Always include a
"version": "1.0.0"field and implement migration logic. - NEVER use absolute OS paths โ Hardcoding
C:/Users/...will break on every other machine. Always use theuser://protocol, which Godot maps to the correct OS-specific app data folder. - NEVER attempt to save Node references directly โ Nodes are objects, not raw data. Extract the necessary primitive data (positions, health, levels) into a
DictionaryorResourceinstead. - NEVER forget to close FileAccess handles โ Leaving a file open can lead to handle leaks and save-file corruption. In Godot 4, files auto-close when the variable goes out of scope, but explicit
close()is safer for long-running logic. - NEVER use JSON for very large binary data โ Storing 10MB of texture data as Base64 in JSON is slow and bloats file size. Use binary
store_var()or separate dedicated asset files. - NEVER trust loaded data without validation โ Users can edit save files. Always use
data.get("field", default_value)and validate that numbers are within expected ranges to prevent crashes. - NEVER trigger a save during high-frequency physics or animation updates โ A crash mid-write will corrupt the file. Save only on explicit game events like entering a menu, finishing a level, or at a checkpoint.
- NEVER modify a save Dictionary while iterating over its keys โ Calling
erase()oradd()inside a loop over the same dictionary causes iteration errors. Usedata.duplicate()to iterate safely. - NEVER store raw passwords or sensitive credentials in unencrypted JSON โ If you have sensitive data, use
FileAccess.open_encrypted_with_pass()to secure it. - NEVER use ResourceLoader.load() for massive scenes on the main thread โ It causes a visible freeze. Use
ResourceLoader.load_threaded_request()to load levels in the background. - NEVER rely on get_instance_id() for cross-session identification โ These IDs are assigned at runtime and change every time the game restarts. Generate your own persistent
StringUUIDs for game objects. - NEVER forget to call duplicate(true) on a loaded Resource stats block โ If multiple enemies load the same "goblin_stats.tres", they will all share the same health pool unless duplicated.
- NEVER use the "allow_objects" flag in store_var/get_var for untrusted data โ Setting this to
trueallows full object decoding, which is a major security risk for saves downloaded from the web. - NEVER use JSON for data requiring strict type preservation โ JSON converts
Vector3to a string or dictionary. For strict data types, usevar_to_bytes()or a binary format. - NEVER leave internal metadata (set_meta) in persistent dictionaries โ This unnecessarily inflates save file size. Clean your dictionaries before serialization.
Available Scripts
MANDATORY: Read the script for the chosen format before writing SaveManager code.
save_load_patterns.gd
MANDATORY for JSON / binary / PERSIST collect โ patterns default store_var(..., false).
save_migration_manager.gd
MANDATORY when any save has a version field that can lag the build.
save_system_encryption.gd
MANDATORY before encrypted slots โ password from secure storage / user secret, never hardcoded in examples.
save_integrity_validator.gd
Rolling .bak + SHA-256 verify before trusting a slot; fall back to backup on mismatch.
Deep dive (load on demand)
MANDATORY for JSON/PERSIST walkthroughs, binary examples, gotchas, and elite encrypted paths โ references/save-patterns-deep.md. Do not paste Step 1โ3 Autoload tutorials into scenes.
Expert WHY (critical)
CAUTION: Baseline tutorials used
store_var(data, true). Untrusteduser://saves mustallow_objects=falseโ RCE risk on modded/workshop files.
- Vectors in JSON โ store
{x,y,z}components; JSON does not round-tripVector3faithfully. - Rolling backup โ crash mid-write corrupts primary; copy to
.bakbefore overwrite (save_integrity_validator.gd). - When to save โ menu/checkpoint/level complete only โ never per physics frame.
Decision Tree: Pick a Persistence Shape
| Need | Format | MANDATORY |
|---|---|---|
| Human-readable, small/medium progress | JSON + version |
save_load_patterns.gd |
| Type-faithful Variants / larger blobs | Binary store_var with allow_objects=false |
same |
| Typed Resource trees / inspector schemas | ResourceSaver / .tres/.res |
Peer godot-resource-data-patterns |
| Many scene nodes auto-collect | PERSIST group + save()/load() |
save_load_patterns.gd |
| Schema evolved | Migrate then load | save_migration_manager.gd |
| Anti-tamper / sensitive fields | Encrypted FileAccess | save_system_encryption.gd |
Do not paste Step 1โ3 JSON Autoload tutorials here โ implement from the scripts.
allow_objects Trust Boundary
Default always store_var(data, false) / get_var(false).
| Case | allow_objects |
Rule |
|---|---|---|
Player user:// saves, workshop mods, downloads |
false |
NEVER true โ RCE risk |
| Trusted local only (your own tooling, offline debug fixtures you control) | true only if unavoidable |
Document why; never ship as default; prefer Resources / Dictionaries of primitives |
Encrypted elite paths still use false unless the payload is explicitly trusted-local and non-user-editable.
Golden Path (version โ migrate โ backup โ atomic write)
- Version field on every save blob.
- Migrate via save_migration_manager.gd when versions differ.
- Backup existing file (
DirAccess.copy_absoluteto.bak) before overwrite. - Write to temp then rename, or write-after-backup; validate open errors.
- Integrity optional:
FileAccess.get_sha256compare; fall back to backup on mismatch. - Paths only
user://โ never absolute OS paths. - When to save โ menu, checkpoint, level complete โ never per physics frame.
Settings may use ConfigFile separately from run-progress JSON/binary.
Reference
Progressive disclosure: open Official Documentation links only when researching a specific API; load Related Skills when routing to a peer domain โ do not preload the whole lattice.
Official Documentation
- Saving games โ Persist group serialization, JSON line format, and the canonical save/load loop this skill builds on.
- File paths in Godot โ
user://vsres://mapping across OS app-data folders; never hardcode absolute paths. - Binary serialization API โ
store_var/get_varVariant encoding, type fidelity, and whyallow_objectsis unsafe for untrusted saves. - Background loading โ
ResourceLoader.load_threaded_requestfor hitch-free level/resource loads after a save restore. - File system โ FileAccess/DirAccess workflow for existence checks, backups, and safe overwrite patterns.
- Resources โ Resource vs Dictionary persistence,
duplicate(true), and when.tres/.resbeats hand-rolled JSON. - Groups โ SceneTree group membership used by the Persist/PERSIST auto-collect pattern.
- FileAccess โ Open modes, encrypted-with-pass AES helpers, SHA-256, compression flags, and buffer I/O.
- JSON โ
stringify/parse/parse_stringfor human-readable saves and validation of parse errors. - ConfigFile โ INI-style settings (
user://settings.cfg) separate from full game-state saves. - ResourceSaver โ Persist typed Resources/custom Resource trees when JSON type loss is unacceptable.
- AESContext โ Low-level AES block encrypt/decrypt used by custom compressed encrypted save pipelines.
Related Skills
Prerequisites
- godot-project-foundations โ ProjectSettings, Autoload registration, and
user://project identity must exist before a SaveManager can own paths. - godot-gdscript-mastery โ Typed Dictionaries, Resources, and error-handling patterns for versioned serialize/deserialize code.
- godot-autoload-architecture โ SaveManager is almost always an Autoload; use this for singleton ownership, boot order, and scene-change survival.
Complements
- godot-resource-data-patterns โ Custom Resource schemas and
.tresworkflows that pair with ResourceSaver instead of flattening everything to JSON. - godot-scene-management โ Threaded scene swaps and wipe/rebuild Persist nodes after load without leaking old world state.
- godot-signal-architecture โ
game_saved/game_loadedevent buses so UI and systems react without hard-wiring SaveManager. - godot-ui-containers โ Settings menus that write ConfigFile/volume keys this skill persists separately from run progress.
- godot-inventory-system โ Item stacks and equipment dictionaries are the heaviest Persist payloads; share ID schemes with save migration.
- godot-quest-system โ Quest flags/stage IDs must round-trip through versioned saves without breaking journal UI.
- godot-economy-system โ Currency wallets and shop unlocks need the same version field and validation as player progress.
Downstream / consumers
- godot-adapt-single-to-multiplayer โ Local save patterns become host-authoritative state sync; never trust client-edited JSON in multiplayer.
- godot-monte-carlo-balancer โ Use when progression/economy curves stored in saves need simulated balance passes against migration defaults.
- godot-multiplayer-networking โ Server-side validation and snapshot formats that replace plaintext
user://saves for competitive modes.
Master
- godot-master โ Library router and mirrored module entry; open when discovering which Domain Skill owns persistence vs content systems.