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:
src/gdext/bridge.nim(lines 260–320) – Exports the publicgdexporttemplates and macros that you write in your class definitions.src/gdext/private/internalbridge.nim(lines 196–240) – Implementsgdexport_internal, which inspects thegdexportpragma, extracts optionalAppearancemetadata, builds a GodotPropertyInfostructure, and registers the property with the engine.src/gdext/appearances.nim– Defines theAppearancehelper 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/appearancesand passAppearance.range,Appearance.enum,Appearance.file, or other helpers to customize the inspector widget. - Registration: The bridge in
src/gdext/private/internalbridge.nimhandlesPropertyInfocreation and engine registration automatically at compile time. - Organization: Use the
gdexportmacro withAppearance.categoryto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →