Quick answer: Custom signals must be declared at the top of your script using the 'signal' keyword, such as 'signal my_signal'. If the signal is declared inside a function or after the _ready() method, the editor may not detect it. Also try saving the script and refreshing the editor with Ctrl+Shift+R.
Here is how to fix Godot custom signal not showing in editor. You declared a custom signal in your GDScript file, but when you select the node in the editor and open the Node → Signals panel, your signal is nowhere to be found. The built-in signals are all there, but your custom one is missing. This is a common stumbling block, especially for developers new to Godot 4’s signal declaration syntax.
The Symptom
You have a script attached to a node with a custom signal declaration. When you select that node in the scene tree and click the Node tab → Signals panel on the right side of the editor, you see all the built-in signals for that node type (like ready, tree_entered, etc.) but your custom signal does not appear in the list.
Your script might look something like this:
extends CharacterBody2D
var health: int = 100
func _ready():
pass
func take_damage(amount: int):
health -= amount
signal health_changed # Wrong! Declared inside a function
emit_signal("health_changed", health)
The signal declaration inside the function compiles without error in some cases, but the editor does not pick it up because it only scans top-level declarations when building the signal list for the inspector panel.
What Causes This
Cause 1: Signal declared inside a function instead of at the top level. The Godot editor parses scripts statically to find signal declarations. It looks for signal keywords at the class level — the same indentation level as var, func, and @export declarations. If you put a signal declaration inside a function body, the static parser does not see it, even though the runtime might process it differently.
Cause 2: Confusing @export with signal. Some developers coming from other engines expect signals to be declared with an annotation or decorator pattern. In Godot 4, @export is for exposing variables to the inspector, not for signals. Writing something like @export signal health_changed is a syntax error, but the confusion leads developers to try various incorrect patterns before landing on the right one.
Cause 3: The editor has not refreshed after adding the signal. The Godot editor does not always reparse scripts immediately after you modify them. If you add a signal declaration and immediately check the Signals panel without saving the script, the editor may still be showing the old parsed version of your script. This is especially common when editing scripts in an external editor rather than the built-in one.
The Fix
Step 1: Declare signals at the top level of your script. Signals must be declared at the class body level, not inside any function. Place them near the top of your script alongside variable declarations:
extends CharacterBody2D
# Signals declared at top level — visible in editor
signal health_changed(new_health: int)
signal died
signal damage_taken(amount: int, source: Node)
var health: int = 100
func take_damage(amount: int, source: Node):
health -= amount
health_changed.emit(health)
damage_taken.emit(amount, source)
if health <= 0:
died.emit()
Step 2: Use the Godot 4 signal declaration syntax with typed parameters. Adding parameter types to your signal declarations makes them more useful in the editor. The Signals panel will show the parameter names and types, making it easier to connect them correctly:
# Without types — works but less informative in editor
signal score_changed
# With types — editor shows parameter info
signal score_changed(new_score: int, multiplier: float)
# Emitting the typed signal
func add_score(points: int):
score += points * combo_multiplier
score_changed.emit(score, combo_multiplier)
Step 3: Save the script and refresh the editor. After adding your signal declaration, save the script with Ctrl+S. If the signal still does not appear in the Signals panel, try these steps in order:
- Click away from the node and click back to it in the scene tree.
- Close and reopen the scene tab.
- Use Project → Reload Current Project to force a full reparse.
If you are using an external editor, Godot may not detect the file change immediately. Switch focus to the Godot editor window and it should detect the modification and reparse the script.
Step 4: Verify the script is actually attached to the node. A common oversight is checking the Signals panel on a node that has no script or has a different script than you expect. Right-click the node in the scene tree and choose Attach Script or verify the script path shown in the Inspector panel matches the file you edited:
# Quick sanity check — print the script path at runtime
func _ready():
print("Script: ", get_script().resource_path)
Why This Works
The Godot editor uses a static analysis pass to build the list of signals for the Node → Signals panel. This pass reads the script file from disk (not from the running game) and looks for signal keyword declarations at the top indentation level. It does this for performance — parsing every script fully every time you click a node would be too slow for large projects.
By placing signal declarations at the top level with the signal keyword, you ensure the static parser finds them. The typed parameter syntax is optional for the editor to detect the signal, but including it provides better information in the Signals panel and enables autocompletion when connecting signals in code.
Saving the script triggers the editor to reparse it. The Godot editor watches for file changes on scripts that are currently loaded, and a save event triggers a reparse that updates the Signals panel, the autocompletion database, and the error checker.
“If your signal is not at the top level of the script, the editor cannot see it. The runtime might tolerate it, but the tooling will not.”
Related Issues
If your custom signal shows in the editor but you get a “Nonexistent function” error when connecting it, the problem is with the connection target, not the signal declaration. See our guide on fixing the Nonexistent Function error when connecting signals.
If the signal connects fine but fires before child nodes are ready, you have a timing issue with the _ready() call order. See fixing signals that fire before child nodes are ready.
For signals that connect and fire but pass the wrong number of arguments to the callback, check our post on fixing signal argument mismatches.
Top-level declaration. Save. Refresh. That is the recipe.