Quick answer: Save to user://, never res:// or /sdcard/. user:// maps to the app’s private writable directory on every platform and needs no permissions. res:// is read-only at runtime; system paths are blocked by Android’s scoped storage.
Here is how to fix Godot ResourceSaver.save failing with permission denied or write errors on Android. You save to res://saves/savegame.tres and the call returns a permission error. Or you try /sdcard/MyGame/save.tres hoping for visibility in file managers, and Android’s scoped storage rejects it. The fix is simple: always use user:// for runtime writes.
The Symptom
ResourceSaver.save returns ERR_FILE_CANT_OPEN or ERR_FILE_NO_PERMISSION on Android. The same code works in editor on desktop. Inspecting the device with adb shows the file was never created.
What Causes This
res:// is read-only. At runtime, res:// is bundled into the APK and cannot be written. Editor exposes it as writable; device does not.
System paths blocked. Modern Android (10+) restricts /sdcard and external storage. Apps without MANAGE_EXTERNAL_STORAGE cannot freely write there.
Wrong absolute path. Constructed paths via OS calls can land outside the app sandbox; rejected by the OS.
iOS scoped storage. iOS has similar rules; only the app sandbox is writable.
The Fix
Step 1: Save to user://.
var save_data := PackedScene.new()
# or any Resource subclass
ResourceSaver.save(save_data, "user://savegame.tres")
user:// is the standard cross-platform user data path. Maps to:
Windows: %APPDATA%\Godot\app_userdata\<projectname>\
macOS: ~/Library/Application Support/Godot/app_userdata/<projectname>/
Linux: ~/.local/share/godot/app_userdata/<projectname>/
Android: /data/data/<package>/files/userdata/ (sandbox)
iOS: <app sandbox>/Library/Application Support/godot/app_userdata/
No permissions, no platform-specific paths, no errors.
Step 2: Verify writability.
var path := "user://savegame.tres"
var err := ResourceSaver.save(save_data, path)
if err != OK:
push_error("Save failed: %s" % err)
else:
print("Saved at: %s" % ProjectSettings.globalize_path(path))
globalize_path converts user:// to a real OS path; useful for logs.
Step 3: For player-visible exports, use share intents. If you want the player to share or back up the save outside the app sandbox, use the platform’s share API:
# Godot Android Share Plugin (community plugin)
if Engine.has_singleton("GodotShare"):
var share = Engine.get_singleton("GodotShare")
share.shareFile(ProjectSettings.globalize_path("user://savegame.tres"))
The user picks where to save through the OS dialog; no MANAGE_EXTERNAL_STORAGE needed.
Step 4: Use FileAccess for non-Resource saves.
var file := FileAccess.open("user://config.json", FileAccess.WRITE)
file.store_string(JSON.stringify({ "volume": 0.8 }))
file.close()
Step 5: Handle iOS / macOS sandbox extras. On iCloud-enabled apps, also write to ubiquitous container if you want sync. The default user:// works without that; only enable iCloud if you actively want it.
Common Path Mistakes
Hard-coded paths like C:\Users\name\save.tres work in editor on Windows only. Always use user://.
Trying to write to res:// for editor utilities. Use editor-only code paths and the AssetDatabase API instead. At runtime, res:// is sealed.
“user:// is the only path that writes everywhere without permissions. Saves are user data; that is the right home.”
Related Issues
For Android export keystore errors, see Android Export Keystore. For other resource issues, see Preload Not Finding Resource.
user:// for saves. Share intents for export. No special permissions needed.