Calling MifBridge
The mistakes people and agents make most when they call MifBridge: parameter names, pin names, the type field, numbers and what an answer really means. Each entry says what you see, why, and what to do.
For the full calling conventions see MifBridge. Mistakes specific to Blueprint graphs are on mistakes-blueprints.
Ask for the shape, do not guess it
A guessed parameter name is the most common cause of a refused or ignored call. describe_endpoint returns an endpoint's parameters, and a refusal that lists "unknown parameters" is telling you the name you sent is not one of them. Make a small real call and read the answer before you build a long sequence on a guess.
Identifying a node
What you see: get_node works, but move_node with the same field says the node is missing.
Why: endpoints that address one node accept any of nodeGuid, node, guid and nodeId. That covers get_node, move_node, remove_node, refresh_node, disconnect_pin, set_pin_default, set_pin_type and remove_pin. Endpoints that take two or more nodes keep distinct names on purpose, so one node cannot fill two roles:
| Endpoint | Parameters |
|---|---|
connect_pins, reconnect_pin |
srcNode, dstNode |
splice_into_exec |
afterNode, insertNode |
recipe_splice_before_parent |
parentNode, clusterEntry, clusterExit |
recipe_argmax_over_components |
loopBodyNode, scoreNode, indexNode |
GUID format is not the problem. Both the dashed 36-character form and the undashed 32-character form are accepted everywhere. The bridge itself always returns the undashed form. If one call seems to want dashes and another does not, the real difference is the field name.
Naming a placed actor
What you see: a call that takes an actor answers ok:false and lists several candidates.
Why: an actor reference resolves in this order:
- an exact object path (the
actorPathfromlist_level_actors) - an object name that exactly one actor carries
- a label (the Outliner name) that exactly one actor carries
A name or label that two or more actors carry resolves to nothing, and the refusal names every candidate's path and name. Labels are display text and the editor does not keep them unique. Object names are unique per level, not per world, so two streamed sublevels can each hold a StaticMeshActor_0.
Do instead: pass the path. A name beats a label if one actor's name matches another actor's label.
For calls that take a list of actors (group_actors, ungroup_actors, duplicate_actors, snap_actors_to_ground and similar), an ambiguous entry refuses the whole call. An entry that names no actor at all stays a notFound row, and the rest go ahead. An actor that sits in an unloaded World Partition cell is not in the world, so it is not found.
Class parameters are required
What you see: add_cast answers that the class is missing, or a node comes out typed to the Blueprint itself.
Why: an empty class name means "this Blueprint's own class". That is deliberate for add_function_call, where an empty class means "call on self". Everywhere a class is mandatory, an empty value is refused. Alternate spellings are accepted:
| Endpoint | Primary | Also accepted |
|---|---|---|
add_cast, add_class_cast |
targetClass |
class, castTo, to, targetType |
add_spawn_actor |
actorClass |
class |
add_create_widget |
widgetClass |
class |
add_tree_widget |
widgetClass |
class |
add_get_subsystem |
subsystemClass |
class |
add_component |
componentClass |
class |
add_interface, remove_interface |
interface |
interfaceClass, class |
add_variable_get, add_variable_set |
targetClass |
class, ownerClass |
Pin names: the label on screen is not the name
What you see: you copy a pin name from a screenshot of the graph, the call succeeds, the graph looks built, and nothing runs.
Why: the editor draws pin names with spaces inserted before capital letters. A ForEach loop shows Loop Body, Array Element, Array Index and Completed. The real names are LoopBody, Array Element, Array Index and Completed. Three of the four are the same either way. LoopBody is one word, and it is the exec pin that carries the loop body, so the spaced spelling looks like the pattern and is wrong in the one place where it breaks the graph quietly.
Do instead: ask the node. get_node returns every pin with its exact name. Nodes you create with add_variable_set and the recipes return their pins in the answer.
A wire that does not connect may not fail the call. The recipes report each edge separately: recipe_reset_and_loop answers arrayWired and arrayWireError, and recipe_argmax_over_components answers execWired and execWireError. ok:true from a composition means the nodes were made. Read the edge fields and warnings before you believe the graph does anything.
Output pin aliases
Nodes disagree about what to call their single value output. When an exact name misses, the lookup retries inside one alias group and accepts the hit only if exactly one pin matches, so a node that really has the pin you named is never redirected:
| Group |
|---|
ReturnValue, Result, Output, OutputPin |
Array, OutArray |
Out Row, OutRow, Row |
Known surprises: add_format_text outputs Result, not ReturnValue. add_get_data_table_row outputs Out Row, with a space. add_cast outputs As<ClassName>.
The type grammar
The type field is used by add_variable, the pins of create_function, add_pin and set_pin_type.
Scalars: bool, byte, int, int64, float, double, real, string, name, text.
float is 32 bits, double is 64
| Spelling | Result |
|---|---|
float, float32, single |
32-bit float |
double, float64, real |
64-bit double |
Float and double pins still connect to each other through the schema's automatic conversion. The distinction matters where the engine insists on a float, for example the PercentDelegate and OpacityDelegate bindings on UMG widgets, which need a function that returns a float. If you have graphs built when float made a double, change those to double.
References need a prefix
Struct, enum and class names may be given bare. A reference to an object or class does not resolve without its prefix:
object:<ClassOrPath> class:<C> (alias subclassof:<C>)
softobject:<C> softclass:<C>
interface:<C> enum:<E>
Paths work: object:/Game/BP/BP_Foo.BP_Foo_C. Containers go in a separate container field with the value array, set or map.
Maps take two types
For container: "map", the type field is the key and a separate valueType field is the value:
{ "name": "ItemCounts", "type": "name", "container": "map", "valueType": "int" }
Read-back shows the value type as a nested valueType object. A map value cannot itself be a container, because Blueprint has no map of arrays. Wrap the array in a struct.
Numbers and booleans are strict
What you see: ok:false naming a field path such as location.x, the value you sent and the type it expected.
Why: a value of the wrong JSON type is an error. It is never quietly replaced by a default. Omitting a field still means "leave it alone", but supplying one the bridge cannot read stops the call. This is enforced for every numeric and boolean parameter on every endpoint (limit, radius, fov, count, confirm), not only transforms. When it trips, the answer carries ignoredParameters.
Rules worth knowing:
- Vectors take
{x,y,z}or[x,y,z]everywhere. Rotators also take{pitch,yaw,roll}, andx/y/zthen means pitch, yaw, roll.scalealso takes a bare number for uniform scale. - An unrecognized key inside a vector object is refused:
{"x":1,"y":2,"zz":3}is a typo, not a 2D vector. - A string that is wholly numeric (
"12.5") is accepted. A partly numeric one ("12abc") is refused, because the engine's parsers would take the12and drop the rest without saying so.
Values are checked against the property type
set_property, override_inherited_component and the calls like them check the value against the property's type before it is imported. The engine's own importers turn garbage into a default and report success, and a read-back then compares that default with itself and passes. These are refused up front, with the reason and the accepted form:
| Kind | What is refused |
|---|---|
| Numeric | Anything that does not parse whole: "12abc", "not-a-float", and exponent form such as "1e5", which the engine would store as 1 |
| Bool | Any word that is not True, False, Yes, No, On, Off, 1 or 0. An unrecognized word used to import as false |
| Enum | A name that is not a real entry. The valid entries are listed in the error. A wrong name used to import as the first entry |
| Object or class reference | A path that does not resolve. Soft references are exempt, since they can name unloaded assets |
Structs and containers sent as export text cannot be checked in advance. The answer says so with typeValidated:false and a typeValidationNote. Send them as typed JSON and every leaf is checked on its own.
Arrays count too
Sending a string where an array is expected (select_level_actors with "actorPaths": "/Game/Foo") is refused like any other wrong type. A request that does nothing and reports success is the thing this rule exists to prevent.
Take an enum by the name you can see
What you see: the value the Details panel shows (UserInterface2D (RGBA)) is refused, or works on one engine and not the next.
Why: a UI label is not a stable identifier. The authored name (TC_EditorIcon) is what the engine stores, the panel shows a display name, and Epic changes display names between versions. MifBridge accepts the authored name, and for the enums its own help recommends it also accepts the display spelling with spaces and a trailing parenthetical ignored. For a user-defined enum the authored entries are NewEnumeratorN and the name you chose lives only in the display name.
Do instead: read the valid entries from the refusal, which lists them, or from list_enum_values.
Reading a response
ok:true from a rename that renamed something else
Some engine calls take a name and hand back a different one when it is taken, and report success. add_component with Turret produces Turret1 if Turret exists, and a setup script replayed twice grows Turret, Turret1, Turret2. Whenever you name something, compare the name in the answer with the name you sent. add_variable, create_function, add_event_dispatcher and add_component refuse a taken name instead. If you omit the name, the engine picks one and numbers it.
A zoom parameter that "does nothing"
orbitZoom on thumbnail and capture calls is a distance offset in world units added to the camera distance, not a zoom factor. On a mesh framed from hundreds of units away, orbitZoom: 2 moves the camera by almost nothing and the image comes back byte-identical, while orbitPitch: 60 visibly changes it, because pitch and yaw are angles. If a zoom seems to do nothing, raise the magnitude by three orders of magnitude before you conclude it is ignored.
Never silence a mutating call
delete_level_actor requires confirm: true and answers {"ok":false,"error":"delete_level_actor requires confirm=true"} without it. A script that sends its output to /dev/null never sees that, and the actors it meant to clear accumulate. A shell && echo removed prints regardless, because curl exits 0 on any HTTP response, including a JSON body that reports failure.
Rule: always read ok from the answer of a call that changes state. Never treat a transport exit code as the operation's result.
A timed-out call may have run
See mistakes-editor-hangs. A timeout on a write does not mean the write did not happen.
Properties that look settable and are not
Class defaults and component defaults
set_property, get_property and list_object_properties take any objectPath and walk a dotted propertyPath from it. Two routes that are easy to miss:
Class defaults. Every class has a default object named
Default__<ClassName>. Pointset_propertyat it to edit what the Class Defaults panel shows:{ "objectPath": "/Game/BP/BP_Vehicle.Default__BP_Vehicle_C", "propertyPath": "bReplicates", "value": "True" }Component defaults. A Blueprint component's editable template is named
<ComponentName>_GEN_VARIABLEand sits in the generated class, separated by a colon:{ "objectPath": "/Game/BP/BP_Vehicle.BP_Vehicle_C:Mesh_GEN_VARIABLE", "propertyPath": "StaticMesh", "value": "/Game/Meshes/SM_Body.SM_Body" }list_componentsreports the name. Append the suffix.Node settings. Every node returned by
list_nodescarries anobjectPath. Feed it toset_propertyto change Details-panel-only settings such as a transition'sCrossfadeDuration. If the change affects the node's pins, callrefresh_nodeafterward.
Editing a placed actor's component reruns its construction scripts
set_property on a component of a placed actor makes the engine rerun the actor's construction scripts, which destroys the component and builds a replacement. The bridge finds the new component and verifies against it, and reports reconstructed, retargetedTo and verifiedOn. If it cannot find it, the call fails as unverified.
A property the construction rerun cannot carry over (transient, not editable per instance, a multicast delegate, or written by the construction script itself) will honestly fail. Edit the component template (..._GEN_VARIABLE) instead. That also applies the change to every instance.
Prefer the endpoint to the raw property
When an endpoint exists for a structure, use it instead of set_property on the structure's members. The endpoint follows what the engine actually reads, and reflection only follows what the struct happens to declare. The clearest case is the IK Rig retarget root: on UE 5.8 the engine split that field in two, and a set_property write to the old one answers ok:true, verified:true while the rig does not retarget. set_ik_rig_retarget_root is correct on every engine. See mistakes-engine-versions.
Where to go next
- mistakes-blueprints for variables, functions, pins and structs
- mistakes-losing-work for what
ok:falsedoes not promise - mistakes-editor-hangs for a bridge that stops answering