Introduction

The Godot Engine stores all project resources (scripts, scenes, images, etc.) as files in the host file system.

You can access these resources using host file system paths, but this is not recommended, because using them can be tricky and difficult to port between different operating systems.

To solve this problem, Godot provides a layer of abstraction over the file system, using two special paths:

  • res:// – this path points at the project root. All project assets are contained in this folder or in its subdirectories. You can write in this path only when editing or running the game in the Godot editor, otherwise it is read-only.
  • user:// – this path is always writable and it’s used, for example, to store game preferences, game saves and data downloaded from the internet.

Also for portability reasons, Godot only supports “/” as a path delimiter, even in Windows.

Another thing to watch out for is that some operating system have case sensitive file systems, while others do not. It could happen that, for this reason, a path that works in a operating system may not work in another. So, it’s important to choose a naming convention and stick to that (the simplest one is to use only lowercase names for files and directories).

In Godot, all file and directory operations are performed through two classes:

  • File: this class is used to read and write data into the file system.
  • Directory: this class is used to manage directories and their content.

Let’s take a look at how to perform the most common file and directory operations in scripts.

How to create and write a file

Here’s a sample on how to write a file:

var file = File.new()
var error = file.open("user://savegame.dat", File.WRITE)
if error == OK:
	file.store_string("Hello World!")
else:
	print("Error opening file!")
file.close()

The first line creates a new object of the File class, which we will use for all subsequent operations.

Then, we use the open() function to open the file. The first argument of the function is the path to the file we want to open / create. The second argument is the opening mode, which can have one of the following values:

  • File.READ: opens the file for read operations.
  • File.WRITE: opens the file for write operations. Create it if the file does not exist and truncate if it exists.
  • File.READ_WRITE: opens the file for read and write operations. Does not truncate the file.
  • File.WRITE_READ: opens the file for read and write operations. Create it if the file does not exist and truncate if it exists.

The open() function returns an error code. If the error code value is OK, the opening is successful, and you can then proceed to write the data to the file.

To write data into the file, Godot provides a set of functions, identified by the store_ prefix. In our example, we used the store_string() function, which writes a string into the file.

Finally, we close the file using the close() function.

Below is a list of all writing functions:

  • store_8(value), store_16(value), store_32(value), store_64(value): these functions store in the file an integer of the length (in bits) specified in their name.
  • store_buffer(buffer): stores the given array of bytes (passed as a PoolByteArray) in the file.
  • store_csv_line(values, delim=”,”): store the given values (passed as a PoolStringArray) in the file as a line formatted in the CSV (Comma Separated Values) format. You can pass as second argument a different delimiter (the default is comma “,”).
  • store_double(value), store_float(value), store_real(value): these functions store a floating point number in the file in double (64 bit) or single (32 bit) precision. store_real() stores the value keeping the size it has in memory.
  • store_string(string), store_line(string), store_pascal_string(string): these functions store a string in the file. store_line() adds a newline character at the end of the string, while store_pascal_string() saves the string in Pascal format (i.e. also store the length of the string).
  • store_var(value, full_objects=false): Stores any Variant value in the file. When full_objects is true, objects can be encoded to file.

How to read a file

Here is an example of how to read a file:

var file = File.new()
if file.file_exists("user://savegame.dat"):
	var error = file.open("user://savegame.dat", File.READ)
	if error == OK:
		var data = file.get_as_text()
		print(data)
	else:
		print("Error opening the file")
	file.close()
else:
	print("File doesn't exists")

After creating an object of type File, we use the file_exists() function to check if the file we want to open exists. If so, we open it with the open() method, specifying File.READ as the opening mode.

If there are no errors in opening the file, we can read its content using any of the File functions prefixed by get_. In this example, we use the get_as_text() function to read the whole file as a string.

Finally, we close the file using the close() function.

Below is a list of all reading functions:

  • get_8(), get_16(), get_32(), get_64(): return the next x bits from the file as an integer, where x is the value specified in their name.
  • get_as_text(): returns the whole file as a string.
  • get_buffer(len): returns next len bytes of the file as a PoolByteArray.
  • get_csv_line(delim=”,”): returns the next value of the file in CSV (Comma Separated Values) format. You can pass a different delimiter to use other than comma.
  • get_double(), get_float(), get_real(): Returns the next 64 or 32 bits from the file as a double precision or single precision floating point number.
  • get_line(): returns the next line of the file as a String.
  • get_pascal_string(): returns a String saved in Pascal format from the file.
  • get_var(allow_objects=false): returns the next Variant value from the file. When allow_objects is true decoding objects is allowed.

How to edit an existing file

Here’s a sample on how to edit an existing file:

var file = File.new()
if file.file_exists("user://savegame.dat"):
	var error = file.open("user://savegame.dat", File.READ_WRITE)
	if error == OK:
		file.seek(6)
		file.store_string("Everybody!")
	else:
		print("Error opening the file")
	file.close()
