Finding UDT Member Variable Usage in TIA Portal Projects

David Krause14 min read
SiemensTIA PortalTutorial / How-to
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

Problem: UDT Member Variables Are Invisible to Standard TIA Portal Cross-Reference

When a Siemens S7-1200 or S7-1500 program uses a custom PLC data type (UDT) such as type_motor with members temperature, current, speed, and torque, the integrated cross-reference browser (right-click the symbol → "Cross-references") only shows the call sites of the data type itself, not the call sites of its individual members. Searching for type_motor.temperature in the cross-reference returns zero hits in TIA Portal V12 SP1 Update 4, even when dozens of FBs, FCs, and instance DBs read or write that exact member. The same blind spot appears in the dependency structure, in search-and-replace, and in the compiled error list under normal conditions.

Engineers who need to refactor, rename, delete, or trace a single UDT member are therefore forced to use indirect workarounds. This reference documents every known method — including the manual delete-and-compile trick, the source-file export, the Openness API script, and a hybrid cross-reference + dependency workflow — and compares their cost, accuracy, and applicability across TIA Portal V12 through V18. Test the behavior in your installed version before relying on it for a refactor that touches production code.

Field note: the limitation is by design, not a defect. TIA Portal's static analysis symbol table stores the UDT as a single node and indexes accesses to the parent type. Member-level indexing was gradually introduced in later versions; verify exact behavior in your build via Help → Installed software → TIA Portal.

Why the TIA Portal Cross-Reference Cannot Resolve UDT Members

The cross-reference engine is built on the offline symbol table of the S7 program. When a programmer declares a data type in the PLC data types editor, the type is registered as a single symbol with a fixed name (for example, type_motor). The compiler stores the offsets of each member inside the type, but it does not emit per-member entries into the cross-reference index. The engine only registers the type as a "used in" target wherever the type appears as a whole (in the Interface of an FB, in the Static section of a DB, as a multi-instance, or as a parameter type).

The reason type_motor.temperature shows zero hits is that the qualified name resolves to a sub-symbol that the static analyzer never created. The dot is interpreted as a member access at code-generation time, not at cross-reference-collection time. When the engineer renames the member, the refactoring tool rewrites every textual occurrence inside the same compilation unit, but the project-wide browser is unaware of the change because the symbol temperature lives only inside type_motor.

This is the same architectural reason why the Dependency structure view (TIA V12 SP1 Update 4 and later) shows type_motor as a dependency of every block that uses it, but never shows temperature as a sub-dependency. The view stops at the type level. Renaming temperature in the data type editor will propagate to every consumer at compile time, but locating those consumers ahead of the rename still requires one of the manual methods below.

Method 1 — Cross-Reference Browser (Type-Level Only)

The simplest first step is the cross-reference browser. It will not return member-level hits, but it returns the list of blocks that use the type, which is the starting set for the member search.

  1. Open the PLC data types editor and select type_motor.
  2. Right-click → Cross-references (or press Shift+F11).
  3. Read the Used in column. Each row is a block (FB, FC, DB, OB) that references the type.
  4. Open each block individually, place the cursor inside it, and use Ctrl+Shift+F (Find and replace) with the regex \.temperature\b (or simply temperature in the open block).

This is the recommended first pass because the browser is fast and reliable, and the list of candidate blocks is usually small. For a 200-block project where type_motor is used in roughly 30 blocks, the per-block search takes 5 to 10 minutes manually.

Method 2 — Dependency Structure (Hierarchy View)

The dependency structure produces a tree that shows how blocks depend on the selected type. Open it via Project tree → PLC_x → right-click → Dependency → Dependency structure. The selected node is type_motor; the children are the blocks that depend on it. As with the cross-reference, the tree stops at the type level, so the engineer must expand each block node to inspect the actual access.

View Shows type uses Shows member uses Drill-down required
Cross-reference browser Yes No One per block
Dependency structure Yes No One per block
Search & replace (open block) No (single block) Yes None
Source file export → text search Yes Yes None
Delete-and-compile No (type) Yes (via error list) None
TIA Portal Openness API Yes Yes None (script)

