GUML (Godot UI Markup Language) is a declarative markup language for building user interfaces in Godot .NET applications. This guide walks you through installation, project setup, and common usage patterns.
For complete syntax details, see guml_syntax.md.
Add the NuGet packages to your Godot .NET project:
dotnet add package GUML
dotnet add package GUML.SourceGenerator
Then register your .guml files as additional files so the source generator can process them. Add the following to your .csproj:
<ItemGroup>
<!-- Register .guml files for source generation -->
<AdditionalFiles Include="gui\**\*.guml" />
</ItemGroup>If you use custom GUI component types in your own namespaces, also configure:
<PropertyGroup>
<!-- Semicolon-separated namespaces containing custom GUI components -->
<GumlNamespaces>MyGame.GUI;MyGame.Widgets</GumlNamespaces>
</PropertyGroup>
<ItemGroup>
<CompilerVisibleProperty Include="GumlNamespaces" />
</ItemGroup>Initialize GUML in your root Node's _Ready() method:
using Godot;
using GUML;
public partial class Main : Node
{
[Export]
public Node Root;
private GuiController _controller;
public override void _Ready()
{
Guml.Init();
Guml.ResourceProvider = new DefaultResourceProvider();
_controller = Guml.LoadGuml(Root, "gui/main.guml");
}
public override void _Process(double delta)
{
_controller?.Update();
}
public override void _ExitTree()
{
_controller?.Dispose();
}
}Guml.Init()initializes the GUML runtime.Guml.ResourceProvidersets the resource loader.DefaultResourceProviderprovides built-in caching and reference-counted lifecycle management. ImplementIResourceProviderfor custom loading logic.Guml.LoadGuml(rootNode, path)loads a.gumlfile, attaches the generated UI torootNode, and returns the controller instance.Guml.DefaultThemean optional static property to set a default theme applied to all UI components.
Every .guml file has a paired C# controller class. The naming convention is: convert the file name to PascalCase and append Controller. For example, main.guml MainController.
Annotate your controller with [GumlController] and declare it as partial:
using GUML;
[GumlController("../../gui/main.guml")]
public partial class MainController : GuiController
{
public override void Created()
{
// Called after the UI tree is built and all bindings are established.
}
}The [GumlController] attribute path is relative to the controller source file.
Every controller inherits from GuiController and implements INotifyPropertyChanged.
| Method | When called | Notes |
|---|---|---|
Created() |
After the UI tree is built and all bindings are set up | Called automatically by LoadGuml |
Update() |
Every frame equivalent to Node._Process |
Call manually in _Process |
Dispose() |
When the controller is destroyed | Call manually in _ExitTree. Automatically removes the bound GUI node from the scene tree. |
GUML uses a Roslyn source generator (GUML.SourceGenerator) that compiles .guml files into strongly-typed C# classes at build time, providing compile-time error checking and zero-reflection runtime loading.
For each [GumlController]-annotated class the generator produces:
- View class (
XxxGumlView.g.cs): ABuild()method that constructs the UI tree. A[ModuleInitializer] Register()method that registers the view factory intoGuml.ViewRegistryautomatically. - Controller partial (
XxxController.NamedNodes.g.cs): Strongly-typed properties for all@aliasnamed nodes and imported controller instances. Only generated when the controller ispartialand the.gumlfile has aliases or imports.
Guml.LoadGuml() looks up the registered factory in Guml.ViewRegistry. If found, it instantiates the controller and calls Build() no reflection. If not found, a GumlRuntimeException is thrown with guidance to add [GumlController].
Use @name: before a component declaration to name it. The source generator injects a strongly-typed property for it on the controller.
main.guml:
Panel {
@hello_label: Label {
text: "hello"
}
}
Auto-generated (MainController.NamedNodes.g.cs):
public partial class MainController
{
/// <summary>Named node '@hello_label' (Label).</summary>
public Label HelloLabel { get; internal set; }
}Access it in the controller with full type safety:
public override void Created()
{
HelloLabel.Text = "Updated!"; // compile-time type check
HelloLabel.Modulate = Colors.Red;
}The alias name uses snake_case in GUML and is converted to PascalCase for the C# property (hello_label HelloLabel).
When a .guml file imports another component, the source generator automatically produces a typed property on the controller:
main.guml:
import "panel/setting"
Panel {
Setting {}
}
Auto-generated:
public partial class MainController
{
/// <summary>Import controller for 'panel/setting.guml'.</summary>
public SettingController SettingController { get; set; }
}If you manually define the property in your controller, the generator detects it and skips generation.
| ID | Severity | Description |
|---|---|---|
| GUML005 | Error | .guml file not found in AdditionalFiles |
| GUML006 | Error | Multiple controllers reference the same .guml file |
| GUML007 | Warning | Controller not declared as partial named node properties cannot be generated |
GUML supports binding controller properties to UI component properties using :=. When the controller property changes (calls OnPropertyChanged()), the UI updates automatically.
All properties involved in bindings must call OnPropertyChanged() in their setter:
public partial class MainController : GuiController
{
private string _userName = "Player";
public string UserName
{
get => _userName;
set
{
_userName = value;
OnPropertyChanged(); // required to trigger UI updates
}
}
}Use := to bind a controller property to a UI property:
main.guml:
Panel {
Label {
position: vec2(10, 10),
size: vec2(200, 30),
text := $controller.user_name
}
}
Expression binding the UI re-evaluates whenever any referenced property changes:
Label {
text := $"Hello, {$controller.user_name}! Score: {$controller.score}"
}
Use <=> for symmetric binding between a component property and a controller property. The right-hand side must be a writable controller property. Requires the component to support property change notifications.
CustomToggle {
checked <=> $controller.is_enabled
}
The data source for each blocks must implement INotifyListChanged. GUML provides NotifyList<T>:
public NotifyList<string> Names
{
get => _names;
set
{
_names = value;
OnPropertyChanged();
}
}
private NotifyList<string> _names = new() { "Alice", "Bob" };main.guml:
Panel {
each $controller.names { |idx, name|
Control {
custom_minimum_size: vec2(220, 40),
Label {
position: vec2(10, 10),
text: name
}
}
}
}
Incremental updates: Calling Names.Add(...) only creates nodes for the new item. Other list mutations (Remove, Insert, Clear) trigger a full re-render.
GUML provides built-in tr() and ntr() expressions for text translation, following the gettext convention.
Implement the IStringProvider interface and assign it at startup:
public override void _Ready()
{
Guml.Init();
Guml.ResourceProvider = new DefaultResourceProvider();
Guml.StringProvider = new MyStringProvider(); // your IStringProvider implementation
_controller = Guml.LoadGuml(Root, "gui/main.guml");
}IStringProvider requires:
Tr(msgid, context?, args?)— singular translation.Ntr(singular, plural, count, context?, args?)— plural translation.INotifyPropertyChanged— raisePropertyChanged("CurrentLocale")on locale switch.
When Guml.StringProvider is null, tr() returns the msgid unchanged.
Label { text: tr("Start Game") }
Label { text := tr("Hello, {name}!", { name: $controller.user_name }) }
Label { text := ntr("One item", "{count} items", $controller.item_count,
{ count: $controller.item_count }) }
When used with :=, translations automatically re-evaluate on locale changes.
For full syntax details, see the Translation Expressions section in the syntax reference.
MyGame/
Main.cs
gui/
main.guml
MainController.cs
panel/
setting.guml
SettingController.cs
import "panel/setting"
Panel {
@title_label: Label {
position: vec2(10, 10),
size: vec2(400, 40),
text := $"Welcome, {$controller.player_name}!"
}
@start_btn: Button {
position: vec2(10, 60),
size: vec2(120, 40),
text: "Start Game",
#pressed: $controller.on_start_pressed
}
Setting {
position: vec2(10, 110)
}
each $controller.scores { |idx, score|
Control {
custom_minimum_size: vec2(400, 30),
Label {
text: $"{idx + 1}. {score.name}: {score.value}"
}
}
}
}
using Godot;
using GUML;
[GumlController("main.guml")]
public partial class MainController : GuiController
{
private string _playerName = "Player";
public string PlayerName
{
get => _playerName;
set { _playerName = value; OnPropertyChanged(); }
}
public NotifyList<ScoreEntry> Scores { get; } = new();
public override void Created()
{
// TitleLabel and StartBtn are auto-generated from @alias declarations
GD.Print(TitleLabel.Text);
}
private void OnStartPressed()
{
GD.Print("Game started!");
}
}
public record ScoreEntry(string Name, int Value);using Godot;
using GUML;
public partial class Main : Node
{
[Export] public Node Root;
private GuiController _controller;
public override void _Ready()
{
Guml.Init();
Guml.ResourceProvider = new DefaultResourceProvider();
_controller = Guml.LoadGuml(Root, "gui/main.guml");
}
public override void _Process(double delta)
{
_controller?.Update();
}
public override void _ExitTree()
{
_controller?.Dispose();
}
}GUML provides a ThemeOverrides property on components that mirrors the editor's ThemeOverrides panel. Its value is an object literal:
Button {
theme_override_colors_font_color: color(1.0, 1.0, 1.0, 1.0),
theme_override_font_sizes_font_size: 16,
theme_override_styles_normal: style_box_flat({
bg_color: color(0.2, 0.2, 0.8, 1.0),
corner_radius_top_left: 4,
corner_radius_top_right: 4,
corner_radius_bottom_left: 4,
corner_radius_bottom_right: 4
})
}
Guml.ThemeOverrides a static property providing all available theme override entries and their types.
Guml.DefaultTheme a static property to set a default theme applied to all UI components globally.
See guml_syntax.md for the complete syntax reference, including:
- All value types (strings, template strings, enums, arrays, typed dictionaries, vectors, colors, resource loaders, etc.)
- Expressions and operators (arithmetic, logical, ternary, member access, call expressions)
- Property mapping operators (
:,:=,=:,<=>) - List rendering (
eachblock form and projection form) - Parameter declarations (
param) - Event declarations (
event) - Comma separator rules
- Identifier naming conventions