EasyStore is a modular save system addon for Godot 4. It exposes one clean Autoload API (EasyStore) while routing all persistence through pluggable backends — Local and Steam Cloud — so game code stays the same regardless of where data is stored.
Slots, sections, in-memory caching, autosave, versioned migrations, multi-backend sync, and conflict resolution are all handled internally. You configure the active backends, call save / load / sync, react to signals, and drop optional nodes such as EasyStoreTrigger where you need scene-level auto-save.
| Single public API | One EasyStore Autoload: backends, slots, sections, signals, autosave, migrations, and debug. |
| Swappable backends | StorageBackend contract; Local and Steam Cloud backends included. New backends can be added without touching game code. |
| Multi-backend sync | Run Local + Steam simultaneously. Save writes to both in parallel; sync() aligns timestamps automatically on reconnect. |
| Conflict resolution | Four strategies: NEWEST_WINS (default), CLOUD_WINS, LOCAL_WINS, and MANUAL (signal-driven dialog). |
| Section cache | Write-through in-memory buffer with dirty tracking. save() is instant; I/O is flushed asynchronously. |
| Slot system | Multiple independent save slots with lightweight sidecar metadata for fast slot listing. |
| Autosave | Built-in timer with configurable interval. Emits autosave_triggered every cycle. |
| Migrations | Register from → to callables. Applied automatically on load when save_version < current_version. |
| Async I/O | Thread + semaphore worker keeps file operations off the main thread. |
| Steam Cloud backend | Reads and writes to Steam Remote Storage via GodotSteam. Resolved at runtime — no parse errors when the plugin is absent. |
| Drop-in node | EasyStoreTrigger auto-saves on configurable scene lifecycle events. |
| Debug tooling | Structured logger, runtime debug info, and a debug_mode() toggle. |
| Item | Required? | Notes |
|---|---|---|
| Godot 4.4+ | Yes | Developed and tested on Godot 4.4+ (see plugin.cfg). |
| GodotSteam GDExtension 4.4+ | Steam backend only | Official GodotSteam plugin by Gramps. Required only when using StoreEnums.BackendType.STEAM_CLOUD. |
Expected install path in your project:
res://addons/easystore/
- Copy this repository's
addons/easystorefolder into your Godot project underres://addons/easystore/. - Open Project → Project Settings → Plugins.
- Enable EasyStore — the editor registers the
EasyStoreautoload (seeplugin.gdandeasystore.tscn). - Optionally configure a
EasyStoreConfigresource and pass it toEasyStore.initialize(config). - Add at least one backend with
EasyStore.add_backend(StoreEnums.BackendType.LOCAL)and start saving.
Steam backend: also install GodotSteam GDExtension 4.4+ and add the Steam backend with
EasyStore.add_backend(StoreEnums.BackendType.STEAM_CLOUD)after verifying that Steam is running.
After enabling the plugin, you should see EasyStore under Project → Project Settings → Autoloads, pointing at res://addons/easystore/easystore.tscn.
func _ready() -> void:
EasyStore.initialize()
EasyStore.add_backend(StoreEnums.BackendType.LOCAL)# Save a section — written to cache instantly, flushed to disk async
EasyStore.save("player", { "level": 5, "health": 100, "coins": 320 })
# Load a section — returns from cache if already loaded, {} otherwise
var player_data: Dictionary = EasyStore.load("player")
# Await confirmation that the write reached disk
await EasyStore.save_completedfunc _ready() -> void:
EasyStore.initialize()
EasyStore.add_backend(StoreEnums.BackendType.LOCAL)
if Engine.has_singleton("Steam") and Engine.get_singleton("Steam").isSteamRunning():
EasyStore.add_backend(StoreEnums.BackendType.STEAM_CLOUD)
await EasyStore.sync() # align local ↔ cloud on startup# Switch to slot 2 and save
EasyStore.set_slot(2)
EasyStore.save("world", { "level_name": "forest", "time": 1200 })
# List all slots with metadata
var slots: Array[SaveMetadata] = await EasyStore.list_slots()
for meta in slots:
print("Slot %d — %s" % [meta.slot, meta.custom.get("title", "Untitled")])func _ready() -> void:
EasyStore.autosave_triggered.connect(func(slot): print("Autosaved slot ", slot))
EasyStore.enable_autosave(120.0) # every 2 minutesfunc _ready() -> void:
EasyStore.set_current_version(2)
# v1 → v2: add a stamina field that didn't exist before
EasyStore.register_migration(1, 2, func(sections: Dictionary) -> Dictionary:
sections["player"]["stamina"] = 100
return sections
)
EasyStore.initialize()
EasyStore.add_backend(StoreEnums.BackendType.LOCAL)func _ready() -> void:
EasyStore.save_completed.connect(_on_save_completed)
EasyStore.load_completed.connect(_on_load_completed)
EasyStore.error_occurred.connect(_on_error)
EasyStore.sync_conflict.connect(_on_sync_conflict)
func _on_save_completed(slot: int, success: bool) -> void:
if success:
print("Slot %d saved." % slot)
func _on_load_completed(slot: int, data: Dictionary, success: bool) -> void:
if success:
print("Loaded slot %d: %s" % [slot, data])
func _on_error(code: int, message: String) -> void:
push_error("[EasyStore] Error %d: %s" % [code, message])
func _on_sync_conflict(slot: int, key: String, local_data: Variant, cloud_data: Variant) -> void:
# MANUAL strategy — decide which version wins
if cloud_data.get("level", 0) > local_data.get("level", 0):
EasyStore.save(key, cloud_data)
else:
EasyStore.save(key, local_data)The official documentation is hosted at:
EasyStore Official Documentation
Full interactive docs with sidebar navigation, EN / ES language toggle, and quick search — covering all backends, the full API reference, signals, enums, migrations, multi-backend sync, custom backend guide, and complete examples.
addons/easystore/
├── plugin.cfg # Plugin metadata
├── plugin.gd # EditorPlugin: autoload registration
├── easystore.tscn # Autoload scene root
├── easystore.gd # EasyStore singleton (public API facade)
├── config/
│ ├── easystore_config.gd # Global configuration resource
│ ├── local_backend_config.gd # Local backend options (path, format, encryption)
│ └── steam_backend_config.gd # Steam backend options (file prefix)
├── core/
│ ├── store_enums.gd # BackendType, ConflictStrategy, SerializationFormat, ErrorCode
│ ├── store_events.gd # Internal event bus (Node child of autoload)
│ ├── storage_backend.gd # Abstract backend contract
│ ├── save_file.gd # Save data model (Resource)
│ └── save_metadata.gd # Per-slot metadata sidecar (Resource)
├── backends/
│ ├── local/
│ │ └── local_backend.gd # Local filesystem backend
│ └── steam/
│ └── steam_backend.gd # Steam Remote Storage backend (GodotSteam)
├── subsystems/
│ ├── slot_manager.gd # Active slot tracking and slot lifecycle
│ ├── section_cache.gd # In-memory write-through cache with dirty tracking
│ ├── autosave_timer.gd # Configurable autosave interval
│ ├── migration_manager.gd # Versioned migration chain execution
│ ├── sync_manager.gd # Multi-backend orchestration and conflict resolution
│ └── async_worker.gd # Thread + semaphore async I/O queue
├── debug/
│ ├── store_logger.gd # Structured log buffer
│ └── store_debugger.gd # Runtime debug info and mode toggle
└── nodes/
└── easy_store_trigger.gd # Drop-in scene node for automatic saving
- Initial release.
- Local backend — saves to
user://saves/slot_N.sav+slot_N.meta.json. Supports JSON and binary serialization formats, optional AES encryption, and optional compression viaFileAccess. - Steam Cloud backend — reads and writes to Steam Remote Storage using GodotSteam GDExtension 4.4+. Steam is resolved at runtime via
Engine.get_singleton("Steam")— no parse errors when the plugin is absent. - Multi-backend sync —
sync()comparesSaveMetadata.timestampacross all active backends and resolves conflicts with the configuredConflictStrategy(NEWEST_WINS,CLOUD_WINS,LOCAL_WINS,MANUAL). - Section cache — write-through in-memory buffer with per-section dirty tracking.
save()is synchronous from the game's perspective; disk I/O is dispatched toAsyncWorker. - Slot system — multiple save slots with lightweight sidecar metadata for instant
list_slots()without deserializing full save data. - Autosave — built-in timer with configurable interval. Emits
autosave_triggered. - Migrations — register
from_version → to_versioncallables viaregister_migration(). Applied automatically on load. - Drop-in node —
EasyStoreTriggerfor scene-level auto-save on configurable lifecycle events. - Debug tooling —
StoreLogger,StoreDebugger,get_logs(),get_debug_info(),debug_mode().
- EasyStore — IUX Games, Isaackiux — version 1.0.0 (see
plugin.cfg). - GodotSteam — Gramps — used as the transport layer for the Steam Cloud backend.