• Tutorials
  • How to save things with Godot, for real

I have been seeing many questions about how to "properly" save things with Godot in the discord server, so I wanted to write a complete tutorial about how to save stuff, from easy to advanced solutions, for beginners and advanced users , hoping this help.


So, you want to save data? Maybe your player score? maybe a respawn point? or maybe a very complex data structure that involves very delicated and fine grained bytes? Godot got you covered.

The thing with Godot is that there's no only one solution, you had many solutions.

But before seeing these solutions, you must know what you want to save first.

I'll help you defining how and where you can use these saved methods, and you decide what to use, deal? Deal.

Define what you want to save first

Don't say "I'm going to use [Your Favorite Format Here] to save my things", this will probably over-complicate the process for you and your team. Decide what you really want to save first, and then say "That data is going to be saved using [Your Favorite Format Here]" if you want.

Why? Because is easier to define what tools do you need according your requirements than having the tools first and then meet the requirements.

For this example, let's assume that we want to save the player health, player name and player points. An integer, an string and a real(float) value.

Decide what format do you want to use to save your data

This step may be tricky, specially for new users. You know what you want to save, but what kind of file are you going to use? Godot let you use whatever you want, so from all file formats that exist, what would be the "ideal" format?

A while ago I made a little flowchart to help you to decide what you need according what you want to save, here's an updated version:

But should I save nodes?

Most of the time you don't want to save a node, you want to save the data that node contains.

Save nodes when you want to save the node structure itself, with the current applied values

Where do I save my data files?

Save to user:// folder.

res:// folder will be read-only when you export the game.

You can read more about file paths in Godot on its official documentation page.

Using ConfigFile

ConfigFile is a helper class that helps you writing INI (.ini) style format, which its structure looks like:

[section]
variable_name = <variable value>

This is widely used when you want to save configuration files, I've also seen the usage of this format on some mod loaders, where they define the assets and workflow of the mod.

With Godot, you only need to define a ConfigFile object to save or load the data, and each data/section you need to save or load:

# Let's assume the PlayerNode is the node where we save the player data in game
var PlayerNode:Node

var save_path := "user://player_data.ini"

# To save data
func save() -> void:
  var config_file := ConfigFile.new()

  config_file.set_value("Player", "health", PlayerNode.health)
  config_file.set_value("Player", "name", PlayerNode.name)
  config_file.set_value("Player", "points", PlayerNode.points)

  var error := config_file.save(save_path)
  if error:
    print("An error happened while saving data: ", error)

# To load data
func load() -> void:
  var config_file := ConfigFile.new()
  var error := config_file.load(save_path)

  if error:
    print("An error happened while loading data: ", error)
    return

  PlayerNode.health = config_file.get_value("Player", "health", 1)
  PlayerNode.name = config_file.get_value("Player", "name", "UNDEFINED")
  PlayerNode.points = config_file.get_value("Player", "points", 0.0)

The usage from Godot 3 and Godot 4 is practically the same with code. In Godot 3, you'll need to use an external text editor to see the saved data, Godot 4 let you see your ConfigFile in editor, through Script editor, loading it as plain text.

The main advantage of using ConfigFile is that Godot knows what is saved to a ConfigFile, so data types are preserved, even if is an Object.

Using JSON

JSON stands for JavaScript Object Notation*, and is widely used for data transference between things that are not directly related but needs and easy and human readable way to transfer that data.

In a file, it may look like a GDScript dictionary:

{
  "variable_name": <variable value>
}

There's no scenario apart from the data transference that I should recommend this format. Its usage really depends on you, and is ok if you don't use it for data transference.

# Let's assume the PlayerNode is the node where we save the player data in game
var PlayerNode:Node

var save_path := "user://player_data.json"

# To save data
func save() -> void:
  var data := {
    "name": PlayerNode.name,
    "health": PlayerNode.health,
    "points": PlayerNode.points
  }

  var json_string := JSON.stringify(data)

  # We will need to open/create a new file for this data string
  var file_access := FileAccess.open(save_path, FileAccess.WRITE)
  if not file_access:
    print("An error happened while saving data: ", FileAccess.get_open_error())
    return

  file_access.store_line(json_data)
  file_access.close()

