Skip to main content

VariableTypes

In OPC UA, VariableTypes serve as reusable blueprints for variables. They define the data type, value rank, default value, and nested metadata properties that any instance of this variable type must carry.

This guide explains how to define custom VariableTypes, instantiate them within ObjectTypes, and configure optional properties using qualified-name path segment rules.


1. Defining a Variable Type

Custom VariableTypes are declared under the top-level variableTypes section in your YAML model.

Defining MyVariableType
variableTypes:
- browseName: MyVariableType
dataType: ua:Double
value: 0

At minimum, a variable type declaration requires:

  • browseName: The unique identifier of your variable type (by convention, suffixed with Type).
  • dataType: The underlying OPC UA data type (e.g. ua:Double, ua:String, or a custom DataType).
  • value (optional): A default initial value for instances of this type.

Variable Attributes & Metadata

You can further customize a VariableType (or individual variable components) using standard OPC UA attributes:

Configuring variable attributes
variableTypes:
- browseName: MySensorValueType
dataType: ua:Double
valueRank: 1 # Array of any size
accessLevel: CurrentRead | CurrentWrite | HistoryRead
minimumSamplingInterval: 100 # Minimum sampling interval in milliseconds

Here is a breakdown of the supported attributes:

  • valueRank: Determines if the variable holds a single value, an array, or a multi-dimensional matrix:

    • -1 (Scalar): Single value (default if omitted).
    • 1 (OneDimension): A one-dimensional array of any size.
    • 2 (TwoDimensions): A two-dimensional matrix.
    • 0 (OneOrMoreDimensions) / -2 (Any): Flexible array dimensions.
  • arrayDimensions: A list of integers specifying the exact size of each dimension (mandatory for multi-dimensional matrices, e.g. [2, 3] for a 2x3 matrix).

  • accessLevel: A bitwise flag string that defines client permissions. Multiple flags are combined with |:

    • CurrentRead: Clients can read the current value.
    • CurrentWrite: Clients can write the current value.
    • HistoryRead: Access to historical data.
    • HistoryWrite: Ability to modify historical data.
    • StatusWrite: Ability to write status codes.
  • minimumSamplingInterval: Specifies the fastest rate (in milliseconds) at which the server should sample this variable's value.


2. Instantiating a Variable Type

Once a VariableType is defined, you can instantiate it as a component or property inside an ObjectType declaration by referencing its name in the typeDefinition property.

Instantiating inside an ObjectType
objectTypes:
- browseName: MyObjectType
properties:
- browseName: MyVariable
typeDefinition: MyVariableType
modellingRule: Mandatory # or Optional
Automatic Inference

The OPC UA Modeler automatically infers that MyVariable is a Variable node because its typeDefinition points to a registered VariableType.


3. Optional Properties & Exposing Optionals

VariableTypes can have their own nested metadata (like engineering units, ranges, or custom properties).

Any property or component declared on a type with modellingRule: Optional is not instantiated by default. An instantiating node must explicitly opt-in to inherit it by listing the target property in its optionals: list.

Exposing and instantiating optionals
- browseName: MyOptionalVariableType
dataType: ua:Double
value: 0
properties:
- browseName: Units
dataType: ua:String
modellingRule: Optional

instances:
- browseName: MyObject
typeDefinition: ua:BaseObjectType
organizedBy: /ua:Objects
components:
- browseName: MyVariable
typeDefinition: MyOptionalVariableType
optionals:
- Units

4. The optionals: List — Qualified-Name Path Segment Rules

Each entry in the optionals: block is a path through the type hierarchy of the instance's typeDefinition.

[!IMPORTANT] Strict Qualified BrowseNames (v4.0.0+) Every segment of an optional path must be fully qualified with the namespace alias of the namespace that declares it. The alias is omitted only when the segment belongs to your model's own namespace. Earlier modeler versions tolerated bare names because they matched on leaf names; the current compiler resolves paths segment-by-segment and will reject unqualified paths.

Here is how to resolve paths for different namespace scenarios:

A. Single-Segment Optionals (Foreign Namespace)

When the optional property is declared directly on the base type from a foreign namespace, prefix it with that namespace's alias (e.g., di: for Device Integration):

Single-segment optional properties
# di:DeviceType declares SerialNumber (Optional) and Manufacturer (Optional)
- browseName: Axis1
typeDefinition: di:DeviceType
organizedBy: /ua:Objects
optionals:
- di:SerialNumber # qualified — required for foreign-namespace segments
- di:Manufacturer

B. Nested / Deep Optionals

When an optional property is nested inside intermediate components, list the full path segment-by-segment.

  • Separate segments using a dot (.).
  • Each individual segment must carry its own namespace alias if it belongs to a foreign namespace.
