GDScript style guide

This style guide lists conventions to write elegant GDScript. The goal is to encourage writing clean, readable code and promote consistency across projects, discussions, and tutorials. Hopefully, this will also support the development of auto-formatting tools.

Since GDScript is close to Python, this guide is inspired by Python’s PEP 8 programming style guide.

Style guides aren’t meant as hard rulebooks. At times, you may not be able to apply some of the guidelines below. When that happens, use your best judgment, and ask fellow developers for insights.

In general, keeping your code consistent in your projects and within your team is more important than following this guide to a tee.

Note

Godot’s built-in script editor uses a lot of these conventions by default. Let it help you.

Here is a complete class example based on these guidelines:

  1. class_name StateMachine
  2. extends Node
  3. ## Hierarchical State machine for the player.
  4. ##
  5. ## Initializes states and delegates engine callbacks ([method Node._physics_process],
  6. ## [method Node._unhandled_input]) to the state.
  7. signal state_changed(previous, new)
  8. @export var initial_state: Node
  9. var is_active = true:
  10. set = set_is_active
  11. @onready var _state = initial_state:
  12. set = set_state
  13. @onready var _state_name = _state.name
  14. func _init():
  15. add_to_group("state_machine")
  16. func _enter_tree():
  17. print("this happens before the ready method!")
  18. func _ready():
  19. state_changed.connect(_on_state_changed)
  20. _state.enter()
  21. func _unhandled_input(event):
  22. _state.unhandled_input(event)
  23. func _physics_process(delta):
  24. _state.physics_process(delta)
  25. func transition_to(target_state_path, msg={}):
  26. if not has_node(target_state_path):
  27. return
  28. var target_state = get_node(target_state_path)
  29. assert(target_state.is_composite == false)
  30. _state.exit()
  31. self._state = target_state
  32. _state.enter(msg)
  33. Events.player_state_changed.emit(_state.name)
  34. func set_is_active(value):
  35. is_active = value
  36. set_physics_process(value)
  37. set_process_unhandled_input(value)
  38. set_block_signals(not value)
  39. func set_state(value):
  40. _state = value
  41. _state_name = _state.name
  42. func _on_state_changed(previous, new):
  43. print("state changed")
  44. state_changed.emit()
  45. class State:
  46. var foo = 0
  47. func _init():
  48. print("Hello!")

Formatting

Encoding and special characters

  • Use line feed (LF) characters to break lines, not CRLF or CR. (editor default)

  • Use one line feed character at the end of each file. (editor default)

  • Use UTF-8 encoding without a byte order mark. (editor default)

  • Use Tabs instead of spaces for indentation. (editor default)

Indentation

Each indent level should be one greater than the block containing it.

Good:

  1. for i in range(10):
  2. print("hello")

Bad:

  1. for i in range(10):
  2. print("hello")
  3. for i in range(10):
  4. print("hello")

Use 2 indent levels to distinguish continuation lines from regular code blocks.

Good:

  1. effect.interpolate_property(sprite, "transform/scale",
  2. sprite.get_scale(), Vector2(2.0, 2.0), 0.3,
  3. Tween.TRANS_QUAD, Tween.EASE_OUT)

Bad:

  1. effect.interpolate_property(sprite, "transform/scale",
  2. sprite.get_scale(), Vector2(2.0, 2.0), 0.3,
  3. Tween.TRANS_QUAD, Tween.EASE_OUT)

Exceptions to this rule are arrays, dictionaries, and enums. Use a single indentation level to distinguish continuation lines:

Good:

  1. var party = [
  2. "Godot",
  3. "Godette",
  4. "Steve",
  5. ]
  6. var character_dict = {
  7. "Name": "Bob",
  8. "Age": 27,
  9. "Job": "Mechanic",
  10. }
  11. enum Tiles {
  12. TILE_BRICK,
  13. TILE_FLOOR,
  14. TILE_SPIKE,
  15. TILE_TELEPORT,
  16. }

Bad:

  1. var party = [
  2. "Godot",
  3. "Godette",
  4. "Steve",
  5. ]
  6. var character_dict = {
  7. "Name": "Bob",
  8. "Age": 27,
  9. "Job": "Mechanic",
  10. }
  11. enum Tiles {
  12. TILE_BRICK,
  13. TILE_FLOOR,
  14. TILE_SPIKE,
  15. TILE_TELEPORT,
  16. }

Trailing comma

Use a trailing comma on the last line in arrays, dictionaries, and enums. This results in easier refactoring and better diffs in version control as the last line doesn’t need to be modified when adding new elements.

Good:

  1. var array = [
  2. 1,
  3. 2,
  4. 3,
  5. ]

Bad:

  1. var array = [
  2. 1,
  3. 2,
  4. 3
  5. ]

Trailing commas are unnecessary in single-line lists, so don’t add them in this case.