else:
	print("File doesn't exists")

After creating an object of type File, we use the file_exists() function to check if the file we want to open exists. If so, we open it with the open() method, specifying File.READ_WRITE as the opening mode.

If there are no errors in opening the file, the seek() function is used to move the file read/write cursor to the position (in bytes from the beginning of the file) we want to edit.

At this point, we can use any of the write functions to modify the file data.

Finally, we close the file using the close() function.

How to delete a file

This is the code to delete a file:

var directory = Directory.new()
if directory.file_exists("user://savegame.dat"):
	var error = directory.remove("user://savegame.dat")
	if error:
		print("Error deleting file!")
else:
	print("File not found")

The first line creates a new Directory object.

To check if the file we want to delete exists, we use Directory‘s file_exists() function. If it exists, we delete it using the remove() method. This method returns an error code that we can use to know if the operation was successful.

How to use file compression and encryption

If you want to open a compressed file for reading or writing, you can use the open_compressed() function. In addition to the arguments already seen for open(), this function has a third argument to set the compression mode to use:

  • File.COMPRESSION_FASTLZ: uses the FastLZ compression method.
  • File.COMPRESSION_DEFLATE: uses the Deflate compression method.
  • File.COMPRESSION_ZSTD: uses the Zstd compression method.
  • File.COMPRESSION_GZIP: uses the gzip compression method.

If you want to open a file using encryption, you can use these functions:

  • open_encrypted(path, mode_flags, key): this function use a 256 bit binary encryption key passed as a PoolByteArray.
  • open_encrypted_with_pass(path, mode_flags, password): this function use a password to encrypt/decrypt data.

How to list the content of a directory

Here’s the code to list the content of a directory:

var directory = Directory.new()
var error = directory.open("user://")
if error == OK:
	directory.list_dir_begin()
	var file_name = directory.get_next()
	while (file_name != ""):
		if directory.current_is_dir():
			print(file_name + "/")
		else:
			print(file_name)
		file_name = directory.get_next()
else:
	print("Error opening directory")

After creating a new Directory object and opening the path, we use the list_dir_begin() function to initialize the stream used to list all files and directories.

You can pass two arguments to the list_dir_begin() function:

  • skip_navigational: if true, then . and .. directories would be filtered out (default = false).
  • skip_hidden: if true, then hidden files would be filtered out (default = false).

We use the get_next() function to get the next element (file or directory) in the current directory. Only the name of the file or directory is returned (not its full path).

In a while loop, we check that the filename is not an empty string (when the stream has been fully processed, the method returns an empty string and closes the stream automatically).

If the filename is not empty, we check with the function current_is_dir() if the current element is a directory or a file and, according to this, we decide whether to show the value followed by the slash or not.

As a last step, we call get_next() again to process the next element.

How to create a directory

Here’s a sample on how to create a directory:

var directory = Directory.new()
if not directory.dir_exists("user://user_data"):
	var error = directory.make_dir("user://user_data")
	if error:
		print("Error creating directory")
else:
	print("Directory already exists!")

After creating a Directory object, we use the dir_exists() function to check if the directory we want to create already exists.

If it does not exist, the function make_dir() is used to create the directory. The argument of the function can be a path relative to the current directory (the one eventually set with the open() function), or an absolute path. The target directory should be placed in an already existing directory. If, on the other hand, you want to create the full path recursively, you can use the make_dir_recursive() function.

The make_dir() function returns an error code that we can use to know if the operation was successful.

How to copy a file

This is the code to copy a file:

var directory = Directory.new()
var error = directory.copy("user://savegame.dat", "user://new_savegame.dat")
if error:
	print("Error copying file")

After creating a Directory object, we simply use the copy() method. This method accepts two parameters: the source file and the destination file.

The copy() method returns an error code that we can use to know if the operation was successful.

How to rename/move a file or a directory

The code to rename or move a file is very similar to the one for copying, but uses the rename() function. If the destination directory is different from the original one, the file/directory is moved.

var directory = Directory.new()
	var error = directory.rename("user://user_data", "user://userdata")
	if error:
		print("Error renaming file")

How to recursively copy a directory

Since it’s not possible to copy a directory and its content using the copy() function, we need to create a function that copy each file inside the source directory to the destination directory. Also, we need to rebuild the original subdirectory structure in the target directory.

This is the code to recursively copy a directory:

func copy_recursive(from, to):
	var directory = Directory.new()
	
	# If it doesn't exists, create target directory
	if not directory.dir_exists(to):
		directory.make_dir_recursive(to)
	
	# Open directory
	var error = directory.open(from)
	if error == OK:
		# List directory content
		directory.list_dir_begin(true)
		var file_name = directory.get_next()
		while file_name != "":
			if directory.current_is_dir():
				copy_recursive(from + "/" + file_name, to + "/" + file_name)
			else:
				directory.copy(from + "/" + file_name, to + "/" + file_name)
			file_name = directory.get_next()
	else:
		print("Error copying " + from + " to " + to)