Nested / deep optional path
objectTypes:
# di:DeviceType declares ParameterSet (in 'di' namespace), and
# our MyDeviceType adds Optional Temperature inside ParameterSet.
- browseName: MyDeviceType
subtypeOf: di:DeviceType
components:
- browseName: di:ParameterSet
components:
- browseName: Temperature
dataType: ua:Double
modellingRule: Optional

instances:
- browseName: DeviceA
typeDefinition: MyDeviceType
organizedBy: /ua:Objects
optionals:
- di:ParameterSet.Temperature # segments joined with dots, each qualified appropriately

[!CAUTION] Namespace Colon vs Path Dot The colon (:) separates a namespace alias from its browseName within a single segment. It is not a path separator.

  • Correct: di:ParameterSet.Temperature
  • Malformed: di:ParameterSet:Temperature

C. Own-Namespace Optionals

For elements declared inside your own model, you can either omit the namespace prefix entirely (the own: prefix is implicit) or explicitly write own::

Own-namespace optional segments
objectTypes:
- browseName: MyDeviceOwnType
subtypeOf: di:DeviceType
properties:
- browseName: SerialNumber
dataType: ua:String
modellingRule: Optional
instances:
- browseName: Device1
typeDefinition: MyDeviceOwnType
organizedBy: /ua:Objects
optionals:
- SerialNumber # own namespace, alias optional
- own:SerialNumber # equivalent

Troubleshooting Common Mistakes

If an optional path is malformed, the modeler's compiler or LSP diagnostics will output a helpful warning:

Symptom (warning text)CauseFix
Optional "Foo" is not a valid child of type "BarType". It will be ignoredMissing namespace alias on a foreign-namespace segmentPrefix with the alias declaring Foo (e.g. robotics:Foo).
did you mean "di:ParameterSet.robotics:ActualSpeed" ?Bare leaf name; the compiler resolved a fuzzy matchCopy/paste the suggested fully qualified path from the warning.
Optional "X" is not marked as optional in the type definitionThe targeted node is already Mandatory in the type definitionRemove from optionals: (it is already instantiated automatically).

Full example

📄 Full working example — variable-types.model.yaml
variable-types.model.yaml
# yaml-language-server: $schema=../../../schemas/nodeset2.schema.json
namespaceUri: http://sterfive.com/UA/doc-examples/variable-types/
version: 1.0.0
publicationDate: "2026-05-26T00:00:00Z"

namespaces:
- di

variableTypes:
- browseName: MyVariableType
dataType: ua:Double
value: 0

- browseName: MySensorValueType
dataType: ua:Double
valueRank: 1 # Array of any size
accessLevel: CurrentRead | CurrentWrite | HistoryRead
minimumSamplingInterval: 100 # Minimum sampling interval in milliseconds

objectTypes:
- browseName: MyObjectType
properties:
- browseName: MyVariable
typeDefinition: MyVariableType
modellingRule: Mandatory # or Optional

- browseName: MyOptionalVariableType
dataType: ua:Double
value: 0
properties:
- browseName: Units
dataType: ua:String
modellingRule: Optional

instances:
- browseName: MyObject
typeDefinition: ua:BaseObjectType
organizedBy: /ua:Objects
components:
- browseName: MyVariable
typeDefinition: MyOptionalVariableType
optionals:
- Units

# di:DeviceType declares SerialNumber (Optional) and Manufacturer (Optional)
- browseName: Axis1
typeDefinition: di:DeviceType
organizedBy: /ua:Objects
optionals:
- di:SerialNumber # qualified — required for foreign-namespace segments
- di:Manufacturer

objectTypes:
# di:DeviceType declares ParameterSet (in 'di' namespace), and
# our MyDeviceType adds Optional Temperature inside ParameterSet.
- browseName: MyDeviceType
subtypeOf: di:DeviceType
components:
- browseName: di:ParameterSet
components:
- browseName: Temperature
dataType: ua:Double
modellingRule: Optional

instances:
- browseName: DeviceA
typeDefinition: MyDeviceType
organizedBy: /ua:Objects
optionals:
- di:ParameterSet.Temperature # segments joined with dots, each qualified appropriately

objectTypes:
- browseName: MyDeviceOwnType
subtypeOf: di:DeviceType
properties:
- browseName: SerialNumber
dataType: ua:String
modellingRule: Optional
instances:
- browseName: Device1
typeDefinition: MyDeviceOwnType
organizedBy: /ua:Objects
optionals:
- SerialNumber # own namespace, alias optional
- own:SerialNumber # equivalent
📄 Generated NodeSet2.xml — variable-types.model.Nodeset2.xml
variable-types.model.Nodeset2.xml
# ⚠ File not found: ./_examples/variable-types.model.Nodeset2.xml