Good:

  1. var array = [1, 2, 3]

Bad:

  1. var array = [1, 2, 3,]

Blank lines

Surround functions and class definitions with two blank lines:

  1. func heal(amount):
  2. health += amount
  3. health = min(health, max_health)
  4. health_changed.emit(health)
  5. func take_damage(amount, effect=null):
  6. health -= amount
  7. health = max(0, health)
  8. health_changed.emit(health)

Use one blank line inside functions to separate logical sections.

Note

We use a single line between classes and function definitions in the class reference and in short code snippets in this documentation.

Line length

Keep individual lines of code under 100 characters.

If you can, try to keep lines under 80 characters. This helps to read the code on small displays and with two scripts opened side-by-side in an external text editor. For example, when looking at a differential revision.

One statement per line

Avoid combining multiple statements on a single line, including conditional statements, to adhere to the GDScript style guidelines for readability.

Good:

  1. if position.x > width:
  2. position.x = 0
  3. if flag:
  4. print("flagged")

Bad:

  1. if position.x > width: position.x = 0
  2. if flag: print("flagged")

The only exception to that rule is the ternary operator:

  1. next_state = "idle" if is_on_floor() else "fall"

Format multiline statements for readability

When you have particularly long if statements or nested ternary expressions, wrapping them over multiple lines improves readability. Since continuation lines are still part of the same expression, 2 indent levels should be used instead of one.

GDScript allows wrapping statements using multiple lines using parentheses or backslashes. Parentheses are favored in this style guide since they make for easier refactoring. With backslashes, you have to ensure that the last line never contains a backslash at the end. With parentheses, you don’t have to worry about the last line having a backslash at the end.

When wrapping a conditional expression over multiple lines, the and/or keywords should be placed at the beginning of the line continuation, not at the end of the previous line.

Good:

  1. var angle_degrees = 135
  2. var quadrant = (
  3. "northeast" if angle_degrees <= 90
  4. else "southeast" if angle_degrees <= 180
  5. else "southwest" if angle_degrees <= 270
  6. else "northwest"
  7. )
  8. var position = Vector2(250, 350)
  9. if (
  10. position.x > 200 and position.x < 400
  11. and position.y > 300 and position.y < 400
  12. ):
  13. pass

Bad:

  1. var angle_degrees = 135
  2. var quadrant = "northeast" if angle_degrees <= 90 else "southeast" if angle_degrees <= 180 else "southwest" if angle_degrees <= 270 else "northwest"
  3. var position = Vector2(250, 350)
  4. if position.x > 200 and position.x < 400 and position.y > 300 and position.y < 400:
  5. pass

Avoid unnecessary parentheses

Avoid parentheses in expressions and conditional statements. Unless necessary for order of operations or wrapping over multiple lines, they only reduce readability.

Good:

  1. if is_colliding():
  2. queue_free()

Bad:

  1. if (is_colliding()):
  2. queue_free()

Boolean operators

Prefer the plain English versions of boolean operators, as they are the most accessible:

  • Use and instead of &&.

  • Use or instead of ||.

  • Use not instead of !.

You may also use parentheses around boolean operators to clear any ambiguity. This can make long expressions easier to read.

Good:

  1. if (foo and bar) or not baz:
  2. print("condition is true")

Bad:

  1. if foo && bar || !baz:
  2. print("condition is true")

Comment spacing

