Skip to content

Action API

The full contract of HenScriptMacroBase, the class every action extends. For a guided walkthrough start at Write a Custom Action.

MethodReturnsNotes
get_id()StringNameRequired, and abstract. Unique and permanent, saved actions reference it.
get_display_name()StringName in the UI. Empty falls back to the file name capitalized.
get_description()StringOne or two sentences, shown on hover.
get_icon()StringLucide icon name from addons/hengo/assets/new_icons/, no extension.
get_color()StringHex tint, for example '#f97316'.

Icon and color fall back to the category, which is the first-level folder the file sits in.

MethodReturnsNotes
get_inputs()Array[Dictionary]Value slots, see input keys. Called once at load, the result is cached.
get_outputs()Array[Dictionary]{name, type, id, doc}, optionally branch. Each one needs a get_output_<id>() -> String.
get_flow_inputs()Array[Dictionary]{name, id} where the id is a phase: enter, update, physics, exit.
get_flow_outputs()Array[Dictionary]{name, id, doc, optional}. Each one is a branch on the card.

Because get_inputs() is cached from the first call, the types of the ports are static per file. A file cannot show a Vector2 slot on a 2D node and a Vector3 one on a 3D node: that is two files, gated by get_target_classes().

KeyTypeMeaning
nameStringLabel in the editor.
typeStringVariant type or class name. Drives the widget and the binding filter.
idStringNameThe {{placeholder}} name.
docStringOne-line tooltip.
default_valueanyStarting value.
optionalboolA required slot left empty skips the action, an optional one is left out of the code.
rawboolEmit the value verbatim instead of quoting it as a literal.
optionsArray[String]Fixed choices, rendered as a picker. Pair with raw, and make default_value the first option.
option_labelsArray[String]What each option reads as on screen, when the emitted value is not what a human should see.
lvalueboolAssignment target. Satisfied only by a binding, never by an expression or an inline action.
bind_onlyboolRead-only slot that needs a real source (a node, a raycast). Any binding works, node paths included.
type_fromStringNameAdopt the bound type of another input or output instead of the declared one.
pickerStringNameSuggest values the project actually has, listed when the slot opens: input_action, audio_bus, scene_path, group.

node_ref_input(doc, name, id) on the base class builds the usual “which node” slot for you, already bind_only and defaulting to Self.

A flow output takes name, id, doc and optional. An optional branch is a shortcut, not the reason the action runs: leaving it unwired keeps the plain body instead of failing the action. Ask is_flow_connected(id) inside the body and emit the if only when somebody wired it.

MethodReturnsWhere it lands
get_flow_enter()StringThe state’s enter().
get_flow_update()StringThe state’s update(delta).
get_flow_physics()StringThe state’s physics(delta).
get_flow_exit()StringThe state’s exit().
get_output_<id>()StringRight side of the assignment for that output.
get_unstored_body()StringWhat to emit when the action has outputs and nobody stores any. Empty means it is a pure producer and vanishes.
get_flow_reset()StringTop of the state’s enter(), whatever phase the action is on.
get_flow_teardown()StringThe state’s exit(), the mirror of the reset.
get_script_base()StringClass-level declarations inside the state class, so a value survives frames.
get_script_scope()StringDeclarations at script scope, next to the script’s variables.
get_function_overrides()Array[Dictionary]{name, params, body} merged into a virtual method on the node, for example _input.

Only bodies that reach an emitted action are collected. A skipped action leaves behind no declaration, no connect and no override.

TokenReplaced with
{{<input_id>}}The slot’s resolved value: inline action, expression, binding or literal, in that priority.
{{out:<id>}}<store> = <get_output_id()> when the output is stored, and the whole line is dropped when it is not.
{{<flow_output_id>}}The branch’s steps and transition, or pass when unset.
{{loop_body}}The nested actions, one indent deeper. Injected last.
{{VCNODE_ID}}This action’s unique id. Use it in every name you emit.

