Godot 4 Migration Guide
Overview
A critical guide for developers transitioning from Godot 3.x to Godot 4. Covers the major syntax changes in GDScript 2.0, the new Tween system, export annotation updates, signal connections, typed arrays, and coroutine await replacements.
When to Use
- Use when porting a Godot 3.x project to Godot 4.
- Use when encountering GDScript syntax errors after upgrading the engine version.
- Use when replacing deprecated
Tweennodes withcreate_tween(). - Use when updating
exportvariables to@exportannotations. - Use when converting
yield/setget/string-based signal connections to Godot 4 equivalents.
Prerequisites
- Godot 4.x installed and opening the project without crashing (project.godot upgraded).
- A backup or version-controlled copy of the original Godot 3.x project.
- Familiarity with GDScript syntax.
Procedure
1. Update Annotations (@ prefix)
Godot 4 uses @ for keywords that modify behavior. Replace all old annotation syntax:
| Godot 3.x | Godot 4 |
|---|---|
export var x |
@export var x |
onready var y |
@onready var y |
tool (top of file) |
@tool |
export_range(...) |
@export_range(...) |
export_file(...) |
@export_file(...) |
2. Convert Setters and Getters to Inline Syntax
Properties now define setters/getters inline instead of using setget.
Godot 3.x:
var health setget set_health, get_health
func set_health(value):
health = value
Godot 4:
var health: int:
set(value):
health = value
health_changed.emit(health)
get:
return health
3. Replace Tween Node with create_tween()
The Tween node is deprecated. Use create_tween() which returns a Tween object.
Godot 3.x:
$Tween.interpolate_property($Sprite, "position", Vector2.ZERO, Vector2(100, 100), 1.0, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
$Tween.start()
Godot 4:
var tween = create_tween()
tween.tween_property($Sprite, "position", Vector2(100, 100), 1.0)
tween.parallel().tween_property($Sprite, "modulate:a", 0.0, 1.0)
Key differences:
create_tween()is called on anyNode; noTweennode needed in the scene tree.tween_property(target, property, final_value, duration)— note the simplified signature.- Use
.parallel()to run tweens concurrently instead of sequentially. - Use
.set_trans()and.set_ease()for transition/ease types.
4. Update Signal Connections to Callable Syntax
String-based connections are discouraged. Use callables directly.
Godot 3.x:
connect("pressed", self, "_on_pressed")
emit_signal("health_changed", health)
Godot 4:
pressed.connect(_on_pressed)
health_changed.emit(health)
For dynamic connections by name:
button.connect("pressed", Callable(self, "_on_pressed"))
5. Replace yield with await
yield is replaced by await for coroutines.
Godot 3.x:
yield(get_tree().create_timer(1.0), "timeout")
yield($AnimationPlayer, "animation_finished")
Godot 4:
await get_tree().create_timer(1.0).timeout
await $AnimationPlayer.animation_finished
6. Add Typed Arrays and Variable Types
GDScript 2.0 supports typed arrays and variable typing for performance and type safety.
# Godot 3
var enemies = []
# Godot 4
var enemies: Array[Enemy] = []
func _ready():
for child in get_children():
if child is Enemy:
enemies.append(child)
Type all variables where possible (var x: int, var name: String) for performance gains.
7. Replace Implicit Parent Calls with super()
Godot 3.x:
func _ready():
._ready()
Godot 4:
func _ready():
super._ready()
# or for regular methods:
super()
8. Update @export Variants for Inspector UI
Use specialized export annotations for better inspector controls:
@export_range(0, 100, 1) var health: int = 100
@export_file("*.json") var data_file: String
@export_enum("Warrior", "Mage", "Rogue") var class_type: int = 0
@export_group("Combat")
@export var damage: int = 10
Examples
Full Migration Example: A Simple Button Handler
Godot 3.x:
extends Control
export var button_text: String = "Click Me"
onready var label = $Label
func _ready():
connect("pressed", self, "_on_pressed")
func _on_pressed():
yield(get_tree().create_timer(0.5), "timeout")
label.text = "Done!"
Godot 4:
extends Control
@export var button_text: String = "Click Me"
@onready var label: Label = $Label
func _ready():
pressed.connect(_on_pressed)
func _on_pressed():
await get_tree().create_timer(0.5).timeout
label.text = "Done!"
Pitfalls
"Identifier 'Tween' is not a valid type." —
Tweenis now an object returned bycreate_tween(). You rarely type it explicitly; just usevar tween = create_tween(). If you must type it, usevar tween: Tween = create_tween().Signal connection errors after migration. — String-based
connect("signal", target, "method")still works but is discouraged. Ensure the callable signature matches; Godot 4 is stricter about argument counts.setgetsilently broken. — Thesetgetkeyword is removed entirely in Godot 4. The editor will show a parse error. Must convert to inlineset/getblocks.yieldcauses parse errors. —yieldis fully removed in GDScript 2.0. Every instance must be replaced withawait..method_name()parent calls fail. — The dot-prefix syntax for calling parent methods is removed. Usesuper.method_name()orsuper()for the same-named method.@onreadytiming. —@onreadyvariables are initialized just before_ready()is called, same as Godot 3'sonready. But if you access them in_init()or_enter_tree(), they will be null.Typed array casting. —
Array[Node]requires all elements to beNodeor subclasses. Mixing types causes runtime errors. UseArray[Variant]if you need mixed types.emit_signalstill works but is deprecated style. — Prefersignal_name.emit(args)overemit_signal("signal_name", args)for clarity and refactoring safety.Resource
.tres/.tscnfiles. — Scene and resource files may need re-saving in Godot 4. Open them in the editor and save to update the format. Some property names changed (e.g.,margin→offsetinControl).Do not treat this guide as a substitute for environment-specific validation, testing, or expert review. Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
Verification
After migration, verify the project loads and runs correctly:
Open the project in Godot 4 — check the editor console for parse errors:
No GDScript parse errors in the Output panel.Run the project (F5 or CLI):
& "C:\Program Files\Godot\Godot4.exe" --path . --check-onlyExpected: exits with code 0, no script errors reported.
Verify no deprecated patterns remain — search the codebase:
Get-ChildItem -Recurse -Filter *.gd | Select-String -Pattern "setget|yield\(|connect\(`"|emit_signal\(" | ForEach-Object { "$($_.Path):$($_.LineNumber): $($_.Line)" }Expected: no matches (or only intentional dynamic connections).
Verify Tween usage — confirm no
Tweennode references in scenes:Get-ChildItem -Recurse -Filter *.tscn | Select-String -Pattern "type=`"Tween`""Expected: no matches.
Run the main scene and test interactions — confirm signals fire, tweens animate, and no runtime errors appear in the console.
Related Skills
godot-gdscript-best-practices— GDScript 2.0 coding standards and patterns.godot-scene-architecture— Node and scene tree organization for Godot 4.