How to Create Custom Properties That Appear in the Godot Inspector Using gdext-nim

Use the {.gdexport.} pragma on Nim class fields and optionally pass an Appearance value from gdext/appearances.nim to control which editor widget Godot renders in the Inspector.

The godot-nim/gdext-nim repository provides a Nim-to-GDExtension binding that lets you write Godot 4 game logic in Nim while maintaining full editor integration. When you create custom properties that appear in the Godot inspector, the library automatically bridges Nim field definitions to Godot's PropertyInfo system, enabling visual editing without writing boilerplate registration code.

How Property Registration Works

The gdext bridge implements a three-stage pipeline that converts Nim pragmas into Godot inspector entries:

  1. src/gdext/bridge.nim (lines 260–320) – Exports the public gdexport templates and macros that you write in your class definitions.
  2. src/gdext/private/internalbridge.nim (lines 196–240) – Implements gdexport_internal, which inspects the gdexport pragma, extracts optional Appearance metadata, builds a Godot PropertyInfo structure, and registers the property with the engine.
  3. src/gdext/appearances.nim – Defines the Appearance helper type and constructors (range, enum, file, multiline, etc.) that map to specific Godot inspector widgets.

When the Nim compiler processes your class, internalbridge.nim iterates over all fields (for field in classInfo.fields). If a field carries the gdexport pragma, the bridge checks for an optional argument via field.getPragmaVal("gdexport"). The extracted value—an Appearance instance—determines the widget type, validation limits, and display hints that Godot uses when rendering the property panel.

Exporting Basic Properties

To expose a field to the editor with the default widget for its type, annotate the field with {.gdexport.} and ensure it is exported from the Nim module using the * postfix:

import gdext

gdclass MyNode of Node:
  counter* {.gdexport.}: int = 0

This registers counter as a read/write property in the Godot Inspector, appearing as a standard integer spin-box.

Controlling Editor Appearance

For specialized widgets, import gdext/appearances and pass an Appearance constructor to the pragma:

Numeric Inputs with Ranges

Use Appearance.range to replace the spin-box with a slider and define hard limits or soft boundaries:

import gdext, gdext/appearances

gdclass MyNode of Node:
  health* {.gdexport: Appearance.range(0, 100, step = 5).}: int = 50
  damage* {.gdexport: Appearance.range(min = 10, max = 100, step = 5, or_less, or_greater).}: int = 20

The or_less and or_greater flags allow users to input values outside the slider range while keeping the visual guide intact.

Enum Dropdowns and Flag Sets

Export Nim enums as dropdown selectors or bitmask checklists:

import gdext, gdext/appearances

type
  Weapon = enum
    Sword, Bow, Axe
  Ability = enum
    Jump, Run, Fly

gdclass MyNode of Node:
  # Dropdown selector

  weapon* {.gdexport: Appearance.enum("Sword", "Bow", "Axe").}: Weapon = Weapon.Sword
  
  # Bitmask checklist (set[Enum])

  abilities* {.gdexport: Appearance.flags("Jump", "Run", "Fly").}: set[Ability] = {}

File and Directory Pickers

Trigger native file dialogs with extension filters or directory selection:

import gdext, gdext/appearances

gdclass MyNode of Node:
  scriptFile* {.gdexport: Appearance.file("*.txt;*.nim").}: String = ""
  dataFolder* {.gdexport: Appearance.dir.}: String = ""

Multiline Text and Placeholders

For long text fields, enable the multiline editor and add placeholder hints:

import gdext, gdext/appearances

gdclass MyNode of Node:
  description* {.gdexport: Appearance.multiline.}: String = "Default description text"
  secretCode* {.gdexport: Appearance.placeholder("Enter activation code...").}: String = ""

Organizing Properties with Categories

Use the gdexport macro to group related fields under collapsible sections in the Inspector:

import gdext

gdclass MyNode of Node:
  gdexport[MyNode] "Combat Settings", Appearance.category:
    attackPower* {.gdexport.}: int = 10
    defense* {.gdexport.}: int = 5

This creates a "Combat Settings" header in the Godot Inspector containing the two fields, improving editor organization for complex nodes.

Complete Working Example

The test suite at testproject/editor/nim/src/classes/gdproptestnode_pragmas.nim demonstrates every supported editor hint in a single class:

import gdext, gdext/appearances

gdclass PropTestNode of Node:
  icon* {.gdexport.}: gdref Texture2D
  string_with_export* {.gdexport.}: string = "with export"
  string_with_export_placeholder* {.gdexport: Appearance.placeholder("placeholder here...").}: string
  int_with_export_range* {.gdexport: Appearance.range(min = 10, max = 100, step = 5, or_less, or_greater).}: int = 20
  color_with_export* {.gdexport.}: Color = color(1, 1, 1, 0.5)
  node_path* {.gdexport(Appearance.nodePath("Node2D", "Node3D")).}: NodePath

This class exposes textures, ranged integers, colors, and typed node paths, each rendering with the appropriate native Godot widget.

Summary

  • Annotation: Add {.gdexport.} to any exported Nim field (*) to register it with Godot.
  • Appearance: Import gdext/appearances and pass Appearance.range, Appearance.enum, Appearance.file, or other helpers to customize the inspector widget.
  • Registration: The bridge in src/gdext/private/internalbridge.nim handles PropertyInfo creation and engine registration automatically at compile time.
  • Organization: Use the gdexport macro with Appearance.category to create collapsible groups in the Inspector panel.

Frequently Asked Questions

What is the difference between the gdexport pragma and the gdexport macro?

The {.gdexport.} pragma registers individual fields with optional appearance metadata, while the gdexport[ClassName] "Group Name", Appearance.category: macro creates logical groupings that wrap multiple fields under a collapsible Inspector section. Use the pragma for data exposure and the macro for UI organization.

How do I create a ranged slider for float values instead of integers?

The Appearance.range constructor works for any numeric type. Specify float literals in the arguments, and Godot will render a float slider:

speed* {.gdexport: Appearance.range(0.0, 100.0, step = 0.1).}: float = 10.0

Can I restrict NodePath exports to specific node types?

Yes. Pass valid class names to Appearance.nodePath to filter the node selection dialog:

targetSprite* {.gdexport: Appearance.nodePath("Sprite2D", "AnimatedSprite2D").}: NodePath

Only nodes inheriting from the specified types will be selectable in the editor's node picker.

Where is the low-level property binding logic implemented?

The core registration logic resides in src/gdext/private/internalbridge.nim, specifically in the gdexport_internal procedure (around line 196) and the pragma-inspection loop (around line 227) that iterates through class fields and constructs the Godot PropertyInfo objects.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →