Resolving Ignition UDT Instances With Unresolved OPC Paths

Mark Townsend8 min read
HMI / SCADAOther ManufacturerTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Recognize the Fault on the Instance

Here is what you see. The UDT definition looks correct. The member tags show OPC Item Paths with a {TagNum} parameter in them. You create an instance, set the parameter, and every member comes up bad or empty.

  • Exporting the definition gives a fully populated JSON: every member has opcItemPath, opcServer, history, and alarm config.
  • Exporting the instance gives almost nothing: names and parameter values only.
  • The members start working only if you override each one on the instance and re-enter the same OPC Item Path. Every member then shows as overridden.
  • Direct-linking a member to the PLC tag works. Putting the {TagNum} substitution back breaks it.

A near-empty instance export is normal. Instances export only overrides and parameter values, and they inherit everything else. That is not the fault. The fault is in what the definition hands down.

Work the checks below in order. Check 1 settles most cases in under a minute.

Check 1: Read the Resolved OPC Item Path on One Instance Member

Start here. In the Designer Tag Browser, expand the instance and open one member, for example Sts_Running. Read its OPC Item Path in the tag editor or the diagnostics.

What the instance member shows Meaning Next check
ns=1;s=[MCP]EQ_P{TagNum}.Sts_Running with the braces still in it The parameter was never substituted. The definition stores the path as a static string. Check 2
(resolved), but quality is bad Substitution works. The node address or device is wrong. Check 3, then browse the OPC server for the real node
ns=1;s=[MCP]EQ_P.Sts_Running or a malformed number The binding exists, but the parameter value is empty or has the wrong type. Check 3

The rule: an instance member's OPC Item Path must never contain { }. It must be a fully resolved path. If braces show up, stop looking at the PLC, the OPC server, or the device connection. They are fine.

Check 2: Open the Definition JSON and Find the Binding Object

Export the UDT definition and look at how opcItemPath is stored. There are two forms, and they behave very differently.

Broken form: static string.

"opcItemPath": "ns\u003d1;s\u003d[MCP]EQ_P{TagNum}.Sts_Running"

Working form: parameter binding.

"opcItemPath": {
  "bindType": "parameter",
  "binding": "ns\u003d1;s\u003d[MCP]EQ_P{TagNum}.Sts_Running"
}

\u003d is just the JSON escape for =. Ignore it.

Mechanism. Ignition resolves {Param} references only in properties that carry a binding. A plain string is a literal. The instance inherits the literal EQ_P{TagNum}, sends it to the OPC server, and the server finds no such node.

Why Ignition doesn't flag or auto-convert it: braces are legal characters in an OPC UA string Node ID. Ignition's own drivers don't use them, but a third-party OPC server could expect a literal {. So the tag system cannot assume braces mean "parameter."

How the static strings get there:

  • Find/replace in a text editor, swapping 101 for {TagNum} in exported JSON, then pasting or importing it back.
  • Dragging OPC items into the UDT from the OPC browser. Dropped members get hard-coded paths, not bindings.
  • Typing {TagNum} into the path field without switching the property to a binding.

If every opcItemPath in the definition is a bare string, you have found the root cause. Go to the fix sections. Run Checks 3 and 4 anyway before redeploying. They catch the secondary faults that surface once paths resolve.

Check 3: Clear Parameter Type Overrides on the Instance

Compare the parameter's data type in the definition with the instance. In this case the definition declares TagNum as Integer with a default of 0. The instance had been changed to String.

A type override on the instance makes the resolved path unpredictable, and it hides whether the binding is working. Remove it:

  1. Open the instance in the tag editor and go to Parameters.
  2. Click the green override indicator next to the parameter to remove the override. It reverts to the definition's type.
  3. Set a valid value for that type, for example 101 for an Integer.
  4. Apply and re-read the member's OPC Item Path, as in Check 1.

Pick one naming convention for each UDT and keep it:

Parameter Type Value Binding pattern Resolves to
TagNum Integer 101 [MCP]EQ_P{TagNum}.Sts_Running
TagName String P_101 [MCP]EQ_{TagName}.Sts_Running

Check both rows against the actual PLC tag names. If the PLC tag is , the string approach needs P101 as the value, not P_101. Mixing conventions gives a path that resolves cleanly and still points at a node that doesn't exist.

Changing the type alone does not fix static paths. With static strings, neither the Integer approach nor the full-name String approach works. Changing parameter types while the paths are still literal strings wastes your time.

Check 4: Audit Every Other Property That Contains Braces

The same trap applies to any property that references a parameter or a sibling tag. In the definition from this case, the two alarms were configured differently:

Member Property Stored as Result at runtime
Alm_FailToStart label {"bindType": "Expression", "value": "{[.]Cfg_Label}+..."} Evaluates: shows the motor label plus "Fail to Start Alarm"
Alm_FailToStop label Plain string "{[.]Cfg_Label}+\" Fail to Stop Alarm\"" Displays the expression text literally
Both alarms CustomEmailMessage Plain string Email body contains the raw expression text