Regular comments (#) and documentation comments (##) should start with a space, but not code that you comment out. Additionally, code region comments (#region/#endregion) must follow that precise syntax, so they should not start with a space.

Using a space for regular and documentation comments helps differentiate text comments from disabled code.

Good:

  1. # This is a comment.
  2. #print("This is disabled code")

Bad:

  1. #This is a comment.
  2. # print("This is disabled code")

Note

In the script editor, to toggle commenting of the selected code, press Ctrl + K. This feature adds/removes a single # sign before any code on the selected lines.

Whitespace

Always use one space around operators and after commas. Also, avoid extra spaces in dictionary references and function calls. One exception to this is for single-line dictionary declarations, where a space should be added after the opening brace and before the closing brace. This makes the dictionary easier to visually distinguish from an array, as the [] characters look close to {} with most fonts.

Good:

  1. position.x = 5
  2. position.y = target_position.y + 10
  3. dict["key"] = 5
  4. my_array = [4, 5, 6]
  5. my_dictionary = { key = "value" }
  6. print("foo")

Bad:

  1. position.x=5
  2. position.y = mpos.y+10
  3. dict ["key"] = 5
  4. myarray = [4,5,6]
  5. my_dictionary = {key = "value"}
  6. print ("foo")

Don’t use spaces to align expressions vertically:

  1. x = 100
  2. y = 100
  3. velocity = 500

Quotes

Use double quotes unless single quotes make it possible to escape fewer characters in a given string. See the examples below:

  1. # Normal string.
  2. print("hello world")
  3. # Use double quotes as usual to avoid escapes.
  4. print("hello 'world'")
  5. # Use single quotes as an exception to the rule to avoid escapes.
  6. print('hello "world"')
  7. # Both quote styles would require 2 escapes; prefer double quotes if it's a tie.
  8. print("'hello' \"world\"")

Numbers

Don’t omit the leading or trailing zero in floating-point numbers. Otherwise, this makes them less readable and harder to distinguish from integers at a glance.

Good:

  1. var float_number = 0.234
  2. var other_float_number = 13.0

Bad:

  1. var float_number = .234
  2. var other_float_number = 13.

Use lowercase for letters in hexadecimal numbers, as their lower height makes the number easier to read.

Good:

  1. var hex_number = 0xfb8c0b

Bad:

  1. var hex_number = 0xFB8C0B

Take advantage of GDScript’s underscores in literals to make large numbers more readable.

Good:

  1. var large_number = 1_234_567_890
  2. var large_hex_number = 0xffff_f8f8_0000
  3. var large_bin_number = 0b1101_0010_1010
  4. # Numbers lower than 1000000 generally don't need separators.
  5. var small_number = 12345

Bad:

  1. var large_number = 1234567890
  2. var large_hex_number = 0xfffff8f80000
  3. var large_bin_number = 0b110100101010
  4. # Numbers lower than 1000000 generally don't need separators.
  5. var small_number = 12_345

Naming conventions

These naming conventions follow the Godot Engine style. Breaking these will make your code clash with the built-in naming conventions, leading to inconsistent code. As a summary table:

Type

Convention

Example

File names

snake_case

yaml_parser.gd

Class names

PascalCase

class_name YAMLParser

Node names

PascalCase

Camera3D, Player

Functions

snake_case

func load_level():

Variables

snake_case

var particle_effect

Signals

snake_case

signal door_opened

Constants

CONSTANT_CASE

const MAX_SPEED = 200

Enum names

PascalCase

enum Element

Enum members

CONSTANT_CASE

{EARTH, WATER, AIR, FIRE}

File names

Use snake_case for file names. For named classes, convert the PascalCase class name to snake_case:

  1. # This file should be saved as `weapon.gd`.
  2. class_name Weapon
  3. extends Node
  1. # This file should be saved as `yaml_parser.gd`.
  2. class_name YAMLParser
  3. extends Object

This is consistent with how C++ files are named in Godot’s source code. This also avoids case sensitivity issues that can crop up when exporting a project from Windows to other platforms.

Classes and nodes

Use PascalCase for class and node names:

  1. extends CharacterBody3D

Also use PascalCase when loading a class into a constant or a variable:

  1. const Weapon = preload("res://weapon.gd")

Functions and variables

Use snake_case to name functions and variables:

  1. var particle_effect
  2. func load_level():

Prepend a single underscore (_) to virtual methods functions the user must override, private functions, and private variables:

  1. var _counter = 0
  2. func _recalculate_path():

Signals

Use the past tense to name signals:

  1. signal door_opened
  2. signal score_changed

Constants and enums

Write constants with CONSTANT_CASE, that is to say in all caps with an underscore (_) to separate words:

  1. const MAX_SPEED = 200

Use PascalCase for enum names and CONSTANT_CASE for their members, as they are constants:

  1. enum Element {
  2. EARTH,
  3. WATER,
  4. AIR,
  5. FIRE,
  6. }

Write enums with each item on its own line. This allows adding documentation comments above each item more easily, and also makes for cleaner diffs in version control when items are added or removed.

Good:

  1. enum Element {
  2. EARTH,
  3. WATER,
  4. AIR,
  5. FIRE,
  6. }

Bad:

  1. enum Element { EARTH, WATER, AIR, FIRE }

Code order

This section focuses on code order. For formatting, see Formatting. For naming conventions, see Naming conventions.

We suggest to organize GDScript code this way:

  1. 01. @tool
  2. 02. class_name
  3. 03. extends
  4. 04. ## docstring
  5. 05. signals
  6. 06. enums
  7. 07. constants
  8. 08. @export variables
  9. 09. public variables
  10. 10. private variables
  11. 11. @onready variables
  12. 12. optional built-in virtual _init method
  13. 13. optional built-in virtual _enter_tree() method
  14. 14. built-in virtual _ready method
  15. 15. remaining built-in virtual methods
  16. 16. public methods
  17. 17. private methods
  18. 18. subclasses

We optimized the order to make it easy to read the code from top to bottom, to help developers reading the code for the first time understand how it works, and to avoid errors linked to the order of variable declarations.

This code order follows four rules of thumb:

  1. Properties and signals come first, followed by methods.

  2. Public comes before private.

  3. Virtual callbacks come before the class’s interface.

  4. The object’s construction and initialization functions, _init and _ready, come before functions that modify the object at runtime.

Class declaration

If the code is meant to run in the editor, place the @tool annotation on the first line of the script.

Follow with the class_name if necessary. You can turn a GDScript file into a global type in your project using this feature. For more information, see GDScript reference.

Then, add the extends keyword if the class extends a built-in type.

Following that, you should have the class’s optional documentation comments. You can use that to explain the role of your class to your teammates, how it works, and how other developers should use it, for example.

  1. class_name MyNode
  2. extends Node
  3. ## A brief description of the class's role and functionality.
  4. ##
  5. ## The description of the script, what it can do,
  6. ## and any further detail.

Signals and properties

Write signal declarations, followed by properties, that is to say, member variables, after the docstring.

Enums should come after signals, as you can use them as export hints for other properties.

Then, write constants, exported variables, public, private, and onready variables, in that order.

  1. signal player_spawned(position)
  2. enum Jobs {
  3. KNIGHT,
  4. WIZARD,
  5. ROGUE,
  6. HEALER,
  7. SHAMAN,
  8. }
  9. const MAX_LIVES = 3
  10. @export var job: Jobs = Jobs.KNIGHT
  11. @export var max_health = 50
  12. @export var attack = 5
  13. var health = max_health:
  14. set(new_health):
  15. health = new_health
  16. var _speed = 300.0
  17. @onready var sword = get_node("Sword")
  18. @onready var gun = get_node("Gun")

Note

GDScript evaluates @onready variables right before the _ready callback. You can use that to cache node dependencies, that is to say, to get child nodes in the scene that your class relies on. This is what the example above shows.

Member variables

Don’t declare member variables if they are only used locally in a method, as it makes the code more difficult to follow. Instead, declare them as local variables in the method’s body.

Local variables

Declare local variables as close as possible to their first use. This makes it easier to follow the code, without having to scroll too much to find where the variable was declared.

Methods and static functions

After the class’s properties come the methods.

Start with the _init() callback method, that the engine will call upon creating the object in memory. Follow with the _ready() callback, that Godot calls when it adds a node to the scene tree.

These functions should come first because they show how the object is initialized.

Other built-in virtual callbacks, like _unhandled_input() and _physics_process, should come next. These control the object’s main loop and interactions with the game engine.

The rest of the class’s interface, public and private methods, come after that, in that order.

  1. func _init():
  2. add_to_group("state_machine")
  3. func _ready():
  4. state_changed.connect(_on_state_changed)
  5. _state.enter()
  6. func _unhandled_input(event):
  7. _state.unhandled_input(event)
  8. func transition_to(target_state_path, msg={}):
  9. if not has_node(target_state_path):
  10. return
  11. var target_state = get_node(target_state_path)
  12. assert(target_state.is_composite == false)
  13. _state.exit()
  14. self._state = target_state
  15. _state.enter(msg)
  16. Events.player_state_changed.emit(_state.name)
  17. func _on_state_changed(previous, new):
  18. print("state changed")
  19. state_changed.emit()

Static typing

GDScript supports optional static typing.

Declared types

To declare a variable’s type, use <variable>: <type>:

  1. var health: int = 0

To declare the return type of a function, use -> <type>:

  1. func heal(amount: int) -> void:

Inferred types

In most cases you can let the compiler infer the type, using :=. Prefer := when the type is written on the same line as the assignment, otherwise prefer writing the type explicitly.

Good:

  1. var health: int = 0 # The type can be int or float, and thus should be stated explicitly.
  2. var direction := Vector3(1, 2, 3) # The type is clearly inferred as Vector3.

Include the type hint when the type is ambiguous, and omit the type hint when it’s redundant.

Bad:

  1. var health := 0 # Typed as int, but it could be that float was intended.
  2. var direction: Vector3 = Vector3(1, 2, 3) # The type hint has redundant information.
  3. # What type is this? It's not immediately clear to the reader, so it's bad.
  4. var value := complex_function()

In some cases, the type must be stated explicitly, otherwise the behavior will not be as expected because the compiler will only be able to use the function’s return type. For example, get_node() cannot infer a type unless the scene or file of the node is loaded in memory. In this case, you should set the type explicitly.

Good:

  1. @onready var health_bar: ProgressBar = get_node("UI/LifeBar")

Alternatively, you can use the as keyword to cast the return type, and that type will be used to infer the type of the var.

  1. @onready var health_bar := get_node("UI/LifeBar") as ProgressBar
  2. # health_bar will be typed as ProgressBar

This option is also considered more type-safe than the first.

Bad:

  1. # The compiler can't infer the exact type and will use Node
  2. # instead of ProgressBar.
  3. @onready var health_bar := get_node("UI/LifeBar")

User-contributed notes

Please read the User-contributed notes policy before submitting a comment.

Previous Next