Method 3 — Search and Replace in the Open Block

The find-and-replace dialog (Ctrl+F or Ctrl+Shift+F) operates only on the block currently open in the editor, but it does match member names because it is a plain-text search across the source. To trace a member:

  1. Open the candidate block (FB, FC, or DB) from the cross-reference list of Method 1.
  2. Press Ctrl+F and enter the member name, e.g. temperature.
  3. Use Find next and Find previous to step through every occurrence. Tick Match whole word to avoid spurious hits on variables such as temperature_high.
  4. Repeat for every block from the cross-reference list.

The limitation is that the dialog is local to the open block. There is no project-wide search-and-replace equivalent in the offline editor for member-level symbols. The dialog is also case-sensitive by default; ensure Match case is unchecked unless the rename is case-sensitive.

Method 4 — Source File Export and Text Search

The source-file export converts the S7 program into a set of .scl, .sdb, and STL text files that can be searched with any external tool (Notepad++, grep, ripgrep, PowerShell Select-String). This is the only built-in way to do a true project-wide search for a UDT member in one pass.

  1. In the project tree, right-click the Program blocks folder.
  2. Choose External source files → Generate source. TIA Portal emits a .scl file (or STL, depending on the block language) for every block, plus a master source.
  3. Choose a target folder and confirm. The export includes DBs, FBs, FCs, OBs, and the data type declarations.
  4. Open a terminal or text editor and search for the member name as a regular expression: grep -rwn temperature Blocks/ on Linux, or in PowerShell: Select-String -Path .\Blocks\*.scl -Pattern 'temperature\b'.
  5. Each hit is a line number inside a block. Match it back to the original block in the project tree by file name.

The export captures only the SCL/STL representation. FBD, LAD, and GRAPH blocks are converted to SCL representation on export in TIA Portal V13 and later, but the conversion is lossy: comments, network titles, and I/O pin assignments may be reordered. Use the export only for search; do not re-import the modified source as a substitute for the original block.

Engineer's field note: in STEP 7 V5.x and STEP 7 Professional, the source-file export includes the entire program. In TIA Portal V12 SP1, the export works for SCL blocks and data blocks, but the project-wide source generation feature does not always include all FBs in FBD/LAD. Verify the file count against the project tree before relying on the search result.

Method 5 — Delete-and-Compile Workaround

The fastest fully-correct way to enumerate every consumer of a UDT member is to remove the member from the data type and compile. The compiler emits an error for every access site. This is the method most often cited in field reports for TIA Portal V12 SP1 Update 4.

  1. Open the PLC data type editor and select type_motor.
  2. Rename the member to a unique placeholder, for example temperature__TO_DELETE. This avoids accidental re-creation through auto-complete.
  3. Right-click the Program blocks folder and select Compile → Software (rebuild all blocks).
  4. Open the Inspector → Compile tab and read the error list. Every line of the form Block "FB_MotorCtrl" (FB100), Instance "DB_Motor1" (DB200), Access to non-existent member 'temperature' corresponds to one consumer.
  5. Annotate each consumer, then restore the original member name in the data type to clear the errors.

This method is reliable and project-wide, but it is intrusive: the project is briefly in a state that will not download. Use it on a copy of the project, not on a live source. It also doubles the work — the engineer must compile twice and reconcile the list with the original rename. For large projects (more than 500 blocks) the compile takes several minutes; budget time accordingly.

Method 6 — TIA Portal Openness API Script (Recommended for Repeated Work)

For engineers who perform UDT refactors often, the TIA Portal Openness API exposes the project model and can resolve member-level access without touching the build state. The API is available with the TIA Portal Openness option and runs as a C# or PowerShell automation. The script reads every block's source and the data type declaration, then enumerates member accesses.

// C# snippet — read all references to a UDT member
using Siemens.Engineering;
using Siemens.Engineering.SW;
using Siemens.Engineering.SW.Blocks;
using Siemens.Engineering.SW.Types;

