Can you write this from memory?
Declare an exported variable called speed with a default value of 200.0 so designers can tune it.
Godot 4 @export replaces Godot 3's export(). @export_range adds a slider. Typed variables make the Inspector smart. These annotations are how you iterate fast on game feel.
Learn the annotation syntax and tweaking speed, health, and damage is one click away.
Why isn't my export showing? — Check: (1) script saved, (2) has a constant default OR type hint, (3) not a local variable inside a function. Make sure your script foundations are solid first.
Why does _init() read the wrong value? — Inspector values are applied after _init(). Read exported values in _ready() or use a setter.
How do I make a dropdown? — @export var difficulty: Difficulty with an enum, or @export_enum("Sword", "Bow").
How do I make a slider? — @export_range(0, 100, 1) var health: int.
How do I group properties? — @export_group("Movement") before the vars you want grouped.
How do I export a node reference? — @export var target: Node2D for same-scene nodes.
| Annotation | Inspector UI | Use For | Example |
|---|---|---|---|
@export | Type-appropriate widget | Basic exposure | @export var speed: float = 200.0 |
@export_range | Slider with bounds | Constrained numbers | @export_range(0, 100, 1) var hp: int |
@export_enum | Dropdown (strings) | Inline string choices | @export_enum("Sword", "Bow") var weapon: int |
@export_flags | Multi-select checkboxes | Bit flags | @export_flags("Fire", "Ice") var elements: int |
@export_file | File picker | Scene/resource paths | @export_file("*.tscn") var level: String |
@export_multiline | Multiline text box | Long strings | @export_multiline var description: String |
@export_group | Collapsible section | Organizing properties | @export_group("Combat") |
@export_subgroup | Nested section | Sub-organization | @export_subgroup("Advanced") |
@export_category | Top-level divider | Major sections (use sparingly) | @export_category("AI") |
@export_storage | None (hidden) | Serialized but not editable | @export_storage var internal_id: int |
@export_exp_easing | Easing curve editor | Tweening/interpolation feel | @export_exp_easing var curve: float |
@export_flags_2d_physics | Layer checkboxes | Collision layers from Project Settings | @export_flags_2d_physics var mask: int |
@export_color_no_alpha | Color picker (no alpha) | Solid colors only | @export_color_no_alpha var tint: Color |
@export_placeholder | String with hint text | Placeholder guidance | @export_placeholder("Enter name") var s: String |
The Inspector is where game feel lives. When you're tuning jump height, you don't want to:
- Edit code
- Save
- Run game
- Test
- Repeat
With @export, you change a slider and see the result immediately. Future-you (and any designers on your team) can tune the game without touching code.
Basic Exports
@export var speed: float = 200.0 # Number input
@export var player_name: String = "" # Text field
@export var is_active: bool = true # Checkbox
@export var tint: Color = Color.WHITE # Color picker
Constrained Ranges
# Slider from 0 to 100
@export_range(0, 100) var health: int = 100
# Float slider with fine control
@export_range(0.0, 1.0, 0.01) var volume: float = 0.8
# Angles in degrees (stored as radians internally)
@export_range(0, 360, 1, "radians_as_degrees") var rotation_degrees: float = 0.0
Easing Curves (Game Feel)
# Visual curve editor for tweening/interpolation
@export_exp_easing var jump_curve: float = 0.5
@export_exp_easing("attenuation") var fade_out: float = 1.0
@export_exp_easing("positive_only") var bounce: float = 2.0
Values < 1.0 ease out (fast start, slow end). Values > 1.0 ease in (slow start, fast end). Use with ease() function or Tween.
Enums and Dropdowns
enum Difficulty { EASY, NORMAL, HARD }
@export var difficulty: Difficulty = Difficulty.NORMAL
# Or inline without a named enum:
@export_enum("Sword", "Bow", "Staff") var weapon_type: int = 0
Bit Flags (Multi-Select)
# Creates checkboxes for each option
@export_flags("Fire", "Water", "Earth", "Wind") var spell_elements: int = 0
# Values are powers of 2: Fire=1, Water=2, Earth=4, Wind=8
# Selecting Fire+Wind gives spell_elements = 9 (1|8)
# Check if a flag is set:
func has_element(element: int) -> bool:
return (spell_elements & element) != 0
Bit flags are the same concept as collision layer masks—both use bit operations to combine multiple options into a single integer.
Pro tip: For physics layers, use the built-in helpers that read your Project Settings layer names:
# Uses layer names from Project Settings → Layer Names
@export_flags_2d_physics var collision_mask: int
@export_flags_3d_physics var target_layers: int
@export_flags_2d_render var visible_layers: int
Exported Arrays
# Array of scenes (drag and drop in Inspector)
@export var enemy_scenes: Array[PackedScene] = []
# Typed array with range constraint
@export_range(0, 360) var laser_angles: Array[float] = []
# Array of custom Resources
@export var upgrades: Array[UpgradeData] = []
See arrays & loops for iteration patterns.
Resources and Scenes
@export var projectile_scene: PackedScene # Scene picker
@export var icon: Texture2D # Image picker
@export var stats: CharacterStats # Custom Resource
@export_file("*.tscn") var next_level: String # File picker with filter
See resources for custom Resource patterns and the shared-reference pitfall.
Node Reference vs NodePath
# Direct node reference (same scene only, drag from SceneTree)
@export var target: Node2D
# NodePath (works across scenes, requires get_node() to resolve)
@export var path_to_target: NodePath
@onready var resolved_target = get_node(path_to_target) # Common pattern
# NodePath with type filter in the picker
@export_node_path("Sprite2D", "AnimatedSprite2D") var sprite_path: NodePath
When to use which:
- Export Node/Node2D when it's in the same scene and you want direct access
- Export NodePath when you need late binding or a path that auto-updates when nodes move in the editor
Organizing in the Inspector
@export_group("Movement")
@export var speed: float = 200.0
@export var acceleration: float = 1000.0
@export_group("Combat")
@export var max_hp: int = 100
@export var attack_damage: int = 10
@export_subgroup("Advanced")
@export var crit_chance: float = 0.1
Groups create collapsible sections. Subgroups nest inside groups.
Caution about @export_category: Categories break the class-based property organization that developers expect. Prefer @export_group and @export_subgroup for clarity.
Tooltips with Documentation Comments
Add Inspector tooltips using ## comments:
## How fast the player moves in pixels per second.
@export var speed: float = 200.0
## Maximum health points. Clamped to [1, 999].
@export_range(1, 999) var max_hp: int = 100
The ## line immediately before an export becomes its hover tooltip in the Inspector.
Hidden Serialization with @export_storage
# Not stored, not shown (normal var)
var temp_value: int
# Stored in scene/resource, NOT shown in Inspector
@export_storage var internal_id: int
# Stored AND shown (normal @export)
@export var visible_stat: int
Use @export_storage for values you want saved but not editable by designers.
Designer-Safe Clamping via Setter
Even with @export_range, someone can type invalid values directly. Use a setter for runtime safety:
var hp: int = 100
@export_range(1, 100) var max_hp: int = 100:
set(value):
max_hp = clampi(value, 1, 100)
# Optionally update dependent values
if hp > max_hp:
hp = max_hp
Per-Instance Resources
Resources are shared by default. For per-instance mutable state:
@export var base_stats: EnemyStats
var stats: EnemyStats # Runtime copy
func _ready() -> void:
# Each instance gets its own copy
stats = base_stats.duplicate() as EnemyStats
The resource_local_to_scene flag automates this—each instance gets its own copy at load time.
When to Use GDScript Exports
- Exposing speed, health, damage, and other tuning values to the Inspector for rapid iteration
- Adding slider constraints with @export_range so designers cannot set invalid values
- Defining enum or resource exports so scenes can be configured without opening the script
- Using @export_flags for multi-option selections like spell elements or status effects
Check Your Understanding: GDScript Exports
Check Your Understanding: What is the difference between @export and @export_range in Godot 4, and how do typed exports improve the Inspector?
@export exposes a variable to the Inspector with a default editor for its type. @export_range(min, max, step) adds a slider with enforced bounds, preventing invalid values. Typed exports like @export var speed: float give the Inspector a float spinner, while @export var texture: Texture2D shows a resource picker. This makes scenes self-documenting, so designers can tweak values without reading code.
What You'll Practice: GDScript Exports
Common GDScript Exports Pitfalls
- Expecting @export var changes in code to update existing scenes—Inspector values override script defaults, so existing instances keep their saved values
- Reading exported values in _init() instead of _ready()—Inspector/scene values are applied after _init(), so you get the script default instead of the overridden value
- Forgetting @export_group only affects properties declared after it—properties above it remain ungrouped
- Using @export_category for layout when @export_group is clearer—categories break the class-based property organization developers expect
- Mutating a shared exported Resource at runtime—all instances see the change. Use duplicate() for per-instance state (see resources page)
GDScript Exports FAQ
How do I expose a variable to the Inspector in Godot 4?
Add the @export annotation before the variable: @export var speed: float = 200.0. The variable appears in the Inspector when the node is selected. The editor widget matches the type: float shows a number input, Color shows a color picker, NodePath shows a node selector.
What export annotations are available in Godot 4 GDScript?
@export is the basic annotation. @export_range(0, 100, 1) adds a constrained slider. @export_enum("Sword", "Bow") creates a dropdown. @export_flags("Fire", "Ice") creates multi-select checkboxes. @export_file("*.tscn") shows a file picker. @export_group and @export_subgroup organize properties. @export_storage saves values without showing them in the Inspector.
Do Inspector values override code defaults in Godot 4?
Yes. When you set a value in the Inspector, it is saved in the scene file (.tscn) and overrides the default in your script. This is intentional for per-instance tuning. To reset to the code default, click the revert arrow next to the property in the Inspector.
How do I export an enum in Godot 4?
Define the enum, then export a variable of that type: enum State { IDLE, WALKING, JUMPING } followed by @export var state: State = State.IDLE. The Inspector shows a dropdown with all enum values.
How do I export a Node reference vs NodePath?
@export var target: Node2D exports a node reference (drag from scene tree). @export var path: NodePath exports a path string. Node references are easier to use but only work within the same scene. NodePath works across scenes but requires get_node() to resolve. Use @export_node_path("Sprite2D") to filter the node picker by type.
Why isn't my exported value showing in the Inspector?
Check that: (1) the script is saved, (2) the variable has a constant default or a type hint (@export var speed := 200.0 or @export var speed: float), (3) you're selecting the correct node. Also, @export only works on class members, not local variables inside functions.
Why does my exported value read as the default in _init()?
Inspector values are applied after _init() completes. Reading exported variables in _init() returns the script default, not the value set in the Inspector. Solution: read exported values in _ready() instead, or use a setter that runs when the value is applied.
Can I export a Resource and edit it per-instance?
Yes, but be careful: Resources are shared by default. If you export a Resource and edit it in the Inspector, all instances using that Resource see the change. For per-instance data, duplicate() the Resource in _ready or use resource_local_to_scene. See the resources page for the full pattern.
How do I group exported variables in the Inspector?
Use @export_group("Group Name") before the variables you want grouped. All @export vars after it belong to that group until the next @export_group or end of class. Use @export_subgroup for nested organization. Avoid @export_category unless you need top-level separation—it breaks expected class-based organization.
How do I add tooltips to exported properties?
Use documentation comments (##) immediately before the export: ## Speed in pixels per second. followed by @export var speed: float. The comment becomes the Inspector tooltip on hover.
What is @export_flags and when should I use it?
@export_flags creates multi-select checkboxes for bit flag values. Example: @export_flags("Fire", "Water", "Earth") var elements: int = 0. Each option is a power of 2 (1, 2, 4...). Selecting multiple options combines them with bitwise OR. Use for spell elements, status effects, or any multi-option selection. Same concept as collision layer masks.
How do I export an easing curve for game feel?
Use @export_exp_easing var curve: float. This shows a visual curve editor in the Inspector. Values < 1.0 ease out (fast start, slow end), values > 1.0 ease in (slow start, fast end). Use with the ease() function or pass to Tween.set_ease(). Add "attenuation" or "positive_only" hints for specialized curves.
How do I export collision layers using my project layer names?
Use @export_flags_2d_physics or @export_flags_3d_physics instead of manual @export_flags. These read layer names from Project Settings → Layer Names, so checkboxes show "Player", "Enemy" instead of bit numbers. Also available: @export_flags_2d_render, @export_flags_2d_navigation.
GDScript Exports Syntax Quick Reference
extends CharacterBody2D
## Movement tuning
@export_group("Movement")
@export var speed: float = 200.0
@export_range(0.0, 1.0, 0.05) var friction: float = 0.1
## Combat stats
@export_group("Combat")
@export var max_hp: int = 100
@export_range(1, 50, 1) var attack_damage: int = 10
@export var weapon_scene: PackedSceneextends Node
enum Difficulty { EASY, NORMAL, HARD }
@export var difficulty: Difficulty = Difficulty.NORMAL
@export_flags("Fire", "Ice", "Lightning") var spell_elements: int = 0
@export_file("*.tscn") var next_level_path: String
@export_multiline var description: String = ""extends Node2D
@export var enemy_scenes: Array[PackedScene] = []
@export var spawn_points: Array[Marker2D] = []
@export var target: Node2D
@export_node_path("AnimatedSprite2D") var sprite_path: NodePath@export_range(1, 100) var max_hp: int = 100:
set(value):
max_hp = clampi(value, 1, 100)
if hp > max_hp:
hp = max_hpGDScript Exports Sample Exercises
Fill in the blank to make this variable editable in the Godot inspector.
@exportFill in the blank to constrain hp between 0 and 100 in the inspector.
export_rangeWhat does this code print?
100+ 6 more exercises