This function can be simply called like this:

copy_recursive("user://user_data", "user://backup/user_data")

How to recursively remove a directory

If a directory is empty, it can be deleted simply by calling the remove() method. However, if the directory is not empty, the operation will fail. For a directory containing other files and subdirectories, we must recursively remove all the content.

This is the code to recursively remove a directory:

func remove_recursive(path):
	var directory = Directory.new()
	
	# Open directory
	var error = directory.open(path)
	if error == OK:
		# List directory content
		directory.list_dir_begin(true)
		var file_name = directory.get_next()
		while file_name != "":
			if directory.current_is_dir():
				remove_recursive(path + "/" + file_name)
			else:
				directory.remove(file_name)
			file_name = directory.get_next()
		
		# Remove current path
		directory.remove(path)
	else:
		print("Error removing " + path)

This function can be simply called like this:

remove_recursive("user://folder_copies")

Other File methods

The File class has other methods that we have not used in the examples, but which are worth mentioning:

  • eof_reached(): returns true if the file cursor has read past the end of the file.
  • get_error(): returns the last error that happened when trying to perform operations.
  • get_len(): returns the size of the file in bytes.
  • get_md5(path): returns an MD5 string representing the file at the given path, or an empty string on failure.
  • get_modified_time(file): returns the last time the file was modified in unix timestamp format or an error string.
  • get_path(): returns the path of the current opened file.
  • get_path_absolute(): returns the absolute path of the current opened file.
  • get_position(): returns the file cursor’s position.
  • get_sha256(path): returns an SHA-256 string representing the file at the given path, or an empty string on failure.
  • is_open(): returns true if the file is currently opened.
  • seek_end(position=0): move the file cursor to the specified position, expressed in bytes from the end of the file. This is an offset, so you should use negative numbers or the cursor will be at the end of the file.

Other Directory methods

The Directory class also has methods that we have not used in the examples:

  • change_dir(): change the current directory. The argument can be relative to the current directory or an absolute path.
  • get_current_dir(): returns the absolute path to the currently opened directory.
  • get_current_drive(): returns the currently opened directory’s drive index.
  • get_drive(int idx): on Windows, return the name of the drive/partition passed as an argument. On other operating system, or if the requested drive doesn’t exists, the method returns an empty string.
  • get_drive_count(): on Windows, return the number of drives/partitions. On other platforms, returns 0.
  • get_space_left(): on Unix systems, return the available space on the current directory’s disk. On other platforms, the method returns 0 or -1.
  • list_dir_end(): close the current stream opened with list_dir_begin(), whether it has been fully processed with get_next() or not.



7 Comments

Avatar

Donkaaay · March 9, 2020 at 6:52 pm

Thank you! Very helpful, well organised and explained information.

Avatar

LaserTSV · April 5, 2020 at 9:55 pm

Great page!
I have a question about working with text files. It seems that Godot only accepts text files with UTF-8 format. Is there a way to read ANSI format file?
Thanks!

    Avatar

    Davide Pesce · April 6, 2020 at 9:05 am

    Unfortunately no, for now there is only unicode support.

Avatar

*** (Sorry, I don't tolerate profanity here) · May 1, 2020 at 9:37 pm

Is reprinting documentation is viable blog strategy? Does this not violate copyright laws?

For anybody interested in going to the source:
https://docs.godotengine.org/en/latest/getting_started/step_by_step/filesystem.html
https://docs.godotengine.org/en/latest/classes/class_file.html
https://docs.godotengine.org/en/latest/classes/class_directory.html

    Avatar

    Davide Pesce · May 2, 2020 at 10:41 am

    Amazing. Every word of what you just said was wrong.
    Luke Skywalker

    All the articles I publish pass the plagiarism test on Grammarly. In any case, the links to the pages about File and Directory classes are at the beginning of the article. You would have noticed if you had read it.

    In addition, I reported a code example for each file operation, and explained how to copy and remove files recursively, all of which are not present in the original documentation.

    Normally I would have simply trashed this comment, given its uselessness. But it was a great opportunity to use Luke Skywalker’s quote!

Avatar

JNG · July 28, 2020 at 12:52 pm

Can you change in GODOT the atributes of a FILE, for example hidding it?

    Avatar

    Davide Pesce · July 29, 2020 at 8:34 am

    No, you can’t with Godot’s classes. You can try using OS.execute() to run system commands (chmod in Linux / MacOS and attrib in Windows)

Leave a Reply

Your email address will not be published. Required fields are marked *

By continuing to browse this Website, you consent to the use of cookies. More information

This Website uses:

By continuing to browse this Website without changing the cookies settings of your browser or by clicking "Accept" below, you consent to the use of cookies.

Close