Using @export vs @onready in Godot 4.7? What's the convention?

Godot Version

Godot 4.7

Question

A user on reddit told me that I should never be using anything but @export variables in Godot 4. Their exact words:

And in the future, only, ever, use @export. All methods for getting nodes other than @export are redundant, and only exist because of Godot 2 and 3.

I was previously using @onready to avoid having inspector clutter, and because well… it seemed to work just fine. Simple example:

extends CanvasLayer

@onready var speaker_name: Label = %SpeakerName
@onready var dialogue_text: RichTextLabel = %DialogueRichTextLabel

This code works. I get no errors. But according to them, it’s wrong?

I’m a complete beginner, but the justification they gave me doesn’t convince me at all. If the node I want to reference is in my scene, I use @onready, and the editor will even create it that way when you drag it. If the node is outside the scene, then it’s probably necessary to use @export. They’re different concepts.
Perhaps he was referring to sibling nodes. In that case, using @export might be advisable.

No. It’s fine.

The effects of @export and @onready annotations are technically not mutually exclusive. They deal with fundamentally different things.

@onready postpones the initialization assignment to the time _ready() is called. It’s equivalent of assigning the value inside _ready() function.

@export exposes the variable to the inspector and also makes it serializable to disk, i.e. it sets property’s PORPERTY_USAGE_EDITOR and PROPERTY_USAGE_STORAGE flags.

Although you can write only one annotation in scripts, you can actually make a variable initialize on ready and at the same time set its usage flags to behave like it’s exported. You can do so by overriding _get_property_list() or _validate_property(), or annotate it as @export and initialize it in _ready(). The value assigned in _ready() will override the value assigned in the inspector though, so in practice it may seem those two annotations are kinda mutually exclusive in respect to initialization.

I would actually recommend being really careful to use @export only where necessary. Every use of @export is an opportunity to accidentally break the game by forgetting to set the variable in the inspector. If there’s an automated way (@onready) and a manual way (@export) way to do the same thing, always pick the automated way.

@onready is not really “automated”. If the variable is supposed to hold a node reference, it requires hardcoding the node path. That comes with its own set of drawbacks.

It’s automated in the sense that you don’t need to manually edit the variable in the inspector. Either you’re hardcoding the path in the script or you’re hardcoding the path in the scene; either way you’re hardcoding the path.

If the script is reused across multiple different scenes, with different values for the variable, then you have a valid use of @export.

The path assigned to the exported property in the inspector is not hardcoded. The editor will track if that node’s path gets changed in the editor and update the property value accordingly. This automatic update is the main benefit of exported node references.

The same was asked yesterday, so I’ll leave my response here for reference as well

Main feature of @export is to changing value for variable, tracking of node is better using unique % correct?

At least that how I understood it and use it, exporting things like multipliers or speed so I can test how they looks, or assign node from different scenes for example levels when refer to spawn point etc.

Not if you rename the node. @export will track that too. Also % works only inside the same scene.

On the other hand, @export will break if you rename the variable and @onready won’t.

Well that’s why the either/or discussion is really pointless. Both have drawbacks and both will suffice if you know what you’re doing. So best to learn how each works and use the one you think is more fitting for your use case. The best approach is to altogether minimize the usage of node references that are initialized by paths.

I believe that if @export is used, it is always good practice to use an assert in the _ready checking if it has been filled or not.