Skip to main content

A folder a type organizes

A type can hold members through Organizes as well as through HasComponent and HasProperty: a BuildingBlocks folder that groups the parts every device of that type has. Machinery-based companion specifications use this often — OPC 30500 LADS gives LADSDeviceType a MachineryBuildingBlocks folder holding its state machines and lifetime counters.

Organizes is a hierarchical reference but not an aggregating one, and that difference is what this page is about: which node is the folder's parent, what its NodeId is called, and why it matters when the ids have to match a working group's table.

Declaring it

A type organizing a folder
objectTypes:
- browseName: PumpType
organizes:
- browseName: BuildingBlocks
typeDefinition: ua:FolderType
components:
- browseName: OperatingHours
dataType: ua:Double

The folder is a member of the type: it is Mandatory by default, like any member declared in a type, and instances of PumpType get their own copy of it.

What the compiler emits

The type is the folder's declared parent. The NodeSet carries it as ParentNodeId, and the folder and everything under it are named through the type:

The folder and its member, excerpt of the generated NodeSet
<UAObject NodeId="ns=1;i=1001" BrowseName="1:BuildingBlocks" ParentNodeId="ns=1;i=1000">
<DisplayName>BuildingBlocks</DisplayName>
<References>
<Reference ReferenceType="HasTypeDefinition">i=61</Reference>
<Reference ReferenceType="HasModellingRule">i=78</Reference>
<Reference ReferenceType="Organizes" IsForward="false">ns=1;i=1000</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=1002</Reference>
...
<UAVariable NodeId="ns=1;i=1002" BrowseName="1:OperatingHours" ParentNodeId="ns=1;i=1001" ...>
organized-folders.model.symbols.csv (excerpt)
PumpType,1000,ObjectType
PumpType_BuildingBlocks,1001,Object
PumpType_BuildingBlocks_OperatingHours,1002,Variable
ValveType,1003,ObjectType
ValveType_BuildingBlocks,1004,Object

The reference that links them stays Organizes: the declaration names the parent, it does not turn the folder into a component.

📄 Generated NodeSet2.xml — organized-folders.model.Nodeset2.xml
organized-folders.model.Nodeset2.xml
<?xml version="1.0"?>
<UANodeSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd" xmlns:ns1="http://sterfive.com/UA/doc-examples/organized-folders/Type.xsd">
<NamespaceUris>
<Uri>http://sterfive.com/UA/doc-examples/organized-folders/</Uri>
</NamespaceUris>
<Models>
<Model ModelUri="http://sterfive.com/UA/doc-examples/organized-folders/" Version="1.0.0" PublicationDate="2026-09-11T00:00:00.000Z">
<RequiredModel ModelUri="http://opcfoundation.org/UA/" Version="1.05.07" PublicationDate="2026-05-01T00:00:00.000Z"/>
</Model>
</Models>
<Aliases>
<Alias Alias="Double">i=11</Alias>
<Alias Alias="HasComponent">i=47</Alias>
<Alias Alias="HasModellingRule">i=37</Alias>
<Alias Alias="HasSubtype">i=45</Alias>
<Alias Alias="HasTypeDefinition">i=40</Alias>
<Alias Alias="Organizes">i=35</Alias>
</Aliases>
<!--ReferenceTypes-->
<!--ObjectTypes-->
<!--ObjectType - 1:PumpType {{{{ -->
<UAObjectType NodeId="ns=1;i=1000" BrowseName="1:PumpType">
<DisplayName>PumpType</DisplayName>
<References>
<Reference ReferenceType="HasSubtype" IsForward="false">i=58</Reference>
</References>
</UAObjectType>
<!--ObjectType - 1:PumpType }}}}-->
<!--ObjectType - 1:ValveType {{{{ -->
<UAObjectType NodeId="ns=1;i=1003" BrowseName="1:ValveType">
<DisplayName>ValveType</DisplayName>
<References>
<Reference ReferenceType="HasSubtype" IsForward="false">i=58</Reference>
</References>
</UAObjectType>
<!--ObjectType - 1:ValveType }}}}-->
<!--VariableTypes-->
<!--Other Nodes-->
<!--Object - 1:BuildingBlocks {{{{ -->
<UAObject NodeId="ns=1;i=1001" BrowseName="1:BuildingBlocks" ParentNodeId="ns=1;i=1000">
<DisplayName>BuildingBlocks</DisplayName>
<References>
<Reference ReferenceType="HasTypeDefinition">i=61</Reference>
<Reference ReferenceType="HasModellingRule">i=78</Reference>
<Reference ReferenceType="Organizes" IsForward="false">ns=1;i=1000</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=1002</Reference>
</References>
</UAObject>
<UAVariable NodeId="ns=1;i=1002" BrowseName="1:OperatingHours" ParentNodeId="ns=1;i=1001" AccessLevel="3" DataType="Double">
<DisplayName>OperatingHours</DisplayName>
<References>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
<Reference ReferenceType="HasModellingRule">i=78</Reference>
</References>
</UAVariable>
<!--Object - 1:BuildingBlocks }}}} -->
<!--Object - 1:BuildingBlocks {{{{ -->
<UAObject NodeId="ns=1;i=1004" BrowseName="1:BuildingBlocks" ParentNodeId="ns=1;i=1003">
<DisplayName>BuildingBlocks</DisplayName>
<References>
<Reference ReferenceType="HasTypeDefinition">i=61</Reference>
<Reference ReferenceType="HasModellingRule">i=78</Reference>
<Reference ReferenceType="Organizes" IsForward="false">ns=1;i=1003</Reference>
</References>
</UAObject>
<!--Object - 1:BuildingBlocks }}}} -->
<!--Object - 1:Pump1 {{{{ -->
<UAObject NodeId="ns=1;i=1005" BrowseName="1:Pump1">
<DisplayName>Pump1</DisplayName>
<References>
<Reference ReferenceType="Organizes" IsForward="false">i=85</Reference>
<Reference ReferenceType="HasTypeDefinition">ns=1;i=1000</Reference>
</References>
</UAObject>
<!--Object - 1:BuildingBlocks {{{{ -->
<UAObject NodeId="ns=1;i=1006" BrowseName="1:BuildingBlocks">
<DisplayName>BuildingBlocks</DisplayName>
<References>
<Reference ReferenceType="HasTypeDefinition">i=61</Reference>
<Reference ReferenceType="Organizes" IsForward="false">ns=1;i=1005</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=1007</Reference>
</References>
</UAObject>
<UAVariable NodeId="ns=1;i=1007" BrowseName="1:OperatingHours" ParentNodeId="ns=1;i=1006" AccessLevel="3" DataType="Double">
<DisplayName>OperatingHours</DisplayName>
<References>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
</References>
</UAVariable>
<!--Object - 1:BuildingBlocks }}}} -->
<!--Object - 1:Pump1 }}}} -->
</UANodeSet>

