Saving and Loading Games in Godot 4 (with resources)
Saving and loading game data is essential for most games so players can pick up where they left off. In Godot, you can save data in several ways. Resources are the system Godot uses for most project files. You might already use them for character stats, items, or progression. Once you learn them, they immediately appear as a good solution for saving and loading game data.
In this guide you will learn how to save and load game data using resources, the advantages and disadvantages of this method, and also see when another method is the better choice.
Before we start, there's one thing you should know: loading a resource can run code (and not only resources).
If a player sends you a save file for debugging via email, it could contain malicious code that will run when loaded. Similarly, if your game gets big enough so that people exchange save files, a player could download a save file online: any code inside it will run.
This is not much of a concern for browser-based games (browsers sandbox the game), but it is something to be aware of when players can send you or each other files.
And, this is not limited or specific to resources: most save and load methods in Godot support running code when loading!
I explain this in detail in the security section below, along with a mention of two save methods that do not run code by default: FileAccess.store_var() and JSON.
Nathan
Founder and teacher at GDQuest
This video was made with Godot 3. The guide below has been rewritten for Godot 4.
Resources are Godot's most hands-off way of storing reusable data, allowing you to save and load variables with just a few lines of code. In this study guide, you'll learn:
How to create your own resource classes to hold save game data
How to save and load your game using Godot's ResourceSaver and ResourceLoader
How to handle complex save data by nesting resources inside each other
What changed in Godot 4 to make resource-based saving easier
What the security tradeoff of saving with resources is, and when to prefer another save method
How to save and load data without the risk of running code, using FileAccess.store_var() or JSON
Why would you consider resources for save games?
It's an idea that comes naturally from using the engine: Resources are everywhere in Godot and used for all project files. Your scenes, UI themes, and scripts are all resources.
Compared to JSON or other text formats, resources are also tightly integrated with the engine, giving you static typing, less code to save and load back, and they work seamlessly with all Godot data types like Vector2Vector2, ColorColor, or NodePathNodePath.
You can also edit resource files directly in the inspector during development, which can make debugging easier.
Plus, resources can contain other resources. You can build complex save structures naturally: if your game uses resources for inventories or character stats, you can reference them from your savegame resource and save and load them directly without writing data conversion code. All in all, they're a really convenient way to serialize and deserialize game data.
Alternatively, you may also consider using FileAccess.store_var() and FileAccess.get_var() for safe (but a little more constraining) binary serialization.
The open source demo that comes with this guide offers an example of both approaches (resources and FileAccessFileAccess). You'll find it in the Download section below.
What about security? Can't resources execute code?
That's right, but let's be exact about where the risk comes from, because it's not just resources: The problem is more generally loading objects. To put things simply, objects in Godot can contain and run code. This means that a data file downloaded from the web can potentially run harmful code if it is manually copied into the game's save folder by a player.
Similarly, if a malicious actor sends you their save file for debugging and you use this save method, they could potentially run harmful code on your computer if you do not check the file first (or test in a safe environment).
Several of Godot's save and load methods support loading objects, with object loading turned on by default:
Resources (this includes loading scene files and nearly all Godot project files)
Using a method that does not load objects by default.
Using a plugin to wrap around resource loading and make it more secure.
For the first option, you have two safe methods to choose from:
The first save method is using FileAccess.store_var() and FileAccess.get_var() to save data in a binary format. This is the way the Godot community usually recommends today.
Another safe method is JSON, which saves data as text. In previous Godot versions, using JSON used to require a lot of conversion code, because JSON does not natively support Godot data types. In recent versions, Godot added two utility functions, JSON.from_native() and JSON.to_native(), to help you convert Godot data types to and from JSON.
When using these methods, you need to collect and extract the data to save from your game, convert it into dictionaries, and back, so it can potentially be more code than using resources.
For the second option, you have two add-ons to choose from:
The Godot Safe Resource Loader is a drop-in replacement for ResourceLoader.load() that checks text resource files for code before loading them. It shields against known attack vectors when loading resources.
Safe Resource Format is a more elaborate solution that replaces the engine's resource saving and loading functions and lets you whitelist the resources your data files are allowed to use.
If you want to use resources for saving and loading, consider integrating one of these solutions.
How worried should I be about this?
For most small projects, you don't have to be too worried. Security experts use the idea of a threat model: instead of asking "Can this be attacked?" (the answer is almost always yes), you ask yourself "Who would attack this, how, and what would they gain?"
To do harm with a save file, someone has to make a malicious save for your game, convince a player to download it, and get them to copy it into the save folder by hand. This isn't that different from getting a player to download a random executable and run it.
So why point specifically at resources for being insecure? It's about expectations. Players know that applications and mods are code, but nobody expects a save file to run code.
In short, resources are a problem when:
Players share save files online for your game.
You open save files that strangers send you.
You load resources received over the network (user-made levels, multiplayer).
Your game is popular enough for a bad actor to take the time to build a malicious save file.
The risk grows with your game's popularity. The plugins linked above, Godot Safe Resource Loader and Safe Resource Format, are easy to set up, so if you use resources for saves, review both and integrate one of them into your project before release.
Alternatively, consider using FileAccessFileAccess to read and write save files directly, without using resources. You will find an example in the downloadable project at the end of this page.
What changed in Godot 4
When we originally created this demo for Godot 3, resources had two limitations that required workarounds: First, we couldn't easily save and load arrays of resources, like the contents of an inventory. We had to use dictionaries instead. Second, the engine's resource cache would prevent reloading already loaded resources like savegames, forcing us to write extra code to bypass the cache.
Both of these issues are now fixed in Godot 4. You can save and load arrays of resources without any special handling, and the cache works correctly with the CACHE_MODE_IGNORE flag. You need even less save-game-specific code. Your save feature is simpler than before (which is the main reason resources are appealing for saving and loading in the first place).
The main workflow stays the same: create a custom resource class, add exported variables for your data, and use ResourceSaver.save() and ResourceLoader.load(). These operations now work more reliably with fewer edge cases to handle.
Godot 4's save methods
Before diving into implementation, it's worth understanding how Godot's different save methods work under the hood. Godot offers multiple engine-native ways to serialize data: resources (.tres for text and .res files for binary), ConfigFile (.cfg files with an ini-style format), and var_to_str() for converting variables to strings.
Resources, ConfigFile, and var_to_str() all use the same serialization under the hood. They convert your data to a format that Godot can read back later, and they all support Godot-specific data types like Vector2Vector2, ColorColor, and NodePathNodePath. They can also save and load objects and resources directly, with object loading on by default.
As we saw in the security section above, loading objects is what makes a save method able to run code from files made by other people.
The FileAccessFileAccess methods (store_var() and get_var()) work differently: they use the engine's var_to_bytes() and bytes_to_var() functions that serialize to a binary format and disable object serialization by default.
This means no code execution risk by default. However, you can optionally enable object serialization with these methods, in which case they have the same security implications as resources.
If you're already using custom resources in your game for items, characters, quests, and so on, collecting save data is quick: you just reference those resources from your save game resource and Godot handles the rest.
What about JSON?
JSON is widely used and familiar to many developers, so people like to use it when coming from different technologies. It's a text format that allows you to read the saved data in a text editor. Because it's the data format of the JavaScript language, it does not natively support Godot data types.
Recent Godot versions made JSON easier to use for saving and loading game data. The functions JSON.from_native() and JSON.to_native() convert Godot types to and from JSON for you.
JSON is a good fit when you want a safe text format that other tools can read, for web requests, or when you need to exchange data with external applications. Otherwise, you can use FileAccess.store_var() to save data in a binary format.
Creating your first save game resource
Let's start with the basics. To create a resource-based savegame, you start by making a custom class that extends Resource. Then, you add exported variables for the data you want to save. Here's a simple example:
This save resource contains two pieces of data: the player's coin count and global position in the world. The @export keyword tells the engine to serialize those values: Godot will save and load them automatically when you use the resource. You don't have to do anything special.
Now let's look at how to actually save and load your game data. You can save any resource by calling ResourceSaver.save(). Usually, you'll do that from a dedicated script, like your main game script or an autoload that manages savegames:
In this code, at the start of the game, we either load an existing savegame or create a new one if none exists.
If the savegame file exists, ResourceLoader.load() loads the saved resource from disk and gives you back a SaveGame instance with all your data restored. The CACHE_MODE_IGNORE flag is important here. It tells Godot to reload the resource from disk instead of using a cached version already loaded in memory. Without this flag, if someone reloads a loaded game, nothing will change because Godot will use the resource in memory.
In a single line, you can save all your @export variables to a .tres file. The file is human-readable text that you can open in any text editor or directly in Godot's inspector. This makes testing and debugging much easier when you're working on your game. The save happens instantly and synchronously, which means when the function returns, your data is already on disk.
You can expand this code to handle any amount of data. Just add more variables to your SaveGame resource, and copy them back and forth between your game and the save resource when you need to.
Handling errors in production
The examples here just show the basics and skip error checks to keep things clear and simple. In a real game, you should check the return value of ResourceSaver.save(). It returns an error code that you can use to know if an error occurred when trying to write the file:
funcsave()->void:var error_code := ResourceSaver.save(save_game,SAVE_PATH)if error_code !=OK:push_error("Failed to save game: "+error_string(error_code))
The function error_string() produces a human-readable name for the error code returned by ResourceSaver.save().
Working with complex save data
So far the save code looks simple but also underwhelming: it's easy to save a few variables, but most games have more complex data to track. You'll have characters with stats, inventories filled with items, quests in progress, and so on.
The power of resource-based saves becomes clear when you work with complex game data. Resources can contain other resources or arrays of resources as @export variables, which lets you organize save data however you'd like.
For example, in an RPG you might have a Character resource that groups all stats for one character:
This resource isn't save-specific: you'd use it throughout your game to display the character name and stats in menus, calculate damage in combat, know what speed to use in _physics_process(), and so on. But because it's a resource, you can also save it directly.
When you save this SaveGame resource, Godot automatically serializes the entire array of party members with all the properties of the Character resource. When you load it back, you get fully populated Character instances with all their stats. You don't need to write any conversion code or manually reconstruct the nested data.
This approach scales naturally to a fair amount of complexity. You could have an Inventory resource containing an array of Item resources or a QuestLog resource tracking quest progress. We're simply using resources as composable structures we can reference from different parts of our game.
Questions and troubleshooting
How can I prevent players from editing save files?
Many developers want to prevent players from editing their save files. The first thing to note is there's always a way for tech-savvy people to mess with game files. People manage to extract and modify data from AAA games with proprietary security features. As an independent developer or small team, you won't be able to prevent that entirely.
What you can do is make it harder for the average player to edit save files without special tools or knowledge.
The simplest approach with resources is to save as binary in release builds instead of text. Change the file extension from .tres to .res. Binary files aren't human-readable in a text editor, which stops really easy editing.
You can use OS.is_debug_build() to use the text format during development and binary for releases. In this example I code a little helper function to get a different savegame path in debug and release builds:
This way you keep the convenience of readable save files when working on your game while making them harder to edit in the final game (also smaller and faster to read and write as a bonus).
Another option is to use FileAccess.store_var() with encryption enabled. When you open a file with FileAccess.open_encrypted(), you pass an encryption key. Godot encrypts the data when saving and decrypts it when loading.
Keep in mind that encryption makes saving and loading slower because of the extra encryption step. This won't matter in small games, but with larger save files you might notice the difference.
Also, the encryption key has to be in your scripts somewhere, so anyone who looks at your game code can find it. You could compile Godot with game file encryption to make finding the key harder, but again, this just raises the bar rather than making it impossible.
For most games, if you want to limit access to editing save files, I recommend just saving as binary as it is enough to prevent casual editing without adding complexity or a performance overhead.
How about the performance of saving and loading resources? Is it good?
Yes, it's fast! Saving and loading bits of data (strings, numbers, vectors...) is nothing for a modern computer. Godot's scene files (tscn files) are resources. If you've ever made a huge 2D level with a tilemap, you've experienced the engine saving and loading a large resource file quickly (when you double-click the scene in the file system dock, for example).
Doesn't this approach tie savegames to resource scripts?
Yes, it does. If you change the structure of your savegame resource or game resources (especially remove or rename variables), old save files might not load correctly anymore. Note that this is true of any resources you would use in your game, savegame or not.
Generally speaking, whenever your data structures change in a game, you have to handle migrating old data to the new format. This is true whether you're using resources, JSON, or any other format. More generally, it's a common challenge in software development. When your serialized data changes, you need to implement data migrations: write code to convert old data to the new format.
When working with resources, if you make major changes to your savegame structure, my recommendation is to:
Keep the old resource classes around, treating them as a schema for old save files.
Write migration code that loads the old save files using the old resource classes, then creates new instances of the updated resource classes, copying over and transforming data as needed.
Save the new resource instances to new save files.
I actually tend to do the same for any serialized data, not just savegames, based on a little experience working with databases, where schema migrations are a common practice.
Import the zip file in Godot 4.5 or later to explore the code and see how it works. There are two demos:
res://demos/resource/demo_resource.tscn: A savegame using resources only.
res://demos/file_access/demo_file_access.tscn: A savegame using FileAccess.store_var() and FileAccess.get_var(). The game level still uses resources for game data, but the savegame itself is a dictionary saved in binary.
updates / code patches
This demo was originally made with Godot 3 and was updated to Godot 4.5. Suggest an update.