CONTROLS FOUNDRY
Guide

Understanding L5X Files: A Complete Guide to Rockwell Export Formats

Everything you need to know about Rockwell's L5X and L5K export formats — structure, tags, programs, routines, and how to work with them programmatically.

Mackie Gray|Founder, Controls Foundry|March 25, 202612 min read

What Is an L5X File?

An L5X file is an XML export of a Rockwell Automation ControlLogix or CompactLogix project. When you go to File > Export in Studio 5000 (or its predecessor RSLogix 5000), you get a complete, human-readable representation of your entire PLC project — every tag, every routine, every data type, every module configuration.

This matters because unlike the proprietary .ACD project file that only Studio 5000 can open, the L5X is standard XML that any text editor, parser, or script can read. It is the single most useful artifact in the Rockwell ecosystem for automation, documentation, and version control.

If you work with ControlLogix and you are not using L5X files, you are making your life harder than it needs to be.

L5X vs L5K: Which Format and When

Rockwell offers two export formats:

L5X (XML export): Structured XML with clear element hierarchy. Machine-parseable. Preserves all project data including comments, descriptions, and module configurations. This is the format you want for any programmatic work.

L5K (text export): An older, text-based format that reads more like structured assembly language. Each instruction is on its own line. Some people find it more readable for quick visual inspection. Supported since RSLogix 5000 v13.

When to use each:

Use Case Format Why
Version control (Git) L5X XML diff tools work well, structured merge possible
Automated documentation L5X XPath/XSLT make extraction trivial
Quick visual review L5K Reads like a program listing, less XML noise
Migration tooling L5X Every modern tool expects XML
Archival backup L5X Richer metadata, module configs included
Email to a colleague L5K Smaller file, opens in Notepad

For everything in this guide, we are working with L5X.

File Structure Deep-Dive

An L5X file has a predictable structure. Once you learn the hierarchy, you can navigate any project. Here is the top-level skeleton:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RSLogix5000Content SchemaRevision="1.0"
                    SoftwareRevision="33.00"
                    TargetName="MainController"
                    TargetType="Controller"
                    ContainsContext="true"
                    Owner="EdgeOps"
                    ExportDate="Wed Mar 26 14:30:00 2026"
                    ExportOptions="References NoRawData L5KData DecoratedData Context Dependencies ForceProtectedEncoding AllProjDocTrans">
  <Controller Use="Target" Name="MainController">
    <DataTypes>...</DataTypes>
    <Modules>...</Modules>
    <AddOnInstructionDefinitions>...</AddOnInstructionDefinitions>
    <Tags>...</Tags>
    <Programs>...</Programs>
    <Tasks>...</Tasks>
    <Trends>...</Trends>
    <QuickWatchLists>...</QuickWatchLists>
  </Controller>
</RSLogix5000Content>

Let's walk through each major section.

The Controller Element

The root <Controller> element contains the processor configuration:

<Controller Use="Target" Name="MainController"
            ProcessorType="1756-L75"
            MajorRev="33" MinorRev="11"
            TimeSlice="20" ShareUnusedTimeSlice="1"
            ProjectCreationDate="Mon Jan 15 08:00:00 2024"
            LastModifiedDate="Wed Mar 26 14:28:00 2026"
            SFCExecutionControl="CurrentActive"
            SFCRestartPosition="MostRecent"
            SFCLastScan="DontScan"
            ProjectSN="16#0042_A1B3"
            MatchProjectToController="false"
            CanUseRPIFromProducer="false"
            InhibitAutomaticFirmwareUpdate="0">

Key fields to note:

  • ProcessorType: The catalog number (1756-L75, 1756-L83E, 1769-L33ER, etc.). This tells you the hardware you are targeting.
  • MajorRev/MinorRev: Firmware version. Critical for compatibility. A v33 export will not import to a v20 processor.
  • ProjectSN: Serial number. Useful for matching a project to a specific processor.
  • TimeSlice: Percentage of scan time allocated to communications (vs logic execution).

DataTypes: User-Defined Types (UDTs)

The <DataTypes> section defines all custom data structures in the project. Every UDT shows up here:

<DataType Name="UDT_Valve" Family="NoFamily" Class="User">
  <Description>Standard valve control structure</Description>
  <Members>
    <Member Name="Cmd_Open" DataType="BOOL" Dimension="0"
            Radix="Decimal" Hidden="false" ExternalAccess="Read/Write">
      <Description>Command to open valve</Description>
    </Member>
    <Member Name="Fb_Open" DataType="BOOL" Dimension="0"
            Radix="Decimal" Hidden="false" ExternalAccess="Read/Write">
      <Description>Feedback - valve is open</Description>
    </Member>
    <Member Name="Interlock" DataType="BOOL" Dimension="0"
            Radix="Decimal" Hidden="false" ExternalAccess="Read/Write">
      <Description>Safety interlock - prevents operation</Description>
    </Member>
    <Member Name="FaultTimer" DataType="TIMER" Dimension="0"
            Radix="NullType" Hidden="false" ExternalAccess="Read/Write">
      <Description>Fault detection timer - valve failed to respond</Description>
    </Member>
  </Members>
</DataType>

This is one of the most valuable sections for documentation. UDTs reveal how the original programmer organized their thinking. A well-structured project will have UDTs for each equipment type — motors, valves, PID loops, analog inputs — with consistent member naming.

A poorly structured project will have everything in raw DINT and REAL arrays with no structure at all. Both are common. Both will be in your L5X.

Modules: I/O Configuration

The <Modules> section defines every I/O module in the system — both local and remote:

<Module Name="Rack1_Slot3_DI" CatalogNumber="1756-IB16D"
        Vendor="1" ProductType="7" ProductCode="205"
        Major="3" Minor="1"
        ParentModule="Local" ParentModPortId="1"
        Inhibited="false" MajorFault="true">
  <EKey State="CompatibleModule"/>
  <Ports>
    <Port Id="1" Address="3" Type="ICP" Upstream="true">
      <Bus Size="17"/>
    </Port>
  </Ports>
  <Communications>
    <ConfigTag ConfigSize="100" ExternalAccess="Read/Write">
      <!-- Module configuration data -->
    </ConfigTag>
    <Connections>
      <Connection Name="InputData" RPI="10000" Type="Input"
                  EventID="0" ProgrammaticallySendEventTrigger="false">
        <InputTag ExternalAccess="Read Only">
          <Data Format="Decorated">
            <!-- Input data structure definition -->
          </Data>
        </InputTag>
      </Connection>
    </Connections>
  </Communications>
</Module>

The critical information here:

  • CatalogNumber: The actual hardware part number (1756-IB16D = 16-point DC input diagnostic)
  • ParentModule and Address: Where it sits in the rack (Local = same rack as processor, or the name of a remote adapter)
  • RPI (Requested Packet Interval): In microseconds. An RPI of 10000 = 10ms update rate. This matters for understanding system timing.
  • MajorFault: Whether a module failure will fault the processor. Critical for safety analysis.

For remote I/O over EtherNet/IP, you will see modules with a ParentModule pointing to an EN2T or similar adapter, with IP address information in the connection configuration.

Tags: Controller-Scoped Data

The <Tags> section under <Controller> contains all controller-scoped tags — the global variables:

<Tag Name="Line1_Speed_SP" TagType="Base" DataType="REAL"
     Radix="Float" Constant="false" ExternalAccess="Read/Write">
  <Description>Line 1 speed setpoint (ft/min)</Description>
  <Data Format="L5K">
    <![CDATA[450.0]]>
  </Data>
  <Data Format="Decorated">
    <DataValue DataType="REAL" Radix="Float" Value="450.0"/>
  </Data>
</Tag>

<Tag Name="Valves" TagType="Base" DataType="UDT_Valve"
     Dimensions="24" Radix="NullType" Constant="false"
     ExternalAccess="Read/Write">
  <Description>All plant valve control structures</Description>
  <Data Format="Decorated">
    <Array DataType="UDT_Valve" Dimensions="24" Radix="NullType">
      <Element Index="[0]">
        <Structure DataType="UDT_Valve">
          <DataValueMember Name="Cmd_Open" DataType="BOOL" Value="0"/>
          <DataValueMember Name="Fb_Open" DataType="BOOL" Value="1"/>
          <DataValueMember Name="Interlock" DataType="BOOL" Value="0"/>
          <!-- Timer members... -->
        </Structure>
      </Element>
      <!-- Elements 1-23... -->
    </Array>
  </Data>
</Tag>

Notice the dual <Data> elements — one in L5K format (compact text) and one in Decorated format (verbose XML). The Decorated format is easier to parse programmatically. The L5K format is easier to read with your eyes.

Tag scope matters. Tags defined here at the controller level are accessible from any program. Tags defined within a <Program> element are local to that program. Understanding scope is critical when you are trying to trace data flow through a large project.

Programs and Routines: Where the Logic Lives

This is where the actual control logic resides:

<Programs>
  <Program Name="Line1_Control" Type="Normal"
           MainRoutineName="Main" FaultRoutineName="FaultHandler">
    <Tags>
      <!-- Program-scoped tags -->
      <Tag Name="Step" TagType="Base" DataType="DINT"
           Radix="Decimal" Constant="false" ExternalAccess="Read/Write">
        <Description>Current sequence step</Description>
      </Tag>
    </Tags>
    <Routines>
      <Routine Name="Main" Type="RLL">
        <RLLContent>
          <Rung Number="0" Type="N">
            <Comment>
              <![CDATA[*** LINE 1 MAIN SEQUENCE CONTROL ***]]>
            </Comment>
            <Text>
              <![CDATA[JSR(Inputs,0);]]>
            </Text>
          </Rung>
          <Rung Number="1" Type="N">
            <Comment>
              <![CDATA[Start pushbutton with seal-in]]>
            </Comment>
            <Text>
              <![CDATA[[XIC(Line1_Start_PB) ,XIC(Line1_Running) ]OTE(Line1_Running);]]>
            </Text>
          </Rung>
        </RLLContent>
      </Routine>
    </Routines>
  </Program>
</Programs>

Routine Types

Each <Routine> has a Type attribute that determines the logic representation:

RLL (Relay Ladder Logic): The most common. Rungs contain instruction text in a compact notation:

<Text><![CDATA[[XIC(Start),XIC(Ready)]OTE(Run);]]></Text>

This reads as: Start AND Ready -> Run. The brackets denote parallel branches.

FBD (Function Block Diagram): Visual blocks connected by wires. The XML stores block positions, pin connections, and parameter values:

<Routine Name="PID_Control" Type="FBD">
  <FBDContent>
    <Sheet Number="0" Description="Main PID Loop">
      <Block ID="1" Type="PIDE" Operand=".PID_Loop1"
             X="300" Y="100" Visible="true">
        <!-- Pin connections and configuration -->
      </Block>
    </Sheet>
  </FBDContent>
</Routine>

ST (Structured Text): IEC 61131-3 structured text stored as a single text block:

<Routine Name="Calculations" Type="ST">
  <STContent>
    <Line Number="0">
      <![CDATA[// Calculate production rate]]>
    </Line>
    <Line Number="1">
      <![CDATA[ProductionRate := (TotalCount - PrevCount) / ScanTime;]]>
    </Line>
  </STContent>
</Routine>

SFC (Sequential Function Chart): State-machine logic with steps, transitions, and actions. The most complex XML structure — SFC routines can contain embedded RLL, FBD, or ST within each step's actions.

Rung Instruction Encoding

The ladder logic text format in L5X uses a specific notation. Understanding it is essential for parsing:

[XIC(tag1) ,XIC(tag2) ]OTE(tag3);

Breaking this down:

  • [ and ] denote a branch level (parallel path start/end)
  • , separates parallel branches within a level
  • Instructions use the format MNEMONIC(operand1,operand2,...)
  • ; terminates the rung
  • Nested branches use nested brackets: [XIC(a) [OTE(b) ,OTE(c) ] ]

Common instructions in L5X rung text:

Instruction Meaning Operands
XIC Examine If Closed (tag)
XIO Examine If Open (tag)
OTE Output Energize (tag)
OTL Output Latch (tag)
OTU Output Unlatch (tag)
TON Timer On-Delay (timer_tag,preset,accum)
TOF Timer Off-Delay (timer_tag,preset,accum)
RTO Retentive Timer On (timer_tag,preset,accum)
CTU Count Up (counter_tag,preset,accum)
CTD Count Down (counter_tag,preset,accum)
MOV Move (source,dest)
ADD Add (sourceA,sourceB,dest)
CMP Compare (expression)
GRT Greater Than (sourceA,sourceB)
LES Less Than (sourceA,sourceB)
EQU Equal (sourceA,sourceB)
JSR Jump to Subroutine (routine_name,input_par,...)
RET Return from Subroutine (return_par,...)

Working with L5X Programmatically

Parsing with Python

Python's xml.etree.ElementTree handles L5X files well. Here is a practical example that extracts all tag definitions:

import xml.etree.ElementTree as ET

tree = ET.parse('project.L5X')
root = tree.getroot()
controller = root.find('.//Controller')

# Extract all controller-scoped tags
for tag in controller.findall('./Tags/Tag'):
    name = tag.get('Name')
    data_type = tag.get('DataType')
    desc_elem = tag.find('Description')
    description = desc_elem.text if desc_elem is not None else ''
    print(f'{name} ({data_type}): {description}')

Extracting a Cross-Reference

Building a cross-reference — finding every rung that uses a given tag — is the most common programmatic task:

import re
from collections import defaultdict

xref = defaultdict(list)

for program in controller.findall('.//Program'):
    prog_name = program.get('Name')
    for routine in program.findall('.//Routine'):
        routine_name = routine.get('Name')
        for rung in routine.findall('.//Rung'):
            rung_num = rung.get('Number')
            text_elem = rung.find('Text')
            if text_elem is not None and text_elem.text:
                rung_text = text_elem.text
                # Extract all tag references from instructions
                tags_used = re.findall(
                    r'(?:XIC|XIO|OTE|OTL|OTU|MOV|ADD|TON|CTU|CMP|GRT|LES|EQU)\(([^,\)]+)',
                    rung_text
                )
                for tag_ref in tags_used:
                    xref[tag_ref.strip()].append(
                        f'{prog_name}/{routine_name}/Rung {rung_num}'
                    )

Common Pitfalls

Large files crash naive parsers. A full plant L5X can be 50-200 MB. Use iterparse instead of loading the full DOM:

for event, elem in ET.iterparse('large_project.L5X', events=('end',)):
    if elem.tag == 'Rung':
        # Process rung
        elem.clear()  # Free memory

Decorated vs L5K data format. Tag values appear in both formats. The Decorated format is easier to parse but much more verbose. For bulk data extraction, the L5K format inside CDATA sections is more efficient.

CDATA sections break simple text searches. The rung text is wrapped in <![CDATA[...]]>. If you are doing plain-text grep on the XML, the CDATA wrapper can split across lines. Always parse the XML properly instead of treating it as plain text.

Add-On Instructions (AOIs) hide logic. An AOI call in a rung looks like a single instruction, but the AOI definition (in <AddOnInstructionDefinitions>) contains its own routines and tags. Your parser needs to recurse into AOIs to get the full picture.

Produced/Consumed tags are not in the program. Tags that communicate between controllers via Produced/Consumed connections appear in the tag database but are never referenced in ladder logic. They are data exchange points. Do not flag them as "unused" in your analysis.

Real-World Use Cases

Version Control with Git

L5X files are XML, which means Git can track them. Practical workflow:

  1. Export L5X after every program change
  2. Commit to a Git repository with a meaningful message
  3. Use git diff to see exactly what changed between versions

The diffs are not beautiful — XML is verbose — but they are functional. You can see that someone added a rung, changed a timer preset, or modified a tag description. This is infinitely better than the Rockwell standard of "save a copy with today's date in the filename."

For better diffs, consider exporting in L5K format alongside L5X. The text-based L5K format produces cleaner line-by-line diffs.

Automated Documentation

Extracting tag lists, I/O maps, and program structure from L5X is straightforward with XSLT or Python. You can generate:

  • Tag databases in Excel format with descriptions, data types, and scope
  • I/O module maps showing every point and its tag assignment
  • Program call trees (which routine calls which via JSR)
  • Alarm and interlock summaries

This documentation stays in sync with the actual program because it is generated from the same source file.

Migration Planning

When migrating from ControlLogix v20 to v33, or from one processor to another, the L5X file is your migration artifact. Export from the old system, modify the ProcessorType and revision attributes, and import to the new. Studio 5000 will flag incompatibilities.

For cross-platform migrations (e.g., ControlLogix to Siemens S7-1500), the L5X serves as the source-of-truth document that you translate from. Having the program in parseable XML is dramatically faster than working from printouts or screenshots.

What Controls Foundry Does with L5X Files

Controls Foundry parses L5X files as a core capability. When you upload an L5X to the platform, we extract:

  • Complete tag database with descriptions, data types, values, and scope
  • Full cross-reference linking every tag to every rung that uses it
  • I/O map matching physical modules to their tag assignments
  • Program structure showing the call tree and routine hierarchy
  • UDT definitions with member documentation
  • AOI library with internal logic analysis
  • Undocumented element report flagging tags and rungs without descriptions

Our L5X parser handles the full Studio 5000 v20-v35 schema range and has been tested against projects with 100,000+ tags and 10,000+ rungs. The analysis runs in seconds, not the hours it takes to do manually.

The forensics module goes further — it supports rung-by-rung annotation, behavioral recording, and I/O migration mapping, all anchored to the parsed L5X structure.

Get Started

Upload your L5X file to Controls Foundry for free instant analysis. No Studio 5000 license required. No Rockwell TechConnect. Just upload the export and get a complete, searchable, documented view of your program.

Upload your L5X file to Controls Foundry

#l5x#rockwell#controllogix#studio-5000#plc-programming#xml

Ready to analyze your PLC?

Upload your PLC program and get a free automated analysis in minutes.

Upload your PLC program for free analysis

Related Posts

© 2026 Controls Foundry. All rights reserved.

Built for controls engineers

Privacy Policy