var tia = new TiaPortal(TiaPortalMode.WithUserInterface);
var project = tia.Projects.Open(new FileInfo(@"C:\Projects\Plant42.ap18"));
var device = project.Devices.First();
var plc = device.DeviceItems.First(d => d.Name.StartsWith("PLC_")).GetService<PlcSoftware>();

string targetType = "type_motor";
string targetMember = "temperature";
string pattern = $"{Regex.Escape(targetType)}\\.{Regex.Escape(targetMember)}\\b";

var results = new List<string>();
foreach (var blockGroup in plc.BlockGroups)
{
    foreach (var block in blockGroup.Blocks)
    {
        var src = block.GetAttribute("Comment");          // placeholder
        var code = block.Export(ExportOptions.WithDefaults).ReadToEnd();
        foreach (Match m in Regex.Matches(code, pattern))
            results.Add($"{block.Name} ({block.GetType().Name}) offset {m.Index}");
    }
}
results.ForEach(Console.WriteLine);
project.Close();
tia.Dispose();

The script reads each block through the Openness exporter and runs a regex against the textual representation. It does not modify the project, does not require a compile, and produces a stable, repeatable report. For an S7-1500 program with 800 blocks the script typically completes in under 30 seconds.

Prerequisite: TIA Portal Openness requires a valid license (Siemens order number 6ES7822-1AA05-0YA5 or newer). The Openness DLLs are part of the TIA Portal installation directory (C:\Program Files\Siemens\Automation\Portal V<version>\PublicAPI). Reference Siemens.Engineering.dll in your project.

Method Comparison Matrix

Method Project-wide? Modifies project? Speed (200 blocks) Reliability Best for
Cross-reference browser Yes (type level) No 5 s High First pass, candidate list
Dependency structure Yes (type level) No 5 s High Visual hierarchy of type use
Search & replace (per block) No No 5–10 min High Small projects, occasional refactor
Source export + text search Yes No 2 min Medium (lossy for FBD/LAD) One-off audit across all languages
Delete-and-compile Yes Yes (temporarily) 10 min (incl. 2 compiles) Highest Definitive answer when other methods fail
Openness API script Yes No 30 s High (read-only) Repeatable, automated reports

Recommended Hybrid Workflow

The fastest accurate procedure combines Methods 1, 4, and 5 in sequence. The first two are non-invasive and produce a list; the third confirms the list when the search tools are inconclusive.

  1. Cross-reference the type to enumerate candidate blocks (Method 1). Save the list.
  2. Export the source and run a regex search for the qualified member name (Method 4). The result is the consolidated list of consumer blocks.
  3. If the source export missed a block (typical for FBD/LAD-only projects), delete-and-compile the member to confirm the list (Method 5). Restore the member name after the audit.
  4. For ongoing refactors, build an Openness script (Method 6) and run it before every rename.

Version-Specific Notes (TIA Portal V12 – V18)

The behavior of the cross-reference and dependency structure is version-dependent. The following table summarizes the field-reported observations; verify each entry in your installed build before relying on it.

TIA Portal version Cross-reference (member level) Dependency structure (member level) Source export coverage
V12 SP1 Update 4 Not supported Not supported SCL/DB only
V13 / V13 SP1 Partial (multi-instance only) Partial SCL/DB; FBD/LAD converted
V14 / V14 SP1 Partial Partial SCL/DB; FBD/LAD converted
V15 / V15.1 Improved (structured types) Improved SCL/DB; FBD/LAD converted
V16 / V17 Improved for global DBs Improved All block languages
V18 Improved for global DBs and instances Improved All block languages
Verification step: regardless of the version listed above, open the cross-reference of the type, then click one of the consumer blocks. The cross-reference tab inside the block will show whether the type or the member is highlighted. If only the type is highlighted, member-level indexing is not active in that build.

Commissioning and Safety Checks

Member-level refactors are not download-safe. Before performing a delete-and-compile workaround on a project that is being commissioned in the field, take the following precautions:

  1. Take a TIA Portal project backup via Project → Archive. Store the archive on a network share with versioned filenames.
  2. Perform the audit on a copy, not on the project that is online with the PLC.
  3. Document the placeholder name used in Method 5 and revert it before the next download.
  4. If the project is under change control, attach the audit output (list of consumer blocks) to the change request.