Why the name matters: a working group's table

This is exactly what the OPC Foundation's ModelCompiler writes. In OPC 30500 LADS, the folder LADSDeviceType organizes is declared with the type as its ParentNodeId, and the working group's NodeIds.csv names it — and every node below it — through the type:

Opc.Ua.LADS.NodeSet2.xml (excerpt)
<UAObject NodeId="ns=4;i=5063" BrowseName="3:MachineryBuildingBlocks" ParentNodeId="ns=4;i=1002">
<References>
<Reference ReferenceType="Organizes" IsForward="false">ns=4;i=1002</Reference>
Opc.Ua.LADS.NodeIds.csv (excerpt)
LADSDeviceType_MachineryBuildingBlocks,5063,Object
LADSDeviceType_MachineryBuildingBlocks_LifetimeCounters,5091,Object

opcua-modeler spec-repo pull reverses that NodeSet into a model, seeds the NodeIds from that table and compiles it back. For the ids to be reused, the compiled folder must carry the same name as the row: LADSDeviceType_…, not MachineryBuildingBlocks. With the declared parent it does, and the LADS pull reproduces all 650 ids of the group's table.

The two traps this closes

The orphan organized folder. Without a declared parent, an organized folder has no parent at all, since Organizes does not aggregate. It was named on its own (BuildingBlocks), the table's PumpType_BuildingBlocks row was never matched, and a second type organizing a folder of the same name — ValveType above — collided with the first and got no stable id.

A second type organizing a folder of the same name
objectTypes:
- browseName: ValveType
organizes:
- browseName: BuildingBlocks
typeDefinition: ua:FolderType

The first-reference parent. A node reached through two aggregating parents used to take the first reference met as its parent. LADS has one such node per nameplate property: the type's own Manufacturer is also a property of its Identification add-in. The NodeSet declares the type, and the reverse now writes the node under the type, with the add-in's link as a references: entry, so it is compiled once, named after the type, and keeps the group's id.

An existing model keeps its NodeIds

A symbol table written before this change (a .symbols.csv, or the table of a .opcua.yaml) names an organized folder without its type. When the compiler finds such a row and no row under the new name, it moves the row to the new name — the folder and everything below it — so no node is renumbered. The ids do not change; only the names in the table do, the next time it is written.

What an instance gets

An instance of the type
instances:
- browseName: Pump1
typeDefinition: PumpType
organizedBy: /ua:Objects

Pump1 gets its own BuildingBlocks folder, as instantiating a type creates its Mandatory members. An instance organized by the Objects folder does not declare a parent: Objects belongs to the standard namespace, and the instance keeps its own name.

Known limit

The instance's copy of the folder is not yet named after the instance: it is BuildingBlocks, not Pump1_BuildingBlocks, and a second instance's copy gets no symbol of its own. The copy is made by node-opcua when it instantiates the type, and does not declare the instance as its parent yet.