When should you use an AOI? Later than most program standards say. Write the logic plainly until the same behaviour is in several places, you've had to change it more than once, and its interface has settled. An AOI used before that point commits you to a change process you don't need yet.
I've been paid to rewrite other people's code, including AOIs, and in some cases heavily nested ones, so I have opinions. Fair warning: I also wrote a pro-AOI introduction on SolisPLC, and it's still a good place to learn how they work. This post is the counterweight. It came out of episode 23, where an r/PLC meme about loving and hating AOIs led to the rule below.
The meme is a bell curve, and it's accurate. Early in your career, AOIs scare you. In the middle, they're magic and you put them everywhere. Later, you've tripped over your own code enough times that you get careful again. I'm trying to save you the middle.
When should you use an AOI?
Use an AOI when three things are true: the same behaviour exists in several places, you've changed it more than once, and its interface has stopped moving. Until then, keep plain logic you can edit online. Replication alone isn't enough, because a copy nobody changes costs you very little.
Here's how I said it on the episode: you want to avoid using AOIs and UDTs as much as possible until you're replicating your code so much, and changing it so often, that it becomes unmanageable to go and change every copy. There's no hard rule in the industry. So here is mine, as three tests:
- Same behaviour in several places. Not similar. The same. If half your pumps need a different interlock, you don't have one pump yet.
- Changed more than once. This shows you the cost you're trying to remove. If you've never had to change it, you don't know which parts will move.
- The interface has settled. You know the parameters. If you're still adding inputs every week, an AOI will make each of those changes more expensive, because logic edits go offline.
The good candidates haven't changed: a pump, a valve, a tank, or anything else you have many instances of with the same behaviour. Those usually pass all three tests. What usually fails is one-off sequence logic wrapped in an AOI because the standard said everything should be, or logic on a machine that's still being commissioned.
The language question is separate. Whether you write the inside in structured text against ladder logic doesn't change any of this.
What an AOI actually is
An AOI is a user-defined instruction with its own parameters, local tags and instance data, called like a built-in instruction. That makes it closest to a Siemens function block, not a plain function or a macro. Every call runs one shared definition, so a change to that definition reaches every instance.
I need to correct myself here. On the episode I called AOIs "syntactic sugar," a wrapper that doesn't really exist. That was too glib. Rockwell's help says an AOI behaves similarly to a built-in instruction and can be written in Ladder, Function Block Diagram or Structured Text. The February 2018 edition of Rockwell's 1756-PM010 manual gives it parameters, local tags and a separate data instance per use.
Sugar doesn't hold state. An AOI does. Siemens' S7-1200/1500 programming guideline (V1.6, December 2018) draws the same line on its side: an FC has no cyclic data storage, while an FB keeps its data in an instance data block.
| Keeps its own data between calls? | Closest equivalent | |
|---|---|---|
| Logix routine (JSR) | No, it uses program or controller tags | A function acting on shared data |
| Logix AOI | Yes, per instance | Siemens FB |
| Siemens FC | No | A plain function |
| Siemens FB | Yes, in an instance DB | Logix AOI |
What I meant on air still stands in a smaller form. If you studied C or Python, an AOI is just a function with state, and the reason it feels mysterious is mostly the interface. I got into that gap on ladder logic against C++. The ideas are decades old, and books like Clean Architecture cover them well. What's different is that the tools around them, such as editing and testing, are clunkier on the plant floor.
AOI or UDT, or both?
A UDT gives data a shape. An AOI adds behaviour and a change process. They pair well: the UDT describes the pump, and the AOI runs it. If you pass the first test but not the other two, start with the UDT, because it carries no logic and you can still troubleshoot everything online.
On the episode I grouped AOIs and UDTs together when I said to avoid them. I'd split them here. A UDT has a cost, since changing a structure that's used everywhere needs care. But it can't hide logic from a technician, and it doesn't turn one edit into a planned stop.
A sensible order for most plants goes like this:
- Plain logic and tags while you're learning what the machine needs.
- A UDT once the same group of data keeps showing up. Your routines get more readable, and nothing else about your maintenance changes.
- A routine with the UDT passed in by convention, if the logic repeats but is still changing.
- An AOI and UDT pair once the three tests pass.
A healthy program usually ends up with a mix of steps 2 and 4: a few well-tested AOIs for devices, and readable routines for everything specific to that machine.
Why can't you just edit it online?
Rockwell's help is blunt: "You can only edit AOI logic when offline." Updating to a newer revision has to be done offline too. So every AOI logic change means a planned download, and every instance is affected. On a line that can't stop, that's the real cost of adopting one too early.
Rockwell's 37.00 help says it in a tip on the Edit an Add-On Instruction page, and the 38.02 help agrees. The Open Instruction Logic page adds that you can't edit in the context of an instruction call, or when the instruction is source protected or sealed.
Think about what that means at the machine. A routine with a bad timer value is a two minute online edit. The same logic inside an AOI is a change you take offline, verify, schedule, and download, on a line someone would rather keep running. If that's a plant that runs around the clock, the next download window could be weeks away.
So people find a way around it, and this is where version sprawl begins. A new copy doesn't dodge the download, since the February 2018 manual says AOIs can only be created or modified offline. What a copy does dodge is the retest. Change the shared definition and you've changed every tank in the plant. Create a new one and only the new tank is at risk. On a tired Friday, I understand why someone makes that choice, because it's a rational call about risk. Do it enough times and you get the next section.
How tank 1.3 becomes tank 2.8
Logix leaves AOI versioning to you. You set the revision, a change affects every instance, the compare covers only metadata, and logic can still verify with wrong arguments. So people copy instead of change, and a few years later one program has several versions of the same tank and nobody knows which is right.
Here's what I've seen in the field. You open a program and there's an AOI called tank version 1.3. Then tank version 1.4, tank version 2.7 and tank version 2.8. At that point it isn't really an AOI anymore, since it has stopped doing what it was created to do. It's a monstrosity of different versions. I can't give you a count across plants, because this is a pattern I've seen rather than something I measured.
Rockwell's own documentation shows why nothing stops this:
- The revision is yours to manage. The New Add-On Instruction dialog has major and minor numbers, up to 40 characters of extended text, and a revision note. The February 2018 manual says the software doesn't manage the revision for you.
- A change reaches everything. The Updating to a Newer Revision page warns that a definition change reaches every instance of that instruction in the project.
- The compare is shallow. That same page says the Compare tab covers metadata only, so the developer has to enter revision notes for anyone else to make a good decision.
- Verification won't save you. Also from that page: after an overwrite, the parameters can differ and the logic can still verify. You have to check every instance's arguments yourself with a cross reference.
So the tool gives you a place to write a number, and the discipline is on you. Once the versions are in different projects on different lines, this stops being a programming problem. It's the same problem as any other version control for PLC programs, and it needs the same answer: one place that holds the real definition, and a record of what changed.
How Siemens handles the same problem
TIA Portal library types carry a documented Vx.y.z version: the first number for incompatible changes, the second for compatible extensions, and the third for fixes. Rockwell leaves that scheme up to you, so borrow it. Write the meaning of major and minor into your standard, and enforce it at review.
Siemens' Guideline for Library Handling in TIA Portal (V1.0, November 2017, section 8.6) defines the numbers. If the interface is reduced or changed in a way that isn't compatible, the main number moves. A compatible extension moves the second number. An error fix moves the third.
This is a contrast in documentation. I'm not claiming Siemens plants don't sprawl. Any library can. But you start with a scheme written down, and on the Rockwell side you have to write your own. Here's how I'd map it to the Logix fields:
| Change | Field | Rule |
|---|---|---|
| Parameter removed, renamed or reordered, or behaviour changed | Major | Check every instance. Retest fully |
| Parameter added, with old behaviour preserved | Minor | Check arguments. Test the new path |
| Bug fix, no interface change | Minor, with a note in Revision Note | Say what was wrong and how you know it's fixed |
| Validation state | Extended Text | Such as "site test passed" |
Keep the version out of the name. If it's called Tank_v2_8, you've made a new instruction, and nothing connects it to the one before.
The same thinking crosses platforms. Opto 22's 2021 guide maps a CODESYS function block to an AOI too, and I covered what else changes on that platform.
How deep should AOIs nest?
Rockwell's February 2018 manual allows 16 levels and recommends limiting nesting to eight. That's a ceiling, not a target. Every level means a change in an inner AOI reaches every caller above it, with no test pipeline to catch a break. Keep nesting shallow enough that you can test the whole chain by hand.
Nesting is one of my pet peeves. There are reasons to call a function from a function, and traditional software does it all the time. But traditional software usually has tools that run iterative tests and confirm everything still works after a change. We usually don't.
So you change an inner AOI for one good reason, and now every AOI that calls it has changed too, and so has every project that imports that version. The February 2018 manual also says the nested and calling instructions must be the same class, so standard calls standard, and safety calls safety. And it's your responsibility to know where an AOI is used.
Here's my test for depth: can a technician who didn't write it follow a fault from the instance down to the logic that caused it, during a breakdown? And can you test the whole chain after a change to the innermost block? If either answer is no, it's too deep, whatever the manual allows.
How do you test an AOI?
Mostly by hand. Rockwell offers emulation, including FactoryTalk Logix Echo for 5580 controllers, but I found no first-party AOI unit-test framework. TwinCAT's open source TcUnit shows what one looks like. Until Logix has an equivalent, build a small test project with one instance per case, and run it every time.
The tooling has moved on some. Automation World reported in December 2022 on Logix Echo v2, which emulates ControlLogix 5580 controllers. Rockwell has also published open source tools for version control on L5X files. What I couldn't find, in Rockwell's documents or on GitHub, is a first-party framework for AOI unit tests. That's an absence in what I searched, so if you know of one, tell me. On the TwinCAT side, TcUnit is an open source xUnit framework, and it's a good picture of what we're missing.
As I said on the episode, there are power users trying LLMs for this now, and I expect that to get better. For now, here's a manual bench that works:
- Create a test project with the AOI and nothing else that isn't needed. Use Echo or a bench controller.
- Add one instance per case: normal run, each interlock, each fault, and each edge you've been burned by.
- Write the expected outputs next to each case, in a routine or a table.
- Check how it behaves when the rung goes false, and on the first scan after a mode change.
- Keep the project with the AOI, and rerun it for every revision. Note the result in Extended Text.
An hour of that on one AOI is cheap against a change that reaches every tank in the plant.
Audit your AOI library in an afternoon
List every definition and its revision, find the near-duplicates, count nesting depth, read the revision notes, then pick what to retire. You won't fix a sprawled library in an afternoon, but you will know how bad it is and which two or three AOIs are causing most of the risk.
Take one representative project and do this:
- Export the list of AOIs with revision, vendor and last edit date. Open the Assets folder in the Controller Organizer and write them down if you have to.
- Group by what they do, not by name. Tank_v2, TankCtrl and Tank_New may be one instruction.
- Count the instances of each with a cross reference. Anything with one instance failed the first test.
- Trace nesting depth for your five most used AOIs.
- Read the revision notes. Blank notes on a revised AOI are a finding.
- Check whether source protection is hiding logic nobody has the key to.
- Compare the same AOI across two lines. Same name and different logic is the worst case.
- Pick one version of each to keep, one to retire, and the next download window to do it.
Then write the three tests into your standard, along with what major and minor mean. That's usually all the governance a mid-sized plant needs.
So, when should you use add-on instructions? When you've earned them, with repetition, repeated change and a settled interface. Before that, plain logic you can edit online is a good standard too.