# To load data
func load() -> void:
  if not FileAccess.file_exists(save_path):
    return
  var file_access := FileAccess.open(SAVE_PATH, FileAccess.READ)
  var json_string := file_access.get_line()
  file_access.close()

  var json := JSON.new()
  var error := json.parse(json_string)
  if error:
    print("JSON Parse Error: ", json.get_error_message(), " in ", json_string, " at line ", json.get_error_line())
    return
  # We saved a dictionary, lets assume is a dictionary
  var data:Dictionary = json.data
  PlayerNode.name = data.get("name", "UNDEFINED")
  PlayerNode.health = data.get("health", 1)
  PlayerNode.points = data.get("points", 0.0)

JSON has some limitations* which can be summarized on:

  • It doesn't recognize what a real (float) or integer (int) value is, it saves a number.
  • It can't save all data types, only arrays, dictionaries, numbers and strings.

Using FileAccess (File)

FileAcess (or just File in Godot 3) is a helper class to write and read files. No fancy stuff, you're dealing directly with the file itself.

Usually, you use FileAccess in complex scenarios, where you need total control about how the stuff in the file is read or written. Note that most of the FileAcess functions reads and write binary files, only string/text related functions writes and read from human readable text.

This may be useful when you want to write your own format.

# Let's assume the PlayerNode is the node where we save the player data in game
var PlayerNode:Node

var save_path := "user://player_data.dat" # <- custom format

func save(content):
  var file = FileAccess.open(save_path, FileAccess.WRITE)
  file.store_line(PlayerNode.name)
  file.store_32(PlayerNode.health)
  file.store_float(PlayerNode.points)
  file.close()
  

func load():
  var file = FileAccess.open(save_path, FileAccess.READ)
  # Order matters. The same order you use to write, is the order you use to read
  PlayerNode.name = file.get_line()
  PlayerNode.health = file.get_32()
  PlayerNode.points = file.get_float()
  file.close()

If you try to read the generated file with a text editor you'll see this:

Your text editor tries to convert the binary data into unicode characters, resulting in that weird looking characters. The real data may look like this:

Using Resources

Resource is a class that you can serialize directly to a file using Godot ResourceSaver and deserialize using ResourceLoader. They were made for data, they live for data.

Majority (if not all) files you see in editor FileSystem is a Resource.

Resources are something that Godot engine can easily understand, and the way that it loads or save stuff can be modified/extended, so it gives you the same power as if you were writing it with other methods.

Additional to that, since is an object, this data can be shared between instances, it can hold methods, it can emit signals and their variables can define setters and getters.

Let's define one for our data:

@tool
extends Resource
class_name PlayerData

@export var name:String = "Default"
@export var health:int = 1
@export var points:float = 0.0

That's our data class. class_name is optional, but would help us to use it in other scripts and makes it to appear in the resource creation list.

Using our current configuration, saving data would look like this:

# Let's assume the PlayerNode is the node where we save the player data in game
var PlayerNode:Node

var save_path := "user://player_data.tres" # <- tres is Text RESource

func save() -> void:
  var data := PlayerData.new()
  data.name = PlayerNode.name
  data.health = PlayerNode.health
  data.points = PlayerNode.points

  var error := ResourceSaver.save(data, save_path)
  if error:
    print("An error happened while saving data: ", error)

func load() -> void:
  var data:PlayerData = load(save_path)
  PlayerNode.name = data.name
  PlayerNode.health = data.health
  PlayerNode.points = data.points

This works, but we miss the power of sharing the data between instances, what we can do is to update the data hold in the node:

# node script
@export data:PlayerData = PlayerData.new()

and now we can just do

func save() -> void:
  ResourceSaver.save(PlayerNode.data, save_path)

func load() -> void:
  PlayerNode.data = load(save_path)

This way you keep your data and your node code separated, so the data doesn't rely on the node and you can share/modify it and let other node handle it.

But I heard from someone that resources are insecure

Generally, you don't worry about security.

... there's no good reason to prevent malicious users from running arbitrary code, even if you did not provide an official way to do it. But there's also no good way. (xananax)

Yes, is true that Resources can execute arbitrary code, but this is true for all resources, not only yours. This is even true for the files you see in FileSystem, and all things loaded from FileAcess that allows to bind complete objects (which includes the script). Is a more complex topic than just "I don't want to be hacked", a topic that we don't need to deal with.