For S7-1500 projects, also consider the Know-how protection attribute on the type. Protected blocks export as opaque binaries; the source-export method will not see inside them, and the delete-and-compile method will produce an error that is not a member-level access error. Decrypt the block with the password before the audit, or exclude protected blocks from the audit scope.

Troubleshooting Matrix

Symptom Likely cause Resolution
Cross-reference of type_motor.temperature returns zero hits Member-level indexing disabled in this build Use Methods 4 or 5
Source export contains no files Block group selection did not include sub-groups Right-click the Program blocks root node, not a sub-folder
Source export shows truncated SCL Block is GRAPH or FBD with no SCL representation Open the block in the editor and copy the network titles to a side log; use Method 5 for the binary content
Delete-and-compile produces no errors Member was not actually used; safe to delete Confirm with an explicit search of every instance DB
Openness script returns access denied TIA Portal is running in another process Close the TIA Portal UI or use TiaPortalMode.WithoutUserInterface in a separate process
Search finds the member inside a multi-instance DB Engineer expected only the type declaration; the multi-instance is a real consumer Annotate the multi-instance and treat it as a consumer block

Field-Proven Best Practices

  1. Name members for grep-friendliness. Use full English words, no abbreviations. Avoid tmp1, spd, temp — they collide with other variables during a text search.
  2. Prefer one member per concept. If a UDT member is used in more than 30 blocks, consider splitting it into its own data type. The cross-reference will then resolve it directly.
  3. Keep the source export as a build artifact. Generate a .zip of the SCL source under the same change request as the PLC code. Audits become trivial.
  4. Wrap the Openness script as a build tool. Add it to the project TIA Portal Openness BuildProvider so the consumer list is regenerated on every compile.
  5. Avoid rename in the live project. Always rename on a copy. TIA Portal's rename is reliable inside the type, but consumer blocks that were not compiled for the new name will produce runtime access errors after a download to the PLC.

FAQ

How do I find every block that reads type_motor.temperature in TIA Portal V12 SP1?

Use the hybrid workflow: first run the cross-reference on the type (right-click → Cross-references) to get the candidate block list, then export the program source (Program blocks → External source files → Generate source) and grep the resulting .scl files for temperature. If the export misses FBD/LAD blocks, fall back to the delete-and-compile workaround: rename the member, compile, and read the error list.

Why does the cross-reference of a UDT member show zero hits even when the project uses it?

TIA Portal's cross-reference indexes the UDT as a single symbol and does not store per-member entries in the symbol table. Member access is resolved at code generation, not at cross-reference collection. This is by design, not a bug. Newer TIA Portal versions (V15 and later) improve member-level indexing for global DBs and structured types, but the behavior is build-specific; verify in Help → Installed software.

Can the source-file export capture FBD, LAD, and GRAPH blocks?

From TIA Portal V13 onward, FBD and LAD blocks are converted to an SCL representation on export, but the conversion is lossy: comments, network titles, and I/O pin assignments may be reordered. GRAPH blocks export as GRAPH source, which is not searchable as plain text. For a full audit, combine the source export with the delete-and-compile method on the member.

Is the TIA Portal Openness API script supported in production projects?

Yes. Openness is an officially supported Siemens interface; the license is the TIA Portal Openness option (Siemens order number 6ES7822-1AA05-0YA5 or current equivalent). The script runs read-only against the project file and does not require the PLC to be online. Use it in CI pipelines to generate consumer lists on every build.

What happens if I rename a UDT member and download to the PLC without auditing consumers first?

The compiler will rewrite the textual occurrences of the member name inside every block that uses the type, so the download itself is consistent. However, any block that read the member through an AT construct, a pointer, or a hand-written PEEK/POKE will not be rewritten, and the PLC will raise an access error at runtime. Always perform the audit before a download to a live controller.

Back to blog