# How to Implement and Emit Custom Signals in GDExt-Nim Extension Classes

> Easily implement and emit custom signals in GDExt-Nim extension classes. Declare a proc with signal pragmas and call it to send signals to Godot. Boost your Godot-Nim development.

- Repository: [godot-nim 4+/gdext-nim](https://github.com/godot-nim/gdext-nim)
- Tags: how-to-guide
- Published: 2026-03-02

---

**To implement custom signals in GDExt-Nim, declare a proc with your class as the first parameter, return type `Error`, and the `.{.gdsync, signal.}` pragmas, then call that proc directly to emit the signal to Godot.**

GDExt-Nim bridges Nim and Godot 4.x by allowing you to define Godot-compatible **custom signals** directly in Nim-written extension classes. This guide walks through the exact mechanics used in the `godot-nim/gdext-nim` repository to register, emit, and connect signals using compile-time macros and the Godot ClassDB API.

## Signal Declaration Requirements

Signal procs in GDExt-Nim must follow a strict signature to satisfy the `syncSignal` macro validation found in `src/gdext/private/userclass/signals.nim`.

- **First parameter**: Must be the user-class type (the `self` object).
- **Return type**: Must be `Error` (lines 60–66 of *signals.nim* enforce this at compile time).
- **Pragmas**: Add `.{.gdsync, signal.}` for synchronous signals, or `.{.gdasync, signal.}` for asynchronous variants.

```nim
proc signal_arg0*(self: GDExtNode): Error {.gdsync, signal.}
proc signal_arg1*(self: GDExtNode; what: string): Error {.gdsync, signal.}

```

The `signal` pragma triggers the `syncSignal` macro, which transforms this declaration into a registered Godot signal and generates the emit implementation.

## How the Signal Registration Works

### The syncSignal Macro Expansion

When the Nim compiler encounters the `signal` pragma, the `syncSignal` macro (defined at the bottom of *signals.nim*) invokes either `syncSignalGlobal` or `syncSignalLocal`. Both paths call `contractSignal`, which registers the signal with the engine:

```nim
ClassDB.registerExtensionClassSignal(
    className(`arg0_T`), &`gdname`, parseParams(`params`))

```

This call (line 52 of *signals.nim*) adds your signal to Godot's `ClassDB`, making it visible to the editor and other GDScript or C++ code.

### The emitSignal Wrapper

The macro also generates a procedure body (`makebody`) that forwards to `Object.emitSignal`. The generated code resembles:

```nim
var signalName {.global.}: StringName
once:
  signalName = newStringName `gdname`
self.emitSignal(signalName, variantArrDef)

```

The `emitSignal` method is defined in `src/gdext/classes/gdobject.nim` (lines 34–40) as a thin wrapper around Godot's C API `emit_signal` function.

## Emitting Custom Signals

Once registered, you can emit signals using two equivalent syntax styles. The direct call is type-checked at compile time, while the generic string syntax allows dynamic names.

**Direct call (preferred):**

```nim
discard self.signal_arg0()          # No arguments

discard self.signal_arg1("Hello")   # With arguments

```

**Generic string-based syntax:**

```nim
self.signal"signal_arg_0"()
self.signal"signal_arg_1"("Hello")

```

Both approaches invoke the macro-generated body that calls `self.emitSignal`, forwarding the arguments to the Godot runtime.

## Connecting to Signals

Connect signals using the standard Godot `connect` API. The test file `testproject/runtime/nim/src/classes/gdextnode.nim` (lines 31–35) demonstrates this pattern:

```nim
check self.connect("signal_arg_0", self.callable"listen_0") == ok
check self.connect("signal_arg_1", self.callable"listen_1") == ok

```

The `callable` syntax creates a first-class function reference to your handler method, compatible with Godot's `Callable` system.

## Complete Implementation Example

The following self-contained snippet demonstrates defining, connecting, and emitting custom signals in a complete GDExt-Nim class:

```nim
import gdext

# ------------------------------------------------------------

# 1️⃣ Define a class that inherits from a Godot type (e.g. Node)

# ------------------------------------------------------------

type MyNode* = ptr object of Node
  initialized: bool

# ------------------------------------------------------------

# 2️⃣ Register custom signals

# ------------------------------------------------------------

proc ping*(self: MyNode): Error {.gdsync, signal.}
proc pong*(self: MyNode; payload: string): Error {.gdsync, signal.}

# ------------------------------------------------------------

# 3️⃣ Define handler methods that will react to the signals

# ------------------------------------------------------------

proc onPing(self: MyNode) {.gdsync.} =
  echo "Ping received!"

proc onPong(self: MyNode; payload: string) {.gdsync.} =
  echo "Pong received with payload: ", payload

# ------------------------------------------------------------

# 4️⃣ Hook to set everything up (e.g. in _ready)

# ------------------------------------------------------------

method ready(self: MyNode) {.gdsync.} =
  # Connect signals to the handlers

  discard self.connect("ping", self.callable"onPing")
  discard self.connect("pong", self.callable"onPong")

  # Emit the signals

  discard self.ping()                     # direct call

  discard self.pong("hello world")        # direct call with arg

  self.signal"ping"()                     # generic syntax

  self.signal"pong"("nim rocks!")        # generic syntax

```

Under the hood, `ping` and `pong` are transformed by `syncSignal` → `contractSignal` → `ClassDB.registerExtensionClassSignal`. Their bodies become calls to `Object.emitSignal`, while `connect` registers the listener with the Godot engine.

## Summary

- Declare signal procs with `self` as the first argument and return type `Error`, marked with `.{.gdsync, signal.}`.
- The `syncSignal` macro in `src/gdext/private/userclass/signals.nim` automatically registers the signal via `ClassDB.registerExtensionClassSignal` and generates the emit logic.
- Call the signal proc directly (e.g., `self.signalName()`) or use the generic syntax `self.signal"signalName"()` to emit.
- Connect listeners using `self.connect("signalName", self.callable"handler")` as implemented in the Godot API wrapper.

## Frequently Asked Questions

### What is the required return type for signal procs in GDExt-Nim?

Signal procs must return `Error`. The `syncSignal` macro enforces this constraint at compile time (lines 60–66 of `src/gdext/private/userclass/signals.nim`) to ensure compatibility with the Godot extension protocol.

### Can I use asynchronous signals in GDExt-Nim?

Yes. Replace the `.{.gdsync, signal.}` pragma with `.{.gdasync, signal.}`. The macro system supports both synchronous and asynchronous variants through the `syncSignal` and `gdasync` dispatch paths.

### Where is the signal registration handled in the source code?

Registration occurs in `src/gdext/private/userclass/signals.nim` via the `contractSignal` helper, which calls `ClassDB.registerExtensionClassSignal`. The actual emission wrapper lives in `src/gdext/classes/gdobject.nim` at lines 34–40.

### How do I pass arguments when emitting signals dynamically?

Use the generic string-based syntax: `self.signal"signal_name"(arg1, arg2)`. The macro-generated body automatically packs these arguments into a `Variant` array and forwards them to `Object.emitSignal`, matching the signature defined in your proc declaration.