Your resource files are not something that you'll share with the user, and if you'll do, you can write custom format parsers to read/write from file, so there's no problem at the end.

Haha, I pushed the post not knowing that you can't edit the original post, now I can't add how to create custom serializers for resources.

Welp, I'll make a new post, maybe is a most advanced topic, and will link to it here.

Hope you found this post useful, and let me know if you had any questions/commentaries about it and how it can be improved

    AnidemDex you can't edit the original post

    You should be able to edit your original post. If you can't, flag this post (you can't flag your own posts) for the admin's attention, and explain the problem. It could be because you haven't been added as a verified user, or whatever it's called.

      IMO json is more human-readable than ini/cfg as long as you have a decent json reader. cfg/ini is still better if your users are going to be directly editing it though.

        award That's true, but I've never considered using fancy editors, just godot editor or notepad

        My favorite is https://jsoneditoronline.org it's pretty simple and web-based but has great features.

        (Don't use an online editor if you have anything sensitive like private keys though)

        9 days later

        Thank you for your helpful post! Something I learned today while perusing the docs to see if GDScript has a context manager like Python is that you don't need to call close() on the file, at least not in these examples.
        FileAccess will automatically close when it's freed, which happens when it goes out of scope or when it gets assigned with null. In C# the reference must be disposed after we are done using it, this can be done with the using statement or calling the Dispose method directly.
        I thought this was interesting! Tbh, I'll probably continue to call close() because it makes me nervous not seeing it explicitly handled. 😅

        3 months later

        I know this is an older thread, but THANK YOU. Figuring out Saving/Loading has been my personal nightmare, and this helps immensely!!

        a month later
        5 days later

        at first i thought of using json but it quickly turned messy. easiest is to use godot's built in resources.
        Im kinda interested how would you save 3D world state for like 100s of npc's and their positions and inventory, attack states and other params? I havent made anything that big yet but this is on my mind.

        Is there a post on how to structure data in your games, for multiplayer games?

          kuligs2 Im kinda interested how would you save 3D world state for like 100s of npc's and their positions and inventory, attack states and other params?

          That's an interesting question. I have yet to tackle it, but I envisage experimenting with DB. Nope, I won't have attacks (almost) and the game is single player. But hundreds of characters (potentially several thousand) and different states and statuses.

            Tomcat I always wondered, if you use sea-quel (SQL) DB for the game, you need to run server. How do you package SQL server with the game? Its understandable if you make GAAS type of a game, where it doesnt work if you are not connected to some service, but as a ofline SP/MP game? I know how to do it, as standalone application, using docker, pretty easy to run DB server, but im more interested how to run it inside the game itself. And maybe if its a multiplayer game, how do you share data with the other player client? Do you make entries in the other players DB or do they both connect to one server?.. etc..

              kuligs2 pretty easy to run DB server, but im more interested how to run it inside the game itself.

              Maybe I don't know much yet, but why can't a game run a DB server internally?

              The thing is, I'm not making a one-off game, but a project. In which I'm going to start with a simple game, and then complicate and expand it. And in the beginning there will be one character, then two, then a dozen. At that I will use resources and json. And when it gets closer to a hundred, I will start experimenting with SQL. So these are very distant plans.

              Multiplayer, or rather the ability to exchange data, I assume to do, yeah, through a single dedicated server, not directly between players. But this is an optional feature, not mandatory for the game.

              kuligs2 How do you package SQL server with the game?

              SQLite is a self-contained database engine. There's no separate client and server. It lets you access a database, which resides in a file, using SQL queries.

              There's an SQLite Godot addon, but I haven't had a reason to use it.
              https://godotengine.org/asset-library/asset?filter=sqlite&category=&godot_version=&cost=&sort=updated

              A separate DB client and server are needed if the database resides on a remote server, and you want it to serve queries from a local client. Another case could be separating the client and server processing for performance reasons.

              kuligs2 I always wondered, if you use sea-quel (SQL) DB for the game, you need to run server. How do you package SQL server with the game? Its understandable if you make GAAS type of a game, where it doesnt work if you are not connected to some service, but as a ofline SP/MP game?

              TL;DR: You can run a local server along side with the game executable or even in memory of the game executable itself.