Accessing Nested UDT Fields in TIA Portal Data Blocks
When a TIA Portal project throws Tag "DataHandler".Static_1.ON_State_Text.K1_NO_Contact_Text not defined. at compile or download, the symptom is almost always a path that does not match the actual declared structure of the underlying data block (DB) or user-defined type (UDT). The error is reported as event ID 30 in the compile log when an FB method, SCL routine, or HMI tag binding references a fully qualified symbolic path that TIA cannot resolve at compile time. This article walks through the exact resolution path, the syntactic rules TIA Portal uses to qualify nested UDT members, and the verification steps that confirm the fix.
1. Problem Statement
A typical failure on TIA Portal V16–V18 projects is reported as:
30,Tag "DataHandler".Static_1.ON_State_Text.K1_NO_Contact_Text not defined.,,,09:52:54 AM
The string is composed of an event class (30 = warning/info from the compiler), the tag path attempted by the code, and the timestamp. Note that the path uses dot-notation across what the programmer believes are three structural levels: a DB instance, a UDT member, a nested struct member, and a leaf BOOL/String element. When the compile path is rejected, the runtime never gets a chance to evaluate the expression.
Common triggers include:
- Mismatch between the named UDT instance and the actual instance name declared in the DB.
- An intermediate structure or "TagNames" level that was added (or removed) during refactoring.
- Use of
Static_1where the FB static variable isStatic, or vice versa. - Accessing a member that exists in the type but whose instance has not been re-compiled since the type was changed.
- Referencing a UDT field from a different DB instance than the one that owns the path.
2. UDT and DB Model in TIA Portal
A PLC data type (UDT) in TIA Portal is a reusable composite type. It can contain:
- Elementary types: BOOL, INT, DINT, REAL, BYTE, WORD, DWORD, LREAL, SINT, USINT, UINT, UDINT, TIME, DATE, TIME_OF_DAY, CHAR, STRING, WSTRING.
- Other PLC data types (nested UDTs).
- One-dimensional arrays of any of the above (multi-dimensional arrays inside a UDT are not allowed, as documented in the Rockwell Automation KB 48008 describing the same constraint in Logix Designer; the same dimensional restriction applies inside Siemens UDT bodies — only ARRAY[..] of one dimension is permitted).
- Structures (STRUCT … END_STRUCT) of mixed members.
UDTs are types. They become data when instantiated, either as a standalone DB ("DB of type MyUDT") or as a member of a larger DB or FB static area. The naming of the instance is independent of the type name and is what must appear in the access path.
| Concept | Type (UDT) | Instance (DB or DB member) |
|---|---|---|
| What it is | Template / schema | Allocated memory with values |
| Where declared | PLC data types folder | Program blocks > DBs, or FB static section |
| Named in code as | "Type_MyUDT" | "DataHandler".SubMember |
| Edit triggers recompile of | All instances of the type | Only the consumer block |
3. Reproducible Nested UDT Example
Define a base UDT, a container UDT, and a DB that uses the container UDT. The example mirrors the source-code symptom (DataHandler.Static_1.ON_State_Text.K1_NO_Contact_Text) but generalises it so the resolution is unambiguous.
3.1 Base UDT: typeUDT_ContactText
Pre>
TYPE "typeUDT_ContactText"
VERSION : 0.1
STRUCT
K1_NO_Contact_Text : STRING[40];
K1_NC_Contact_Text : STRING[40];
K2_NO_Contact_Text : STRING[40];
END_STRUCT;
END_TYPE
3.2 Mid-level UDT: typeUDT_OnStateText
TYPE "typeUDT_OnStateText"
VERSION : 0.1
STRUCT
Description : STRING[60];
ON_State_Text : "typeUDT_ContactText"; // nested UDT instance
OFF_State_Text : "typeUDT_ContactText";
END_STRUCT;
END_TYPE
3.3 Container UDT: typeUDT_Static
TYPE "typeUDT_Static"
VERSION : 0.1
STRUCT
TagNames : ARRAY[1..16] OF STRING[32];
ON_State_Text : "typeUDT_OnStateText"; // nested UDT instance
END_STRUCT;
END_TYPE
3.4 Data Block: DataHandler
Make the DB a global DB of type typeUDT_Static, then add an explicit instance member named Static_1:
DATA_BLOCK "DataHandler"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
NON_RETAIN
STRUCT
Static : "typeUDT_Static"; // type-level instance
Static_1 : "typeUDT_Static"; // second instance for redundancy
END_STRUCT;
END_DATA_BLOCK
The fully qualified symbolic path to the leaf field is therefore:
"DataHandler".Static.ON_State_Text.K1_NO_Contact_Text
"DataHandler".Static_1.ON_State_Text.K1_NO_Contact_Text // <-- the path the compiler is rejecting
The compiler accepts the first path; it rejects the second only if the actual structure of the Static_1 branch does not contain an ON_State_Text member. The error message is a single line, but it is the path string itself that the editor is using to navigate the type. Fixing the path fixes the compile error.
4. Root Cause Analysis
Errors of the form Tag ... not defined are produced by the SCL/ST/F-LAD/F-FBD compiler when the symbolic resolver cannot find an exported tag whose instance path matches the string in the source. The resolver looks at the project's currently compiled symbol table, not at the open editor. Six root causes account for 95 percent of these failures:
| # | Root cause | How to confirm |
|---|---|---|
| 1 | Instance name in DB does not match the path. Example: DB has Static, code says Static_1. |
Open the DB, compare the member name in the declaration to the string used in code. |
| 2 | Structural level missing. Example: UDT now contains a TagNames array level that the code skipped. |
Open the UDT, count the STRUCT/END_STRUCT nesting, compare to the dot-segment count in the path. |
| 3 | UDT was modified but dependent DB/FB was not recompiled. | Right-click the project → Compile → Software (rebuild all). Confirm Compilation: Successful in the info pane. |
| 4 | Symbolic access is disabled in the FB properties. | Open FB → Properties → Attributes → ensure Optimized block access + Symbolic access are checked (or use absolute access). |
| 5 | The tag is in a multi-instance FB and the multi-instance name is missing from the path. | Open the parent FB static section and look for the multi-instance name (default is the FB name, not the type name). |
| 6 | The "DB name" portion of the path is wrong because the code is in a different program block than the data. | Confirm the FB/FC that holds the code is in the same program resource (or use fully qualified DB name with block number fallback). |
Note that the same conceptual limitation — that nested user-defined structures have a fixed hierarchy and any path written in code must mirror that hierarchy — is also documented in vendor knowledge bases for Logix Designer UDTs (see Rockwell KB 48008). Although the platforms differ, the resolution workflow is the same: confirm the instance, confirm the type, and recompile.
5. Correct Access Syntax
5.1 Symbolic (SCL) — preferred
// Read nested STRING from a UDT-in-UDT-in-DB
#sLocal := "DataHandler".Static_1.ON_State_Text.K1_NO_Contact_Text;
// Write
"DataHandler".Static_1.ON_State_Text.K1_NO_Contact_Text := 'K1 closed';
// Within an FB static of type typeUDT_Static, use the local alias:
#sLocal := #Static.ON_State_Text.K1_NO_Contact_Text;
5.2 Absolute (fallback for non-optimised blocks)
// For non-optimised blocks, the compiler emits the absolute address.
// Syntax: <DB number>.<byte>.<bit>
// Use only if symbolic access is disabled.
L DB123.DBB 40 // load byte offset 40 from DB 123
5.3 In a multi-instance FB
// Multi-instance named "IO_Station_1" inside FB "MainProcess" static area
#sLocal := #IO_Station_1.Static_1.ON_State_Text.K1_NO_Contact_Text;
Path segments must be either the declared member name (case-sensitive) of the data block/UDT, or an array index in square brackets. Quotes around DB names are required when the name contains characters that would otherwise be interpreted as keywords or when the project's language setting reserves the word. TIA Portal V17+ accepts a fully unquoted DB name if the name is a valid identifier (letters, digits, underscores, no leading digit).
6. Step-by-Step Resolution Procedure
-
Open the data block. In the project tree, expand Program blocks > System blocks > DataHandler (or the equivalent global DB). Confirm whether the block is of type
typeUDT_Staticor contains a member of that type. -
Expand every level. Click the small
+next to each STRUCT node in the DB declaration table. TIA Portal V16 and earlier hide nested members; the table view must show every level down to the BOOL/STRING leaf. - Read the actual instance names. Each instance row shows a Name column and a Data type column. The Name is the segment that must appear in the code's dot-path.
-
Compare to the path in the code. The error message itself prints the exact path the compiler tried to resolve. Diff that string against the structure shown in the DB. Look for missing intermediate structs (the source mentions a missing
TagNameslevel — that is the typical pattern). - Adjust the path in code, or rename the instance in the DB. TIA Portal V17+ allows in-place renaming with F2; the change propagates to the symbol table on the next compile.
- Recompile the affected blocks. Right-click the DB → Compile > Software (rebuild all blocks). Alternatively, in the project tree, right-click the PLC and select Compile > Software (rebuild all). Wait for the Compile: Successful status.
- Re-validate the path in the code editor. In the SCL/ST source, right-click after the dot and choose Insert symbol (or press Ctrl+Space). TIA Portal will now list the children of the parent segment. If the desired leaf appears, the path is correct.
- Drag-and-drop verification. Drag the leaf element from the DB table into a code window. TIA Portal inserts the full path automatically. This is the most reliable way to eliminate typos.
7. Compilation and Project State Dependencies
TIA Portal resolves symbolic paths against the compiled symbol table, not against the on-screen declaration. If the UDT definition changes but the DB is not re-compiled, the editor will still show the old members in the DB table (because the table reads from the block's stored declaration), but the SCL compiler will read from the symbol table and reject the path. This produces a confusing situation where the path looks correct in the editor but fails to compile.
Two settings govern this behaviour:
| Setting | Where | Effect |
|---|---|---|
| Optimised block access | DB / FB properties → Attributes | Removes the fixed offset layout; symbolic access is the only valid mode. |
| Symbolic access (in HMI / external source) | DB / FB properties → Attributes | Allows external consumers to refer to tags by name. Required for HMI tag binding by name. |
If a project is upgraded from an older STEP 7 version where absolute addressing was the default, the legacy blocks may still be non-optimised. Migrating to a new TIA Portal version does not automatically convert block access modes. Use Block → Properties → Attributes → Optimised block access to flip the flag, then recompile and re-test all access points. This conversion is non-reversible in the field for S7-1500 — it requires an offline download of the new block.
8. Auto-Complete and Drag-and-Drop Workflow
The single fastest diagnostic in TIA Portal is the integrated symbol picker. Three workflows are reliable:
-
Ctrl+Space after a dot. In SCL, after typing
"DataHandler".Static_1., press Ctrl+Space. TIA Portal lists the immediate children ofStatic_1. PickON_State_Text; TIA inserts it and the next dot; press Ctrl+Space again to see the leaves of that struct. - Drag-and-drop from the project tree. Locate the leaf in PLC data types → typeUDT_Static → ON_State_Text → K1_NO_Contact_Text, then drag it into the SCL editor at the cursor. The fully qualified path is inserted with the correct casing and the correct intermediate segments.
- Cross-reference (right-click → Go to → Cross-reference). From any leaf in a DB, the cross-reference view lists every read and write of that symbol. If a cross-reference appears with a red strike-through, that is the consumer that lost its definition — typically because of a renaming or recompile mismatch.
Use of these three flows eliminates almost all hand-typed path errors. When the source content reports the path DataHandler.Static_1.ON_State_Text.K1_NO_Contact_Text as failing, the fix in practice is to start from the project tree's DataHandler → Static_1 → ON_State_Text → K1_NO_Contact_Text hierarchy, drag the leaf into the code window, and compare the inserted string to the one that the error report cited. The first differing segment is the bug.
9. Common Pitfalls and Edge Cases
9.1 Hidden TagNames level after refactor
The source notes that the path "at least the struct level 'TagNames' is missing." This is the canonical symptom of a UDT that was extended with an outer wrapper (for example, a UDT that previously held contact-text fields directly but now wraps them inside a TagNames struct for indexing). The correct new path is DataHandler.Static_1.TagNames[i].ON_State_Text.K1_NO_Contact_Text. The fix is mechanical, but it can break many call-sites at once; use the cross-reference view to find them all.
9.2 Case sensitivity
TIA Portal preserves the case of UDT and DB member names. Static_1 and static_1 are different identifiers and only one of them will resolve. Older Portal versions (≤ V14) silently case-folded these; V15+ does not. If a project was edited on a V14 system and the source files were re-opened on V18, paths may suddenly fail to compile because of a case change introduced in the diff.
9.3 Array of UDT with non-constant index
Accessing an element of an array of UDT requires a runtime index:
// Valid only inside SCL; not valid in LAD/FBD contact/coil.
#sTmp := "DataHandler".Static_1.TagNames[#iIndex].ON_State_Text.K1_NO_Contact_Text;
Inside a contact network, only a constant index is permitted. The compiler will reject dynamic indexing in a non-SCL language with the same Tag not defined error if the path cannot be resolved at compile time.
9.4 Multi-instance FB inside a parent FB
When the UDT is held inside an FB static that is itself a multi-instance, the path gains an extra leading segment. Example: MainProcess.IOMap.Static_1.ON_State_Text.K1_NO_Contact_Text. The number of dot segments must equal the number of struct levels from the block's static area down to the leaf. Cross-reference the static declaration to count them.
9.5 HMI / OPC UA consumer
When the consumer is an HMI tag or an OPC UA client, the symbolic path must be present in the DB's symbol table. This requires Symbolic access on the DB (Properties → Attributes). If unchecked, the DB is accessible only by absolute address and the HMI/OPC UA symbolic path is undefined — the same Tag not defined error class, just from a different consumer.
9.6 STRING/WSTRING length and the [n] suffix
Symbolic access of a STRING element does not require the length in the path. ...K1_NO_Contact_Text is correct; ...K1_NO_Contact_Text[40] is not. If the length is in the path, the compiler treats the suffix as a member-of-STRING access, which is invalid, and again reports the tag as not defined.
9.7 Same name, different UDT
It is legal (but ill-advised) to declare two UDTs with the same member name but different contents. If the project tree shows two typeUDT_OnStateText types in different libraries, the compiler may resolve a path to the wrong one. Verify the fully qualified library path in the path by hovering over the symbol in the editor — the tooltip shows the originating library.
10. Verification Procedure
After applying a fix, run a structured verification:
- Compile (rebuild all). The information pane must show zero errors of class 30. Warnings are acceptable; errors are not.
- Download to the PLC. Use Online → Download to device. For a S7-1500 with optimised access, the download will fail with a consistency check if any path still references a stale member.
- Go online and monitor. In the DB, expand the path; the value column should display the live value of the leaf. If the value is the default (' ' for STRING, FALSE for BOOL), the path resolves but the data has not been written yet — that is the expected state at first scan.
- Cross-reference check. From the leaf, run Right-click → Go to → Cross-reference. The result list must show the consuming block. If the consumer does not appear, the path is unresolved.
- Watch table test. Add the leaf to a watch table with the full symbolic name. Force a value at the leaf; observe that the consumer reflects the change. Force to a different value to confirm bidirectional read/write.
- Trace test (optional). For high-integrity code, record a trace around the consumer block to confirm the path is being evaluated at the expected scan cycle.
11. Comparison with Other Platforms
UDT access syntax differs across platforms. The pattern is similar — fully qualified dot path from the data-root to the leaf — but the case sensitivity, multi-instance conventions, and dimension restrictions vary.
| Aspect | Siemens TIA Portal | Rockwell Logix Designer | CODESYS 3.5 | B&R Automation Studio |
|---|---|---|---|---|
| Path syntax | "DB".Member.Sub.field | Tag.Member.Sub.field | GVL.Var.Member | pv.var.member |
| UDT-as-member | Yes (nested UDTs allowed) | Yes (nested UDTs allowed) | Yes (DUT nesting) | Yes (struct nesting) |
| Array dimension in UDT | 1-D only | 1-D only (per KB 48008) | Multi-D allowed | Multi-D allowed |
| Multi-instance path | ParentFB.MultiInst.… | AOI name prefix | Instance-of-FB path | Program-inst.var |
| Case sensitivity | Yes (V15+) | Yes | Yes | Yes |
| Symbolic vs absolute | Both; optimised = symbolic only | Symbolic only | Symbolic only | Symbolic only |
The takeaway: in all four platforms, the resolution rule is the same. The path string in code must mirror the structure of the type. A Tag not defined error in any of them almost always means a structural mismatch between the path and the type, not a typographic error in the leaf name.
12. Preventive Best Practices
- Single source of truth for UDTs. Keep UDTs in a master library block (a global library or a master program). Re-import, do not re-type, when distributing to other stations.
- Never hand-type multi-level paths. Drag from the project tree or use Ctrl+Space after every dot.
- Rename in the declaration, not in code. Use F2 on the UDT declaration. The compiler propagates the change to every consumer. Renaming in code creates a divergence between the code and the symbol table.
- Cross-reference before deleting. Before removing a struct level or a UDT member, run the cross-reference view; the dialog shows every consumer that would break.
-
Version UDTs. Increment the UDT
VERSIONfield every time the type changes. This makes stale dependencies visible in the compile log. -
Keep multi-instance names distinct from the type name. A multi-instance
Static_1of typetypeUDT_Staticis unambiguous; a multi-instance of the same name as its type is easy to confuse in the path.
13. Frequently Asked Questions
Why does TIA Portal report "Tag not defined" even though I can see the tag in the DB declaration?
The compiler resolves paths against the compiled symbol table, not the open editor. If the UDT was modified but the DB or consumer block was not recompiled, the editor shows the old member but the compiler uses the new symbol table. Fix: right-click the PLC → Compile → Software (rebuild all).
Can I access a UDT field with a dynamic array index from a LAD contact network?
No. In LAD/FBD, only constant indices are accepted. The compiler will reject TagNames[#iIndex] in a contact and report the path as not defined. Use SCL for dynamic indexing, or expand the array with a CASE statement that uses one constant per branch.
How do I tell whether the path needs the "TagNames" level or not?
Open the UDT and count the STRUCT/END_STRUCT nesting from the outermost level to the leaf. Each STRUCT adds one dot segment. If the UDT was extended with a TagNames wrapper after the code was written, the code must add that segment; the editor will not insert it automatically.
What is the difference between Static and Static_1 in the path?
They are two separate instance members of the same DB, both of type typeUDT_Static. The numeric suffix is the instance name chosen at declaration time. The path must use the actual instance name. Renaming either instance changes the path in every consumer.
Does changing a UDT in a library break all my DBs?
Yes. Every DB of that UDT, and every FB static that uses it, must be recompiled. TIA Portal flags inconsistent versions with a yellow warning in the project tree. Right-click the project → Compile → Software (rebuild all) resolves the inconsistency. On an S7-1500 with optimised access, the PLC will reject the download if the version stamp on the block is older than the live runtime.
Can I have multi-dimensional arrays inside a Siemens UDT?
No. Siemens UDT bodies accept only one-dimensional ARRAY[..] declarations. Multi-dimensional arrays must be modelled either as nested UDTs (one UDT per "row") or as ARRAY of ARRAY of UDT at the DB level (not inside the UDT body). The same constraint is documented for Logix Designer UDTs in Rockwell KB 48008.