Structure / ExtensionObject
In OPC UA, a Structure is a DataType with named fields, and an ExtensionObject is the wire container that carries one: an encoding NodeId plus the encoded body. Any value whose type the client cannot resolve arrives as an opaque ExtensionObject, which is what a failed custom-struct decode actually looks like.
Structure is the abstract root
Structure at i=22 is an abstract DataType. Every structured value in OPC UA derives from it - the standard ones such as Range, EUInformation, Argument and BuildInfo, and every structure a companion specification or a vendor defines. A Variable whose DataType attribute names a subtype of Structure carries its value inside a Variant as an ExtensionObject; there is no other way to move a structured value over the wire.
The three parts of an ExtensionObject
An ExtensionObject is a TypeId, an encoding byte and a body. The TypeId is the NodeId of the encoding node, not of the DataType node - the Default Binary, Default XML or Default JSON child reached from the DataType by a HasEncoding reference. The encoding byte says whether the body is absent, a ByteString of UA Binary, or an XmlElement. A decoder therefore keys its lookup table on encoding NodeIds, which is why a client that resolved the DataType but never followed HasEncoding still cannot decode anything.
Nested and array structures
Fields may themselves be structures, arrays of structures, or fields declared with an abstract DataType so that any subtype may appear. The last case is the expensive one: the decoder has to read the nested ExtensionObject's own TypeId before it knows what it is holding, and code generators either handle it explicitly or fall back to leaving the field as a raw ExtensionObject. UA-ModelCompiler's -useAllowSubtypes switch exists for exactly this.
What decoding failure looks like
Failure is rarely loud. A client with no type information usually returns the value as a byte blob with a TypeId and Good status, so the read succeeds and the data is useless - the umati dashboard client documents custom DataTypes as only partially working for exactly this reason. Where the server can tell, it answers BadDataEncodingUnsupported for an encoding it cannot produce or BadStructureMissing when the structure definition is unavailable; a truncated or mismatched body surfaces as BadDecodingError.
Example
One structured value on the wire, and the nodes a decoder must resolve first
Nodes
Variable ns=6;s=Line3.MC01.CurrentStep
DataType -> ns=1;i=3002 (RecipeStep, subtype of Structure i=22)
DataType ns=1;i=3002
--HasEncoding--> ns=1;i=5002 BrowseName 0:"Default Binary"
--HasEncoding--> ns=1;i=5003 BrowseName 0:"Default XML"
DataTypeDefinition -> StructureDefinition { StepNumber, Setpoint, Comment? }
What arrives in the DataValue
Variant
ExtensionObject
TypeId ns=1;i=5002 <- the ENCODING node, not i=3002
Encoding 1 <- body is a ByteString of UA Binary
Body 01 00 00 00 00 00 00 00 00 00 45 40 01 ...
Decoded, once the definition is known
StepNumber = 1
Setpoint = 42.0
Comment = present (optional-field mask bit 0 set)
Without the definition, a client returns the Body bytes and Good.
Defined in: Part 3 - Address Space Model for Structure DataTypes and encoding nodes; Part 6 - Mappings for the ExtensionObject encoding.
Last verified 7 September 2026 against OPC UA 1.05.07. OPC UA Connect is an educational resource and is not affiliated with the OPC Foundation.