Writing Attributes
GDScript has no user-definable annotations, so attributes are written as strings in the hint text of
@export_custom, separated by ;:
@export_custom(PROPERTY_HINT_NONE, "min_value:0; max_value:100")
var health: int = 50
An attribute’s arguments follow a :, and multi-argument attributes separate them with ,:
@export_custom(PROPERTY_HINT_NONE, "min_max_slider:0,100")
var damage_range: Vector2
Everything after the first : belongs to the attribute, so a condition can contain colons and commas of its own.
Note
The first argument of @export_custom must be PROPERTY_HINT_NONE.
A hint text is parsed only when the property carries no built-in hint;
any other hint is passed through to Godot untouched.
Escaping
; separates attributes and , separates arguments. Escape them as \; and \\, –
the double backslash is GDScript’s, which rejects \, inside a string literal.
Attributes that take their text whole (info_box / warning_box / error_box and label) need no comma escaping at all.
@tool
Conditions are expressions evaluated against the edited object. Reading properties and comparing enum values works on an ordinary script:
extends Node
@export
var is_immortal: bool
@export_custom(PROPERTY_HINT_NONE, "show_if:!is_immortal")
var health: int = 100
Calling a method requires @tool on your script.
Without it the editor gives the script a placeholder instance, which holds values but has no methods:
@tool # required for method calls in attributes
extends Node
@export_custom(PROPERTY_HINT_NONE, "show_if:is_at_max_health()")
var at_max_health: String = ""
func is_at_max_health():
return self.health >= self.max_health
Expressions are Godot’s own Expression class, so comparisons, !, parentheses, and/or
(and their &&/|| spellings), constants and enum values all work:
@tool # required for method calls in attributes
extends Node
@export
var is_immortal: bool
@export
var max_health: int = 100
@export
var health: int = 100
@export_custom(PROPERTY_HINT_NONE, "show_if:is_at_max_health() || is_immortal")
var unbreakable: String = ""
func is_at_max_health():
return self.health >= self.max_health
Godot’s own hints still work
Built-in hints are re-encoded rather than reimplemented, and handed to Godot’s native editor untouched. That lets a built-in and an attribute share the one hint text slot:
@export_custom(PROPERTY_HINT_NONE, "range:0,10,0.1; show_if:is_ranged")
var spread: float
range, enum, flags, multiline, resource_type, node_type, dir, file
and the rest behave exactly as their @export_* equivalents.
This also matters for reference properties. A plain @export var icon: Texture2D already carries a hint,
so it cannot carry attributes. Re-encode the picker and the attribute rides along:
@export_custom(PROPERTY_HINT_NONE, "resource_type:Texture2D; require:icon")
var icon: Texture2D