Substitution order is outputs, inputs, branches, {{VCNODE_ID}}, then {{loop_body}}.

Not every body goes through every pass. get_flow_reset() and get_flow_teardown() get input substitution and {{VCNODE_ID}}. get_script_base(), get_script_scope() and get_function_overrides() get only {{VCNODE_ID}}, so read configured values with value_of() and paste them into the string yourself.

  • _ref, the node the generated script runs on. Inside a function body this is emitted as self instead, since a function has no state class around it.
  • delta, in the update and physics phases only.
  • Anything you declared through get_script_base() (state class) or get_script_scope() (script scope, reached from the state as _ref.<name>).
MethodReturnsMeaning
get_default_phase()StringNamePhase a newly added action lands on. Empty picks the first declared flow input.
get_target_classes()Array[StringName]Native classes the action serves. Empty means all of them.
get_has_body()boolThe action holds a nested action list. The body must contain {{loop_body}}.
get_needs_loop()boolOnly valid inside a loop body, refused at the top level.
get_needs_function()boolOnly valid inside a function body, refused in a state. This is what Finish uses.
get_validation_error()StringA non-empty reason skips the action with a loud marker.
gate_validation_error()StringReady-made reason for a gating action with nothing nested and no branch wired, which would otherwise emit if/else of two pass.

These are primed on a fresh instance before any body getter runs, so a body can inspect how the user configured the action.

Member or methodMeaning
target_classBase class of the script being generated.
input_valuesinput id -> literal, only for slots holding a literal.
bound_inputsinput id -> true for slots fed by a binding, expression or inline action.
connected_flowsbranch id -> true for branches with somewhere to go.
nested_action_countHow many actions this one nests, its body and every branch of it.
action_phaseThe phase this body is being emitted at. A nested action runs at its loop’s phase, not the one it was stored with.
loop_depthHow many loop bodies wrap this action. Above zero means it runs many times in one frame.
value_of(id, fallback)The literal a slot holds, or the fallback.
is_bound(id)Whether a slot has a bound source.
is_flow_connected(id)Whether a branch was wired.
any_flow_connected()Whether any branch was.
targets(class)Whether target_class inherits from class, for dispatching inside a body.
output_branch(id)The branch an output is only produced inside, empty when it is reachable on every path.

An action can be plugged directly into another action’s input when it is a pure value producer: it declares outputs, declares no required flow outputs, has no loop body, does not need a loop, declares no persistent state (no script base, no script scope, no overrides, no reset or teardown), and every non-empty phase body consists of nothing but {{out:...}} lines.

An optional branch nobody wired still counts as a pure producer, since it emits nothing.

Everything else has to store its result in a variable first.

Before opening an action’s .gd to work out what code it writes, ask the CLI:

Terminal window
godot --headless --path . -s tools/hengo_cli.gd -- --preview <action_id>

It prints the inputs with their defaults and options, the outputs with their real right-hand side, the branches, the body emitted per phase and per target class, what the action declares (state_vars, reset, teardown, overrides) and a copyable JSON block for the script CLI.

Two things the preview saves you from: the same action emits different code per target class (change_color has a CanvasItem variant and a Node3D one), and a field with options can change the shape of the body, not just the constant pasted into it.

The plugin can dump the whole pool, the same data that powers the Actions reference:

Terminal window
godot --headless --path . -s tools/hengo_cli.gd -- --export-actions
godot --headless --path . -s tools/hengo_cli.gd -- --export-actions res://temp/actions.json
godot --headless --path . -s tools/hengo_cli.gd -- --export-actions out.json --with-code

With no path it writes into the docs site checked out next to the plugin (../HengoFront/src/data/actions.json), and fails if that folder is not there. Pass a path to write anywhere else, and --with-code adds what each action emits. The file carries a categories array and an actions array with every id, name, category, description, phases, inputs, outputs, branches and target classes. --list-actions prints a slimmer, per-class view.