Add, connect, and configure nodes in Blueprint event graphs and function graphs
Also load
blueprintsskill when creating new blueprints, adding variables/components, or overriding functions. This skill covers node-level graph editing — adding nodes, wiring pins, setting values, and layout.
| WRONG | CORRECT |
|---|---|
list_nodes() | get_nodes_in_graph() |
add_node() | add_function_call_node() or add_event_node() etc. |
disconnect_nodes() | disconnect_pin() |
get_node_connections() |
get_connections() |
disconnect_pin() Signature — 4 Args Onlydisconnect_pin breaks all connections from a single named pin. Do not call it like connect_nodes (which takes 6 args):
# CORRECT — 4 args: path, graph, node_id, pin_name
unreal.BlueprintService.disconnect_pin(bp_path, "EventGraph", custom_event_id, "then")
# WRONG — 6 args crashes with "takes at most 4 arguments (6 given)"
# unreal.BlueprintService.disconnect_pin(bp_path, graph, src_id, src_pin, tgt_id, tgt_pin)
To remove a specific edge, disconnect the output pin on the source node (e.g. "then"). Because the other end is the only connection on that exec pin, the single-pin disconnect is equivalent to removing the edge.
| WRONG | CORRECT |
|---|---|
node.node_name | node.node_title |
node.node_position_x | node.pos_x |
node.node_position_y | node.pos_y |
pin.direction | use the pin input/output boolean from get_node_pins() (bIsInput / Python bool field), not a direction enum |
pin.is_linked | pin.is_connected |
pin.current_value | pin.default_value |
pin.sub_pins | (does not exist) |
discover_nodes() vs get_nodes_in_graph() — Different Object Typesget_nodes_in_graph() returns FBlueprintNodeInfo objects — these have node_title, node_id, pos_x, pos_y, node_type.
discover_nodes() returns FBlueprintNodeTypeInfo objects — these have display_name (NOT node_title), spawner_key, category, tooltip, is_pure, is_latent, keywords.
# get_nodes_in_graph — use node_title
nodes = unreal.BlueprintService.get_nodes_in_graph(bp_path, graph)
for n in nodes:
print(n.node_title, n.node_id) # node_title is correct here
# discover_nodes — use display_name (NOT node_title)
matches = unreal.BlueprintService.discover_nodes(bp_path, "Broadcast")
for m in matches:
print(m.display_name, m.spawner_key) # display_name, NOT node_title
Also: search discover_nodes by function name, not variable type. To find the Broadcast Delegate node for a StateTreeDelegate variable, search "Broadcast" — NOT "Dispatcher" or "StateTreeDelegate".
Use then and else, NOT true/false:
# WRONG
connect_nodes(path, func, branch_id, "true", target_id, "execute")
# CORRECT
connect_nodes(path, func, branch_id, "then", target_id, "execute")
connect_nodes(path, func, branch_id, "else", target_id, "execute")
| WRONG | CORRECT |
|---|---|
Greater_FloatFloat | Greater_DoubleDouble |
Add_FloatFloat | Add_DoubleDouble |
unreal.BlueprintService.add_variable(path, "Health", "float", "100.0")
unreal.BlueprintService.compile_blueprint(path) # REQUIRED before adding nodes
unreal.BlueprintService.add_get_variable_node(path, func, "Health", x, y)
Do not assume Blueprint nodes have numeric IDs like 0 or 1.
get_nodes_in_graph() returns GUID strings in node.node_id.
nodes = unreal.BlueprintService.get_nodes_in_graph(bp_path, graph)
for node in nodes:
print(node.node_id, node.node_title)
For function graphs, find nodes by node_type or node_title.
For event-style overrides, find nodes by the display title Unreal shows in the graph, not by the raw override function name.
"EventGraph" — NOT the Function NameWhen an override is event-style (void/latent functions like ReceiveTreeStart, ReceiveTick,
ReceiveLatentEnterState, ReceiveStateCompleted), the event node lives inside "EventGraph".
Do NOT use the function name as the graph name — that graph doesn't exist.
# WRONG — returns 0 nodes, creates nothing
nodes = unreal.BlueprintService.get_nodes_in_graph(bp_path, "ReceiveTreeStart")
node_id = unreal.BlueprintService.add_function_call_node(bp_path, "ReceiveTreeStart", ...)
# CORRECT — event-style overrides are in EventGraph
nodes = unreal.BlueprintService.get_nodes_in_graph(bp_path, "EventGraph")
node_id = unreal.BlueprintService.add_function_call_node(bp_path, "EventGraph", ...)
Common event-style overrides and their graph name → node title:
| Raw Function Name | Graph Name | Node Title in Graph |
|---|---|---|
ReceiveTreeStart | "EventGraph" | Event TreeStart |
ReceiveTick | "EventGraph" | Event Tick |
ReceiveTreeStop | "EventGraph" | Event TreeStop |
ReceiveLatentEnterState | "EventGraph" | Event EnterState |
ReceiveLatentTick | "EventGraph" | Event Tick |
ReceiveStateCompleted | "EventGraph" | Event StateCompleted |
Only function-style overrides (non-void, like GetStaticDescription) create a separate function graph.
Blueprint display titles and internal UFunction names are often not the same.
Common examples:
| Display title in graph | Internal callable name may be |
|---|---|
Get Actor Location | K2_GetActorLocation |
Set Actor Location | K2_SetActorLocation |
VInterp To | VInterpTo |
If you do not already know the exact callable name, do not guess and do not batch multiple node creations into one shot. Use one of these two patterns:
discover_nodes() + create_node_by_key() for deterministic editor-style creation.add_function_call_node() only after discovery, or when you already know the exact function name.import unreal
bp_path = "/Game/StateTree/Tasks/STT_Chase"
graph = "EventGraph"
matches = unreal.BlueprintService.discover_nodes(bp_path, "Get Actor Location")
actor_get_location = next(
node for node in matches
if node.spawner_key.startswith("FUNC AActor::") or node.spawner_key.startswith("FUNC Actor::")
)
node_id = unreal.BlueprintService.create_node_by_key(
bp_path, graph, actor_get_location.spawner_key, 400, 0)
assert node_id, actor_get_location.spawner_key
If a node-create call returns an empty ID, stop immediately. Re-read the graph, inspect the error output, and fix the lookup before creating anything else.
For fragile graph work such as STT_* task Blueprints, timers, delegate workflows, or any graph being built from a screenshot:
get_connections().Do not create 4-6 nodes in one tool call when some of them have unresolved function names. That makes cleanup harder and leaves duplicate nodes behind on retry.
If a graph-edit attempt fails partway through:
get_nodes_in_graph() and get_connections() to see the true post-failure state.delete_node().disconnect_pin().Do not invent helper names like disconnect_all_pins(). Use the real APIs that exist.
Examples for StateTree task blueprints:
| Override created | Typical graph title |
|---|---|
ReceiveLatentEnterState | Event EnterState |
ReceiveLatentTick | Event Tick |
ReceiveStateCompleted | Event StateCompleted |
When a user asks for a "timer" or "timed delay" in a Blueprint, use Set Timer by Event, NOT Delay:
| Node | Behavior | Use When |
|---|---|---|
Delay | Latent action — blocks execution flow on that path | Simple linear sequences where nothing else runs during the wait |
Set Timer by Event | Non-blocking — fires a delegate callback after the duration | The Blueprint must keep ticking/running during the wait (e.g. State Tree tasks that rotate while waiting, animation tasks, any gameplay that shouldn't freeze) |
The pattern for Set Timer by Event requires a Custom Event:
Important: after add_custom_event_node(...), immediately re-read the graph and re-find that callback by the returned node_id. Do not assume the title will be exactly the event name you passed in. Unreal can display custom event titles as multi-line labels such as OnTimerFinished + Custom Event.
Do not silently substitute a Create Event / Create Delegate node plus a separate function graph when the user asked for a Custom Event node or when the target graph should visibly contain the callback in EventGraph. A Create Event delegate can compile and still be the wrong implementation for the requested graph shape.
import unreal
bp_path = "/Game/MyBlueprint"
graph = "EventGraph"
# 1. Add Set Timer by Event node
timer_id = unreal.BlueprintService.add_function_call_node(
bp_path, graph, "KismetSystemLibrary", "K2_SetTimerDelegate", 300, 0)
# 2. Set the Time pin
unreal.BlueprintService.set_node_pin_value(bp_path, graph, timer_id, "Time", "1.0")
# 3. Add a Custom Event node — this is the callback
custom_event_id = unreal.BlueprintService.add_custom_event_node(
bp_path, graph, "OnTimerFinished", 300, 300)
# 4. Connect the Custom Event's delegate output to the timer's Delegate pin
unreal.BlueprintService.connect_nodes(
bp_path, graph, custom_event_id, "OutputDelegate",
timer_id, "Delegate")
# 5. Wire: EnterState → Set Timer by Event
unreal.BlueprintService.connect_nodes(
bp_path, graph, enter_state_id, "then", timer_id, "execute")
# 6. Wire: Custom Event → Finish Task (or whatever follows)
unreal.BlueprintService.connect_nodes(
bp_path, graph, custom_event_id, "then", finish_id, "execute")
For STT_* Blueprint tasks, do not guess event titles or pin names. The stable pattern is:
override_function(bp, "ReceiveLatentEnterState")get_nodes_in_graph(bp, "EventGraph") and locate the event by title Event EnterStateadd_custom_event_node(...), call get_nodes_in_graph() again and re-find the callback by the returned GUIDget_node_pins() on every newly created nodeCurrent UE 5.7 / VibeUE graph details for this workflow:
| Node | Pin to use |
|---|---|
Event EnterState | then |
Custom Event | OutputDelegate, then |
Set Timer by Event | execute, Delegate, Time, bLooping, bMaxOncePerFrame |
Finish Task | execute, bSucceeded |
Success for this workflow also requires the callback node in EventGraph to be a real custom event node, typically K2Node_CustomEvent in a fresh node listing. K2Node_CreateDelegate is a different node type and should not be treated as equivalent when the requested outcome is a visible custom event callback in the graph.
import unreal
bp_path = "/Game/StateTree/STT_Rotate"
graph = "EventGraph"
unreal.BlueprintService.override_function(bp_path, "ReceiveLatentEnterState")
timer_id = unreal.BlueprintService.add_function_call_node(
bp_path, graph, "KismetSystemLibrary", "K2_SetTimerDelegate", 520, 0)
custom_id = unreal.BlueprintService.add_custom_event_node(
bp_path, graph, "OnTimerFinished", 0, 420)
finish_id = unreal.BlueprintService.add_function_call_node(
bp_path, graph, "StateTreeTaskBlueprintBase", "FinishTask", 520, 420)
nodes = unreal.BlueprintService.get_nodes_in_graph(bp_path, graph)
enter_node = next((n for n in nodes if n.node_title == "Event EnterState"), None)
custom_node = next((n for n in nodes if n.node_id == custom_id), None)
assert enter_node, "Event EnterState not found"
assert custom_node, f"Custom event {custom_id} not found after create"
custom_pins = unreal.BlueprintService.get_node_pins(bp_path, graph, custom_node.node_id)
print("CUSTOM EVENT TITLE:", custom_node.node_title)
print("CUSTOM EVENT PINS:", [p.pin_name for p in custom_pins])
unreal.BlueprintService.set_node_pin_value(bp_path, graph, timer_id, "Time", "1.0")
unreal.BlueprintService.set_node_pin_value(bp_path, graph, timer_id, "bLooping", "false")
unreal.BlueprintService.set_node_pin_value(bp_path, graph, finish_id, "bSucceeded", "true")
assert unreal.BlueprintService.connect_nodes(bp_path, graph, enter_node.node_id, "then", timer_id, "execute")
assert unreal.BlueprintService.connect_nodes(bp_path, graph, custom_node.node_id, "OutputDelegate", timer_id, "Delegate")
assert unreal.BlueprintService.connect_nodes(bp_path, graph, custom_node.node_id, "then", finish_id, "execute")
Use add_create_event_node() only when you need a Create Event / Create Delegate node. For Set Timer by Event, a Custom Event node is the simpler callback source and matches the Blueprint editor workflow shown in screenshots.
If a previous attempt already inserted a Create Event node for this timer callback, treat that as the wrong graph shape when the request is for a custom event. Remove the wrong node, remove any stale callback function graph that only existed to support that delegate node, then create the real Custom Event node and verify it by node ID and node type.
After any graph edit, verify all three layers:
get_connections() and confirm the exact expected wiring.get_node_pins() and use the real pin names.compile_blueprint(...).success, num_errors, and errors.For any node you claim you created, also re-read the graph with get_nodes_in_graph() and confirm that node actually exists in the graph after the edit. A returned node ID from a create call is not enough.
For Custom Event timer callbacks, verify both of these before wiring:
get_nodes_in_graph() result.get_node_pins() on that exact node shows the pins you intend to use, typically OutputDelegate and then for the callback path.Also verify that the node type is the expected custom event form rather than K2Node_CreateDelegate.
result = unreal.BlueprintService.compile_blueprint(bp_path)
assert result.success, result.errors
nodes = unreal.BlueprintService.get_nodes_in_graph(bp_path, graph)
for node in nodes:
print(f"NODE {node.node_id} {node.node_title}")
connections = unreal.BlueprintService.get_connections(bp_path, graph)
for conn in connections:
print(f"{conn.source_node_title}.{conn.source_pin_name} -> {conn.target_node_title}.{conn.target_pin_name}")
unreal.EditorAssetLibrary.save_asset(bp_path)
Never print MODIFIED or describe the graph as complete until the expected connections are present and compile succeeds.
Never describe a node as present until it appears in a fresh get_nodes_in_graph() result.
Never treat compile success alone as proof that the graph matches the requested design.
If you edit a graph, your success check must answer all of these from live asset data:
If you cannot answer those three questions from tool output, the task is not complete yet.
For complex graph tasks, also answer these two questions before claiming completion:
Common mistake: Using Delay when the user says "timer". Delay is a latent action that
pauses the execution chain. Set Timer by Event is non-blocking and fires a separate event —
this is critical for State Tree tasks, animation blueprints, and any actor that must keep
ticking during the wait period.
add_member_get_node)Use add_member_get_node to read a property or component that belongs to another class
(not the current Blueprint). This creates a getter node with a Target input pin.
Target (input) — the object reference (e.g. your BP_Cube variable output)
MemberName (output) — the property value (e.g. the CubeMesh component)
# Get the CubeMesh component from a "Cube" variable of type BP_Cube
mesh_id = unreal.BlueprintService.add_member_get_node(
bp_path, graph, "BP_Cube_C", "CubeMesh", 400, 0)
# Connect: Cube (from validated get) -> Target of the member getter
unreal.BlueprintService.connect_nodes(bp_path, graph, val_get_id, "Cube", mesh_id, "self")
# Output pin name matches the member name: "CubeMesh"
unreal.BlueprintService.connect_nodes(bp_path, graph, mesh_id, "CubeMesh", next_id, "Target")
The TargetClass must be the generated class name (BP_Cube_C, not BP_Cube). The function
resolves it via the same 3-step fallback as create_blueprint.
add_validated_get_node)Use add_validated_get_node to create a Validated Get — a variable getter with execution
pins that only continues on the valid path if the object reference is non-null.
Pin names produced:
| Pin | Name | Direction |
|---|---|---|
| Execution in | "execute" | input |
| Is Valid (object non-null) | "then" | output exec |
| Is Not Valid (object null) | "else" | output exec |
| Variable data | variable name e.g. "MyObject" | output data |
import unreal
bp_path = "/Game/BP_MyActor"
graph = "EventGraph"
# Compile first so the variable type is resolved
unreal.BlueprintService.compile_blueprint(bp_path)
# Add BeginPlay + validated get + some function call
begin_id = unreal.BlueprintService.add_event_node(bp_path, graph, "ReceiveBeginPlay", 0, 0)
val_get_id = unreal.BlueprintService.add_validated_get_node(bp_path, graph, "MyObject", 300, 0)
call_id = unreal.BlueprintService.add_function_call_node(bp_path, graph, "MyObject", "SomeFunction", 700, 0)
# Execution flow: BeginPlay -> ValidatedGet (Is Valid path) -> SomeFunction
unreal.BlueprintService.connect_nodes(bp_path, graph, begin_id, "then", val_get_id, "execute")
unreal.BlueprintService.connect_nodes(bp_path, graph, val_get_id, "then", call_id, "execute")
# Data flow: MyObject output -> function Target
unreal.BlueprintService.connect_nodes(bp_path, graph, val_get_id, "MyObject", call_id, "self")
unreal.BlueprintService.compile_blueprint(bp_path)
unreal.EditorAssetLibrary.save_asset(bp_path)
Only Object/Actor reference variables support Validated Get (
ValidatedObjectvariation). Primitive types (int, float, bool) produce a Branch-style impure get instead.
Use list_overridable_functions to discover what can be overridden, then override_function
to create the graph. After that, use get_nodes_in_graph + set_node_pin_value to wire in
return values.
import unreal
bp_path = "/Game/StateTree/STT_Rotate"
# Step 1: See all overridable functions and which are already overridden
funcs = unreal.BlueprintService.list_overridable_functions(bp_path)
for f in funcs:
status = "OVERRIDDEN" if f.already_overridden else "available"
print(f"{f.function_name} ({f.owner_class}) [{status}] -> {f.return_type}")
# ⚠️ UE Python strips `b` prefix: use `already_overridden` and `is_native_event` (not `b_already_overridden`)
# Step 2: Create the override graph (idempotent — safe to call even if already exists)
unreal.BlueprintService.override_function(bp_path, "GetStaticDescription")
# Step 3: Inspect the new graph — find the result node
nodes = unreal.BlueprintService.get_nodes_in_graph(bp_path, "GetStaticDescription")
result_node = next((n for n in nodes if "Result" in n.node_type), None)
# Step 4: Set the return value pin directly (for FText/FString returns use the pin name)
if result_node:
unreal.BlueprintService.set_node_pin_value(bp_path, "GetStaticDescription", result_node.node_id, "ReturnValue", "Rotate Cube")
# Step 5: Compile and save
unreal.BlueprintService.compile_blueprint(bp_path)
unreal.EditorAssetLibrary.save_asset(bp_path)
override_function automatically picks the right approach based on is_event_style:
is_event_style | Mechanism | Where to find the node after |
|---|---|---|
True (void/latent: EnterState, Tick, StateCompleted…) | Event node added to EventGraph | get_nodes_in_graph(bp, "EventGraph") |
False (returns a value: GetDescription…) | Function graph with entry + result | get_nodes_in_graph(bp, function_name) |
Never call add_event_node manually for override functions — override_function handles it.
For event-style overrides, the visible node title is usually the friendly event name Unreal shows in the graph, such as Event EnterState, not the raw function name like ReceiveLatentEnterState.
list_overridable_functions vs list_functions| Method | What it returns |
|---|---|
list_functions | Functions defined in this blueprint (user-created + already overridden) |
list_overridable_functions | Functions from parent class that are override-able, with already_overridden and is_event_style flags |
Always call list_overridable_functions when the user asks "what functions can I override?" or
"override the X function". Never guess function names — they are case-sensitive.
import unreal
bp_path = "/Game/BP_Player"
# Create function with parameters
unreal.BlueprintService.create_function(bp_path, "TakeDamage", is_pure=False)
unreal.BlueprintService.add_function_input(bp_path, "TakeDamage", "Amount", "float", "0.0")
unreal.BlueprintService.add_function_output(bp_path, "TakeDamage", "NewHealth", "float")
unreal.BlueprintService.compile_blueprint(bp_path)
# Add nodes (entry=0, result=1)
get_health = unreal.BlueprintService.add_get_variable_node(bp_path, "TakeDamage", "Health", -400, -100)
subtract = unreal.BlueprintService.add_math_node(bp_path, "TakeDamage", "Subtract", "Float", -200, 0)
set_health = unreal.BlueprintService.add_set_variable_node(bp_path, "TakeDamage", "Health", 200, 0)
# Connect nodes
unreal.BlueprintService.connect_nodes(bp_path, "TakeDamage", 0, "then", set_health, "execute")
unreal.BlueprintService.compile_blueprint(bp_path)
unreal.EditorAssetLibrary.save_asset(bp_path)
Use add_input_action_node() to create an Enhanced Input Action event node in a Blueprint:
import unreal
bp_path = "/Game/BP_ThirdPersonCharacter"
ia_path = "/Game/Input/IA_Ragdoll"
# Create the Enhanced Input Action node
node_id = unreal.BlueprintService.add_input_action_node(bp_path, "EventGraph", ia_path, -800, 2500)
if node_id:
# Connect to other nodes - Output pins are: Started, Ongoing, Triggered, Completed, Canceled
set_physics = unreal.BlueprintService.add_function_call_node(bp_path, "EventGraph", "PrimitiveComponent", "SetSimulatePhysics", -400, 2500)
unreal.BlueprintService.connect_nodes(bp_path, "EventGraph", node_id, "Started", set_physics, "execute")
unreal.BlueprintService.compile_blueprint(bp_path)
unreal.EditorAssetLibrary.save_asset(bp_path)
Important: The Input Action asset must exist first. Create it with InputService.create_action() if needed.
GRID_H = 200 # Horizontal spacing
GRID_V = 150 # Vertical spacing
DATA_ROW = -150 # Data getters above execution
EXEC_ROW = 0 # Main execution row
# Entry (0,0) → Branch (200,0) → SetVar (400,0) → Return (800,0)
# Getters at Y=-150, math at Y=-75, execution at Y=0
get_health = add_get_variable_node(bp_path, func, "Health", 200, -150)
subtract = add_math_node(bp_path, func, "Subtract", "Float", 200, -75)
branch = add_branch_node(bp_path, func, 200, 0)
# True path: Y=0 (same row)
# False path: Y=150 (offset down)
set_armor = add_set_variable_node(bp_path, func, "Armor", 400, 0) # True
set_health = add_set_variable_node(bp_path, func, "Health", 400, 150) # False
Entry and Result nodes are stacked at (0,0) by default:
nodes = unreal.BlueprintService.get_nodes_in_graph(bp_path, func_name)
for node in nodes:
if "FunctionEntry" in node.node_type:
unreal.BlueprintService.set_node_position(bp_path, func_name, node.node_id, 0, 0)
elif "FunctionResult" in node.node_type:
unreal.BlueprintService.set_node_position(bp_path, func_name, node.node_id, 800, 0)
For add_function_call_node(path, graph, class, func, x, y):
Get Actor Location and Set Actor LocationArray operations use wildcard pins — the TargetArray and element pins start with no type until a typed array is connected. The engine automatically propagates the array element type through all wildcard pins when you connect a typed array variable.
Array_Random, NOT Manual Length+Random+GetWhen you need a random element from an array, use Array_Random — a single node that returns
a random item. Do NOT manually build Array_Length → RandomIntegerInRange → Array_Get.
That pattern has 3 nodes, is harder to wire, and Array_Get is deprecated in UE5.
# CORRECT — single node, editor shows it as "Random Array Item"
arr_rand_id = unreal.BlueprintService.add_function_call_node(
bp_path, "EventGraph", "KismetArrayLibrary", "Array_Random", 600, 200)
# Pins: TargetArray (in, wildcard), OutItem (out, wildcard), OutIndex (out, int)
# Or via build_graph:
{"ref": "rand", "type": "spawner_key", "params": {"key": "FUNC KismetArrayLibrary::Array_Random"}}
| Function | Purpose | Key Pins |
|---|---|---|
Array_Length | Get array element count | TargetArray(in), ReturnValue(int) |
Array_LastIndex | Get last valid index | TargetArray(in), ReturnValue(int) |
Array_IsEmpty | Check if empty | TargetArray(in), ReturnValue(bool) |
Array_Random | Get random element ("Random Array Item") | TargetArray(in), OutItem(out), OutIndex(int) |
Array_Add | Add element, returns index | TargetArray(in/out), NewItem(in), ReturnValue(int) |
Array_Remove | Remove by index | TargetArray(in/out), IndexToRemove(int) |
Array_Clear | Remove all elements | TargetArray(in/out) |
Array_Contains | Check if item exists | TargetArray(in), ItemToFind(in), ReturnValue(bool) |
Array_Append | Append another array | TargetArray(in/out), SourceArray(in) |
discover_nodes FirstArray_Get is marked BlueprintInternalUseOnly and is replaced by the dedicated K2Node_GetArrayItem node in modern UE5. Do NOT try to call Array_Get directly.
To get an element from an array by index:
# 1. Discover the correct node key
result = unreal.BlueprintService.discover_nodes(bp_path, "MyFunction", "get array")
# Look for: "Get" with key "NODE K2Node_GetArrayItem"
# 2. Create the node
node_id = unreal.BlueprintService.create_node_by_key(bp_path, "MyFunction", "NODE K2Node_GetArrayItem", 400, 200)
When you connect a typed array to an array function's TargetArray pin, the engine automatically resolves
all wildcard pins on that node to the correct element type. This is handled by UK2Node_CallArrayFunction.
Pattern for array operations in build_graph:
result = unreal.BlueprintService.build_graph(bp_path, graph_name,
nodes=[
# Get the array variable
{"ref": "get_arr", "type": "variable_get", "params": {"variable": "MyTargetPoints"}},
# Get array length
{"ref": "arr_len", "type": "spawner_key", "params": {"key": "FUNC KismetArrayLibrary::Array_Length"}},
# Get random element
{"ref": "arr_rand", "type": "spawner_key", "params": {"key": "FUNC KismetArrayLibrary::Array_Random"}},
],
connections=[
# Connect typed array to wildcard pins — type propagation happens automatically
["get_arr.MyTargetPoints", "arr_len.TargetArray"],
["get_arr.MyTargetPoints", "arr_rand.TargetArray"],
],
auto_layout=True
)
NODE K2Node_GetArrayItem insteadget_node_pins() to verify pin names after node creationbuild_graph)Use build_graph when you need to create 3+ nodes with connections in a single call. It is
significantly faster and less error-prone than creating nodes one-at-a-time for complex graphs.
Use the individual add_*_node + connect_nodes methods when:
discover_nodes first)| Type | Required Params | Description |
|---|---|---|
function_call | class, function | Calls a UFunction (e.g. KismetSystemLibrary::PrintString) |
spawner_key | key | Creates node from a spawner key (FUNC, EVENT, NODE prefix) |
variable_get | variable | Gets a Blueprint variable |
variable_set | variable | Sets a Blueprint variable |
event | event | Overridable event (e.g. ReceiveBeginPlay) |
custom_event | name | Custom event node |
branch | (none) | If/Then/Else branch |
cast | target_class | Dynamic cast |
print_string | (none) | PrintString shorthand |
input_action | action | Enhanced Input Action (asset path) |
math | operation, operand_type | Math op (Add/Subtract/Multiply/Divide/Clamp/Abs) |
comparison | operation, operand_type | Comparison (Greater/Less/Equal/NotEqual/GreaterEqual/LessEqual) |
delegate_bind | delegate, (optional: component) | Bind a multicast delegate |
create_event | function | Create Event node |
validated_get | variable | Validated Get (with exec pins) |
member_get | member, class | Get member from another class |
create_delegate | function | Create Delegate node |
make_struct | struct | Make Struct node (K2Node_MakeStruct) for any struct type (engine or user-defined) |
instanced_struct | struct | Make Instanced Struct node — wraps a struct into FInstancedStruct |
Connections use "RefOrGUID.PinName" format: {"from_": "A.then", "to": "B.execute"}
⚠️ Key name is from_ (with underscore) because from is a Python reserved keyword.
UE Python maps the C++ UPROPERTY From to from_.
The ref part can be either:
Nodes array (e.g. "MkInst")get_nodes_in_graph())This allows build_graph to wire new nodes to existing nodes in a single call:
# Mix local refs with existing GUIDs
existing_id = "EB9221E84DFFF609028F9DAA6267B654" # from get_nodes_in_graph()
connections = [
{"from_": f"{existing_id}.OutputPin", "to": "NewNode.InputPin"}, # existing → new
{"from_": "NewNode.OutputPin", "to": "OtherNew.InputPin"}, # new → new
]
Pin aliases supported:
execute / exec → first exec input pinthen / output → first exec output pinvalue / result → first non-exec output pinTrue → then, False → else (Branch node)import unreal
bp_path = "/Game/BP_MyActor"
result = unreal.BlueprintService.build_graph(
bp_path,
"EventGraph",
# Nodes
[
{"ref": "BP", "type": "event", "params": {"event": "ReceiveBeginPlay"}},
{"ref": "Print", "type": "print_string", "params": {}},
],
# Connections
[
{"from_": "BP.then", "to": "Print.execute"},
],
# Pin defaults
[
{"node_ref": "Print", "pin_name": "InString", "value": "Hello World!"},
],
True, # auto-layout
True # compile after
)
print(f"Success: {result.b_success}")
print(f"Nodes: {result.nodes_created}/{result.nodes_created + result.nodes_failed}")
print(f"Connections: {result.connections_made}/{result.connections_made + result.connections_failed}")
if result.errors:
for e in result.errors:
print(f" ERROR: {e}")
result = unreal.BlueprintService.build_graph(
bp_path,
"EventGraph",
[
{"ref": "BP", "type": "event", "params": {"event": "ReceiveBeginPlay"}},
{"ref": "GetHP", "type": "variable_get", "params": {"variable": "Health"}},
{"ref": "Cmp", "type": "comparison", "params": {"operation": "Less", "operand_type": "Double"}},
{"ref": "Branch", "type": "branch", "params": {}},
{"ref": "PrintLow", "type": "print_string", "params": {}},
{"ref": "PrintOK", "type": "print_string", "params": {}},
],
[
{"from_": "BP.then", "to": "Branch.execute"},
{"from_": "GetHP.Health", "to": "Cmp.A"},
{"from_": "Cmp.ReturnValue", "to": "Branch.Condition"},
{"from_": "Branch.then", "to": "PrintLow.execute"},
{"from_": "Branch.else", "to": "PrintOK.execute"},
],
[
{"node_ref": "Cmp", "pin_name": "B", "value": "25.0"},
{"node_ref": "PrintLow", "pin_name": "InString", "value": "Health is low!"},
{"node_ref": "PrintOK", "pin_name": "InString", "value": "Health OK"},
],
True, True
)
When a StateTree task Blueprint has a StateTreeDelegate variable (e.g. FinishRotatingDispatcher) and the user wants the custom event timer callback to broadcast that delegate instead of calling Finish Task, the graph must:
CustomEvent.then → Finish Task.executeFinish Task nodeBroadcast Delegate spawner key (search "Broadcast")Broadcast Delegate node + a variable GET for the delegateCustomEvent.then → Broadcast.execute and GET.FinishRotatingDispatcher → Broadcast.Dispatcherimport unreal
bp_path = "/Game/StateTree/Tasks/STT_Rotate"
graph = "EventGraph"
# Step 1: Find node GUIDs
nodes = unreal.BlueprintService.get_nodes_in_graph(bp_path, graph)
custom_node = next((n for n in nodes if "CustomEvent" in n.node_type or "Custom Event" in n.node_title), None)
finish_node = next((n for n in nodes if "Finish Task" in n.node_title), None)
assert custom_node and finish_node, "Nodes not found"
# Step 2: Disconnect CustomEvent.then (correct: 4 args)
unreal.BlueprintService.disconnect_pin(bp_path, graph, custom_node.node_id, "then")
# Step 3: Delete Finish Task
unreal.BlueprintService.delete_node(bp_path, graph, finish_node.node_id)
# Step 4: Discover Broadcast Delegate node — search "Broadcast" NOT "Dispatcher"
matches = unreal.BlueprintService.discover_nodes(bp_path, "Broadcast", "", 10)
for m in matches:
print(m.display_name, m.spawner_key) # display_name, NOT node_title
broadcast_node = next((m for m in matches if "Broadcast" in m.display_name), None)
assert broadcast_node, "Broadcast Delegate node not found"
# Step 5: Build replacement — Broadcast + GET variable
result = unreal.BlueprintService.build_graph(
bp_path, graph,
[
{"ref": "Broadcast", "type": "spawner_key", "params": {"key": broadcast_node.spawner_key}},
{"ref": "GetDisp", "type": "variable_get", "params": {"variable": "FinishRotatingDispatcher"}},
],
[
{"from_": f"{custom_node.node_id}.then", "to": "Broadcast.execute"},
{"from_": "GetDisp.FinishRotatingDispatcher", "to": "Broadcast.Dispatcher"},
],
[], True, True
)
print(f"Success: {result.b_success}, errors: {result.errors}")
Target (self) on the Broadcast node connects to self by default when the Blueprint is the correct class (StateTreeTaskBlueprintBase). You do not need to wire it manually.
Use get_graph_definition to capture an existing graph, modify the definition, then
rebuild with build_graph:
import unreal
bp_path = "/Game/BP_MyActor"
graph = "EventGraph"
# Export current graph
nodes, connections, defaults, error = unreal.BlueprintService.get_graph_definition(
bp_path, graph)
# Inspect exported nodes
for n in nodes:
print(f" {n.ref}: {n.type} {n.params}")
# Modify (add a new node, change a connection, etc.)
# Then rebuild in a different graph or blueprint
auto_layout_graph)auto_layout_graph arranges all nodes in a graph using a simplified Sugiyama algorithm:
unreal.BlueprintService.auto_layout_graph(bp_path, "EventGraph")
Use make_struct to create a K2Node_MakeStruct for any struct type (engine or user-defined).
Use instanced_struct to wrap a struct into FInstancedStruct — required when a pin expects FInstancedStruct (e.g. Make State Tree Event.Payload).
⚠️ The Value pin on Make Instanced Struct is a wildcard. It resolves to the struct's fields only after the struct type is set via struct param. Do not try to split or connect it before the node type is resolved — connecting by field name (e.g. Value.TargetPawn) will fail.
Correct pattern — build_graph example:
result = unreal.BlueprintService.build_graph(
bp_path,
"EventGraph",
[
# 1. Make the struct with its fields populated
{"ref": "MkPayload", "type": "make_struct", "params": {"struct": "FStartChasingPayload"}},
# 2. Wrap it in FInstancedStruct
{"ref": "MkInst", "type": "instanced_struct", "params": {"struct": "FStartChasingPayload"}},
],
[
# Wire the struct output into the instanced struct Value pin
{"from_": "MkPayload.StartChasingPayload", "to": "MkInst.Value"},
# Wire MkInst output into whatever consumes FInstancedStruct
{"from_": "MkInst.ReturnValue", "to": "MkState.Payload"},
],
[],
True, True
)
Pin names for make_struct: The output pin name matches the struct type name exactly as Unreal exposes it (e.g. StartChasingPayload for FStartChasingPayload). If uncertain, create the node, then call get_node_pins() to read the actual output pin name before connecting.
Pin names for instanced_struct: Input is Value, output is ReturnValue.
build_graph returns FBuildGraphResult with detailed audit:
b_success — True only if zero node failures AND zero compile errorsnodes_created / nodes_failed — individual node creation resultsconnections_made / connections_failed — wiring resultsdefaults_set / defaults_failed — pin default resultsref_to_node_id — maps your local refs to engine GUIDserrors — critical failures (node not found, class not found, compile errors)warnings — non-fatal issues (pin mismatch, connection rejected)Always check result.errors and result.warnings after a build_graph call.