Working with User Files
The user:// Directory
user:// is the recommended place to store anything your game writes at runtime: saves, settings, logs, screenshots, and downloaded content. It maps to an OS-specific folder, so it works across Windows, macOS, Linux, Android, and iOS.
print(OS.get_user_data_dir())
# Windows example: C:/Users/Name/AppData/Roaming/Godot/app_userdata/MyGame
Why not res://?
res:// points at your project files. In an exported build it is read-only, so always write to user://.
Loading and Saving User Data
Simple text file
const SETTINGS_PATH = "user://settings.txt"
func save_volume(value: float):
var file = FileAccess.open(SETTINGS_PATH, FileAccess.WRITE)
if file:
file.store_line(str(value))
file.close()
func load_volume() -> float:
if not FileAccess.file_exists(SETTINGS_PATH):
return 1.0
var file = FileAccess.open(SETTINGS_PATH, FileAccess.READ)
if not file:
return 1.0
var value = file.get_line().to_float()
file.close()
return value
ConfigFile for settings
const SETTINGS_PATH = "user://settings.cfg"
func save_settings():
var config = ConfigFile.new()
config.set_value("audio", "volume", 0.8)
config.set_value("video", "fullscreen", true)
config.save(SETTINGS_PATH)
func load_settings():
var config = ConfigFile.new()
var err = config.load(SETTINGS_PATH)
if err != OK:
return
var volume = config.get_value("audio", "volume", 1.0)
var fullscreen = config.get_value("video", "fullscreen", false)
Saving JSON
const SAVE_PATH = "user://save.json"
func save_game(state: Dictionary):
var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
if not file:
return
file.store_string(JSON.stringify(state))
file.close()
func load_game() -> Dictionary:
if not FileAccess.file_exists(SAVE_PATH):
return {}
var file = FileAccess.open(SAVE_PATH, FileAccess.READ)
if not file:
return {}
var json = JSON.new()
json.parse(file.get_as_text())
file.close()
return json.data
Common Use Cases
| Use case | Suggested path / pattern |
|---|---|
| Save games | user://saves/slot1.save |
| Settings | user://settings.cfg |
| Logs | user://logs/session.log |
| Screenshots | user://screenshots/ + timestamp |
| UGC / mods | user://mods/ |
# Screenshot example
var img = get_viewport().get_texture().get_image()
var time = Time.get_datetime_string_from_system().replace(":", "-")
DirAccess.make_dir_recursive_absolute("user://screenshots")
img.save_png("user://screenshots/shot_" + time + ".png")
Creating User Directories
DirAccess.make_dir_recursive_absolute("user://saves")
DirAccess.make_dir_recursive_absolute("user://mods/weapons")
Tips and Pitfalls
# Create the directory before writing
DirAccess.make_dir_recursive_absolute("user://saves")
# Check a file exists before loading
if not FileAccess.file_exists("user://save.json"):
return
# user:// is created automatically on first write
# Use OS.get_user_data_dir() to find the real folder for debugging
Quick Summary:
- Use
user://for all runtime writes. FileAccess,ConfigFile, andJSONcover most save/load needs.- Create missing directories with
DirAccess.make_dir_recursive_absolute. OS.get_user_data_dir()reveals the actual OS path.