Note the difference in binding type:

  • {TagNum} is a parameter reference. It needs "bindType": "parameter".
  • {[.]Cfg_Label}+" ..." is an expression that reads a sibling tag. It needs "bindType": "Expression". A parameter binding will not evaluate the + concatenation.

Fix the alarm label and email message on Alm_FailToStop the same way Alm_FailToStart's label is built. Then check the custom email message property on both alarms.

Fix It in Designer: Bind the Path Segment

Use this for a handful of members, or when you are building the UDT from OPC-browsed items.

  1. Drag the OPC items for one device into the UDT definition. They arrive with hard-coded paths, for example .
  2. Create the UDT parameter, for example TagNum (Integer) or TagName (String).
  3. Open the first member and go to its OPC Item Path.
  4. Click the binding (chain-link) icon to switch the property to a parameter binding.
  5. In the binding text, highlight only the device-specific part (101 or P101). Double-clicking usually selects the tag identifier.
  6. Insert the parameter reference so the text reads ns=1;s=[MCP]EQ_P{TagNum}.Sts_Running. Press Enter to commit.
  7. Repeat for every member. Don't skip the _Runtime members (Val_Starts, Val_DayStarts, Val_MaxRunHrs, Val_PrevStarts, Val_PrevRunHrs, Val_CurRunHrs, Val_DayRunHrs). They live under a different PLC tag, EQ_P{TagNum}_Runtime.
  8. Save the definition. Existing instances pick up the change unless they carry overrides.

Then remove the workaround overrides from each instance. Click the green override indicator on each member's OPC Item Path so the member inherits the new binding again. If you leave the overrides, the instance stays hard-coded and ignores future definition edits.

Fix It in Bulk: Convert the JSON With a Script

For large UDTs, or if you build definitions by find/replace in exported JSON, convert the strings to binding objects before importing. This script turns every opcItemPath string that contains a {Param} reference into a parameter binding. It skips sibling-tag references like {[.]Cfg_Label} and lists every other brace-containing string for manual review.


  1. Export the UDT definition to a file.
  2. Run python convert_udt.py MotorStarter.json MotorStarter_bound.json.
  3. Work the REVIEW lines by hand. Alarm label and CustomEmailMessage strings that concatenate {[.]Cfg_Label} need "bindType": "Expression", not a parameter binding.
  4. Import the converted file back over the definition. Set the import's collision handling to overwrite the existing type.
  5. Keep the converted file as your template. Every future device type then starts from bound paths.

Add other property names to only if you parameterize them, for example opcServer.

Verify the Instance

  1. Open one member on each instance. The OPC Item Path must show a resolved path with no braces, for example .
  2. Confirm the members show good quality and live values. Toggle the motor, or watch Sts_Running/Sts_Stopped change.
  3. Export the instance again. It should still be sparse: parameter values only, and no opcItemPath overrides. A sparse, clean export plus live values means inheritance is working.
  4. Check that no override indicators remain on member OPC Item Paths.
  5. Force or simulate a fail-to-start and a fail-to-stop. Confirm both alarm labels read as the motor's Cfg_Label plus the message text, not the raw expression.
  6. Create a second instance with a different TagNum. It must resolve to its own PLC tags with no per-member edits. If it does, the definition is correct.

FAQ

How do I tell if my Ignition UDT parameter is actually being substituted?

Open a member tag on the instance and read its OPC Item Path. If it still shows {TagNum} with the braces, the definition stores the path as a static string, not a parameter binding. A working member shows a fully resolved path such as .

How do I convert a static OPC Item Path to a parameter binding in a UDT?

In the definition, open the member's OPC Item Path and click the chain-link binding icon. Replace the device-specific segment with {TagNum} and press Enter. In JSON, the property must be an object with "bindType": "parameter" and a "binding" string, not a bare string.

Why does my UDT instance only work when I override every tag?

The override replaces the literal {TagNum} path inherited from the definition with a hard-coded valid path. Bind the definition's paths to the parameter instead, then click the green override indicators on the instance to remove the overrides. Members will then inherit the resolved paths.

How do I fix a UDT alarm label that shows the expression text instead of the value?

The label is stored as a plain string. Change it to an Expression binding with the value {[.]Cfg_Label}+" Fail to Stop Alarm", matching how the working alarm's label is built. Apply the same fix to any custom email message property that uses the same text.

How do I know when to stop troubleshooting and contact support?

Escalate if the definition shows "bindType": "parameter" on opcItemPath, the instance has no overrides or type mismatches, and the instance member still shows unresolved braces. Send Inductive Automation support the exported definition and instance JSON along with your gateway version. Include one direct-linked member that works for comparison.

Back to blog