Godot Autoloads Explained: When to Use Singletons and When Not To
Learn how Godot Autoloads work, when global singletons make sense, common architecture mistakes, and cleaner alternatives using signals and Resources.
Sooner or later, most Godot projects need something that does not neatly belong to a single level or scene.
Maybe you need to remember the player's score while changing levels. Perhaps music should continue playing when the current scene disappears. You might have save data, dialogue state, settings, achievements, or a system responsible for changing scenes.
Godot's **Autoload** feature seems like the obvious solution.
Add a script as an Autoload, give it a name such as `GameManager`, and suddenly any script in the project can call:
```gdscript
GameManager.start_game()
GameManager.score += 100
GameManager.save_game()
```
It is convenient.
Sometimes that convenience is exactly what you need.
The problem begins when every system becomes globally accessible simply because it can.
A project that starts with one useful Autoload can gradually acquire `GameManager`, `PlayerManager`, `EnemyManager`, `AudioManager`, `UIManager`, `InventoryManager`, `LevelManager`, and several other global objects that all depend on one another.
Understanding Autoloads therefore requires answering two different questions:
**How do Autoloads work?**
And, more importantly:
**When should you actually use one?**
## What Is an Autoload in Godot?
An Autoload is a script or scene that Godot automatically places near the root of the running scene tree.
When you Autoload a script, Godot creates a `Node`, attaches your script to it, and adds that node to the root before ordinary project scenes are loaded. Autoload entries can also be scenes rather than individual scripts.
You configure them through:
**Project → Project Settings → Globals → Autoload**
After assigning an Autoload a name, enabled Autoloads can be referenced directly from GDScript.
For example, consider a script called `game_state.gd`:
```gdscript
extends Node
var score: int = 0
var current_level: int = 1
func reset_run() -> void:
score = 0
current_level = 1
```
Add it as an Autoload called `GameState`, and another script can access it with:
```gdscript
GameState.score += 100
print(GameState.current_level)
```
The important difference from an ordinary scene node is its lifetime.
Changing your current scene normally removes that scene and its children. An Autoload remains in the scene tree instead of being freed during a normal `SceneTree.change_scene_to_file()` scene change.
That makes Autoloads useful for information and systems whose lifetime should extend beyond any individual gameplay scene.
## Autoload Does Not Actually Mean Singleton
The words **Autoload** and **singleton** are frequently used interchangeably in Godot tutorials, but they are not exactly the same concept.
A traditional singleton is a programming pattern intended to ensure that only one instance of a particular object exists.
Godot's Autoload system does not enforce that restriction.
The documentation explicitly notes that an Autoload is not necessarily a true singleton. You can still instantiate another copy of the same underlying scene or script yourself. Autoload simply guarantees that Godot automatically creates the configured object and places it at the root of the scene tree.
In practice, developers frequently **use an Autoload like a singleton** because the automatically created instance is globally accessible.
That distinction may sound theoretical, but it helps clarify what Autoload is actually solving.
Autoload gives an object:
* A long lifetime.
* A predictable place in the scene tree.
* Global accessibility.
* The normal capabilities of a `Node`.
It is not automatically the correct architecture for every object that needs to be accessed more than once.
## A Good Autoload Owns Its Responsibility
Godot's own scene-organization guidance provides a useful way of thinking about global systems.
An Autoload is a particularly good candidate when a system:
1. Manages its own information.
2. Needs to be globally accessible.
3. Can operate largely in isolation from individual scenes.
Godot specifically presents broad systems such as dialogue or quest systems as examples where an Autoload can make sense.
Consider persistent game state:
```gdscript
extends Node
signal score_changed(new_score: int)
var score: int = 0
var coins: int = 0
func add_score(amount: int) -> void:
score += amount
score_changed.emit(score)
func add_coin() -> void:
coins += 1
func reset() -> void:
score = 0
coins = 0
```
This is a reasonable Autoload.
Its job is narrow: maintain information that belongs to the current game session.
It does not need to know where the player node is.
It does not search the current level for enemies.
It does not manipulate the HUD directly.
Other systems can interact with its public API, but the Autoload remains responsible for its own state.
That is very different from creating one giant global object responsible for everything happening in the game.
## Persistent State Is One of the Clearest Uses
Imagine a game containing these scenes:
```text
MainMenu
Level01
Level02
Level03
GameOver
```
The player's score needs to survive the transition from `Level01` to `Level02`.
Storing that score inside `Level01` would be a problem because the entire level disappears when it is replaced.
An Autoload solves the lifetime mismatch.
```gdscript
# run_state.gd
extends Node
var score: int = 0
var lives: int = 3
var collected_keys: Array[String] = []
```
The levels come and go.
`RunState` stays alive.
The same principle can apply to information such as:
* Current run progress.
* Player profile information.
* Persistent settings.
* Achievement progress.
* Scene-transition state.
* Save-system coordination.
The common thread is not merely that the information is convenient to access globally.
It is that its **lifetime belongs to the overall game rather than one temporary scene**.
## Autoload Scenes Can Be More Than Data Containers
Because Autoloads are nodes, they can also use normal node functionality.
That separates them from a simple collection of static functions.
For example, a persistent music system might need an `AudioStreamPlayer` that remains alive while gameplay scenes change.
Instead of Autoloading only a script, you could create:
```text
MusicManager
└── AudioStreamPlayer
```
Save it as a scene and add that scene to Autoload.
The manager now owns both its logic and the nodes required to perform its job.
The same approach can be useful for a transition system containing an animation layer, a persistent debugging interface, or another genuinely global system that benefits from having children in the scene tree.
This is one reason Autoloads remain useful even though GDScript supports static functions and static variables.
## The Dangerous Question: "Could This Be an Autoload?"
Technically, many things could be.
That is usually the wrong question.
Ask instead:
**Does this need to be an Autoload?**
Suppose you create:
```gdscript
extends Node
var player
var hud
var current_enemy
var inventory
var current_level
func damage_player(amount: int) -> void:
player.health -= amount
hud.update_health(player.health)
func enemy_died(enemy) -> void:
current_level.remove_enemy(enemy)
hud.update_enemy_count()
func open_inventory() -> void:
inventory.open()
```
This type of `GameManager` feels convenient initially because everything has an easy route to everything else.
But the manager now needs knowledge of:
* The player.
* The HUD.
* The inventory.
* The current level.
* Enemies.
* Their internal APIs.
Instead of reducing dependencies, the global manager has become a central collection of dependencies.
Changing the HUD may require changing `GameManager`.
Testing the player becomes harder because its behavior assumes global systems exist.
Reusing a scene in another project becomes harder.
Loading a gameplay scene directly from the editor may fail because something expected by the global manager has not been registered.
The problem is not that Autoload itself is bad.
The problem is **unnecessary global state and global knowledge**.
Godot's best-practices documentation specifically highlights the scene tree and signals as tools that can reduce the need for global manager objects.
## Prefer Signals When Something Only Needs to Announce an Event
Imagine the player takes damage.
The player could call:
```gdscript
UIManager.update_health(health)
AudioManager.play_hurt_sound()
GameManager.check_if_player_died()
```
Now the player knows about three unrelated global systems.
A different design is for the player to announce what happened:
```gdscript
signal health_changed(new_health: int)
signal died
func take_damage(amount: int) -> void:
health -= amount
health_changed.emit(health)
if health <= 0:
died.emit()
```
The interface can listen for `health_changed`.
Another node can respond to `died`.
The player no longer needs to know exactly who responds.
Signals are specifically designed to allow objects to react to one another without requiring direct references, reducing coupling between them.
Not every signal needs a global event bus, either.
If two nodes already belong to the same scene or have a clear parent responsible for connecting them, ordinary local signals often keep the dependency easier to understand.
A global event bus can be useful for genuinely project-wide events, but turning every event into a global signal simply replaces one form of global dependency with another.
## Prefer Resources When You Need Shared Data
Sometimes developers create an Autoload simply because several objects need the same configuration.
For example:
```gdscript
WeaponManager.pistol_damage
WeaponManager.pistol_fire_rate
WeaponManager.pistol_reload_time
```
That does not necessarily require a persistent node.
A custom `Resource` may represent that data more naturally:
```gdscript
class_name WeaponData
extends Resource
@export var damage: float
@export var fire_rate: float
@export var reload_time: float
```
Different weapons can reference different `WeaponData` resources.
The data remains reusable and editor-friendly without introducing a globally accessible manager responsible for every weapon.
Autoloads are strongest when you need a persistent **system**.
Resources are often stronger when you need reusable **data**.
## Prefer Static Functions for Stateless Utilities
Imagine you create an Autoload called `MathUtils` containing:
```gdscript
func percent(value: float, maximum: float) -> float:
return value / maximum
```
There is no persistent state.
There are no child nodes.
It does not need `_process()` or `_ready()`.
It does not need to survive a scene change because there is effectively nothing to survive.
GDScript supports static functions, and static variables are also available for class-level data. Godot's own Autoload guidance specifically presents static members as an alternative when a globally loaded node is unnecessary.
A utility can instead look like:
```gdscript
class_name GameMath
static func percent(value: float, maximum: float) -> float:
if maximum == 0.0:
return 0.0
return value / maximum
```
Then:
```gdscript
var health_percent := GameMath.percent(health, max_health)
```
No Autoload required.
## Prefer Groups When You Need to Find a Category of Nodes
Another common reason developers reach for a manager is to keep arrays of every enemy, interactable object, damageable object, or checkpoint.
Godot already has a system designed for categorizing nodes: **groups**.
A node can join a group:
```gdscript
func _ready() -> void:
add_to_group("enemies")
```
Another object can find the members:
```gdscript
var enemies := get_tree().get_nodes_in_group("enemies")
```
Or call a method across the entire group:
```gdscript
get_tree().call_group("enemies", "enter_alert_mode")
```
Godot's documentation describes groups as a way to organize large scenes and decouple code.
That can eliminate the need for an `EnemyManager` whose main responsibility is simply maintaining references to enemies that already exist in the scene tree.
## Sometimes a Regular Node Is Enough
Not every manager is bad.
Sometimes it simply belongs inside the scene it manages.
Imagine a level with:
```text
Level
├── Player
├── Enemies
├── Objectives
├── LevelController
└── UI
```
`LevelController` might coordinate objectives, determine when the level ends, and connect signals from the player and enemies.
That does not make it an Autoload candidate.
Its responsibility begins when the level exists and ends when the level disappears.
Putting it inside the level makes that lifetime explicit.
This is one of the most useful rules for Godot architecture:
**Put a system as low in the scene tree as its required lifetime allows.**
If something only matters to one enemy, keep it with that enemy.
If something belongs to one level, keep it with the level.
If something belongs to an entire gameplay mode, put it at that mode's root.
Move it to an Autoload only when its responsibility genuinely extends across those boundaries.
## AudioManager Is More Nuanced Than It Looks
Audio is often given as an obvious Autoload example, but the correct design depends on what kind of audio you are managing.
Persistent background music may fit an Autoload well because you might intentionally want the same player to continue operating while scenes change.
Global volume settings may also make sense at project scope.
But that does not mean every sound effect needs to pass through:
```gdscript
AudioManager.play_sound("enemy_step")
```
A footstep belongs naturally to the character producing the footstep.
A machine's looping sound may belong to the machine.
A local environmental sound can live inside that environment.
A good architecture can therefore mix approaches:
```text
MusicManager (Autoload)
Level
├── Player
│ └── FootstepAudio
├── Enemy
│ └── AudioStreamPlayer
└── Environment
└── AmbientAudio
```
The question is again about ownership.
Who logically owns this behavior, and how long does it need to exist?
## Be Careful When Autoloads Depend on Other Autoloads
Godot allows multiple Autoloads, and their order can be configured.
However, a growing chain such as:
```text
GameManager
↓
SaveManager
↓
InventoryManager
↓
PlayerManager
↓
UIManager
```
should make you examine the architecture.
Each global dependency makes initialization, debugging, and refactoring harder to reason about.
A healthy Autoload often behaves more like an independent service:
```text
Settings
SaveSystem
Music
RunState
```
Each owns a clear responsibility rather than reaching through the others to control the whole project.
The number of Autoloads is therefore less important than the relationships between them.
Five focused global systems can be easier to maintain than one enormous `GameManager`.
Likewise, one global manager that knows about every scene can cause more trouble than several small independent Autoloads.
## A Practical Autoload Decision Test
Before adding something to **Project Settings → Globals → Autoload**, ask:
| Question | If Yes | If No |
| --- | --- | --- |
| Must it survive normal scene changes? | Autoload may fit | Prefer scene ownership |
| Is it needed across unrelated parts of the game? | Global access may be justified | Keep access local |
| Does it own its own state and responsibility? | Good sign | Watch for excessive coupling |
| Does it require Node lifecycle or child nodes? | Autoload may help | Consider static code or Resources |
| Is it mostly reusable configuration data? | Consider a Resource | Continue evaluating |
| Does it only announce that something happened? | Signals may be enough | Continue evaluating |
| Does it only contain helper functions? | Prefer static functions | Autoload may still have a reason |
| Does it constantly store references to temporary scene objects? | Reconsider the design | Better sign |
No single answer automatically decides the architecture.
The table simply forces the important question:
**What problem is global access actually solving?**
## A Sensible Small-Project Structure
A small Godot game might eventually contain something like:
```text
Autoloads
├── RunState
├── SaveSystem
└── MusicManager
Main
├── CurrentLevel
│ ├── Player
│ ├── Enemies
│ ├── LevelController
│ └── LevelUI
└── TransitionLayer
```
`RunState` remembers information that must survive levels.
`SaveSystem` owns saving and loading.
`MusicManager` owns persistent music.
The level still manages its own enemies.
The player still owns player behavior.
The UI still belongs to the context where it is displayed.
That separation preserves one of Godot's biggest strengths: scenes can remain relatively self-contained instead of becoming thin shells controlled by global scripts.
## Autoload Is a Tool, Not a Default Architecture
Autoloads are useful precisely because they break normal scene boundaries.
That is also why they should be intentional.
Use them for systems whose lifetime truly belongs to the entire application or game session. Persistent state, saves, some audio responsibilities, scene-transition services, dialogue systems, and similar broad systems can all be reasonable candidates.
But global accessibility should not become the easiest way for unrelated objects to communicate.
Before creating another manager, check whether the responsibility could live in a regular scene node.
Before storing shared configuration globally, consider a Resource.
Before calling another system directly, consider a signal.
Before creating a utility Autoload, consider static functions.
Before keeping a registry of every object, consider groups.
The goal is not to eliminate Autoloads.
The goal is to make each one earn its place at the root of your game.