When a second PLC_PRG appears in a CODESYS project

6 min read · Published November 5, 2026

You never need a second PLC_PRG: the numbered duplicate is an ordinary POU that received the default name while a PLC_PRG already existed. Check its call context and the task configuration, then delete it if nothing runs it or give each output exactly one writer if a task does.

You never need a second PLC_PRG. The numbered object is an ordinary POU that received the default name while a PLC_PRG already existed, so the editor appended a number to keep object names unique. Nothing in the runtime calls for two. The question that matters is whether anything executes the extra object: if no call and no task references it, it is dead weight; if a task runs it, two POUs may write the same output and the last write wins. Work the checks below in order: call context, task configuration, write locations, then a cleanup that leaves one entry point and one writer per output.

Why is there a second PLC_PRG in my CODESYS project?

You added a POU while a PLC_PRG already existed, the default name collided, and the editor appended a number to keep object names unique; the runtime has no use for a second entry point, and nothing in your project required one. The CoDeSys 2.3 programming manual documents that the first POU created in a new project is named PLC_PRG automatically. Every POU you add after that is offered a default name, and object names in one project must be unique. Accept the default while a PLC_PRG exists and you get the same name with a number attached. The result is an ordinary program POU, the same kind of object as the first. Only the name misleads: it suggests a second entry point, and the runtime has none.

What does the CODESYS runtime execute each cycle?

The runtime calls PLC_PRG once per cycle by default, and every other POU runs only when PLC_PRG calls it or a task calls it; an application with no entry point reports an error at build or download. PLC_PRG is to a CODESYS application what main() is to a C program: the place execution starts each cycle. IEC 61131-3 defines the PROGRAM as the top-level POU type; the development tool supplies the default name. A program's local variables carry their values from one scan to the next. A second POU that nothing calls is never scanned: its code executes zero times per cycle, its outputs never move, and online monitoring shows frozen values. Logic that reads correctly offline and does nothing online is the classic signature of a POU nobody calls.

How do I check whether the extra POU runs?

Open the object's call context in the cross-reference or call-tree view and read the result; a POU with no call site and no task entry executes zero instructions per cycle, whatever its name suggests. Three outcomes, one decision each:

Call context result What it means What to do
No call site, no task entry Accidental object Delete it, or rename and keep it as a sub-program
Called from PLC_PRG Works as a sub-program Rename it to a functional name; only the name is wrong
Listed in a task It executes Go to the task check below

Confirm online before you decide. Go online with the machine running and watch the extra POU's variables through one full machine cycle. Values that never move, on a machine that does, mean the POU is never scanned.

What happens when the extra POU sits in a task?

On a target whose processor and CODESYS installation support tasks, any POU can run cyclically, so PLC_PRG is a convention and not a requirement; a task-assigned duplicate does execute, in its own task or in the order one task lists its programs. Open the task configuration and read the program list of every task:

  • A task lists PLC_PRG only: the extra POU is dead code. Delete it, or call it from PLC_PRG if you want the code.
  • A task lists the extra POU, and PLC_PRG is present: both execute, each where the configuration puts them. Check the outputs for two writers.
  • The target has no task configuration: the runtime relies on PLC_PRG alone, and the extra POU cannot run unless called.

Keep the name PLC_PRG for the main program even where the task configuration accepts any POU. CODESYS users expect it, and a differently named main program slows every later fault search. For how the controller sequences POUs when several tasks are involved, see PLC execution order across task layers.

What does a worked example with two writing POUs show?

The last write in the order the task lists its programs wins, and the earlier write is overwritten inside the same cycle; in the example below, a setpoint of 850 units per minute is written first, 0 second, and the drive receives 0 units per minute every cycle. Scenario: a filling line with one speed reference, iSpeedRef in this example, in units per minute. The task list holds two programs: PLC_PRG first, the numbered duplicate second. PLC_PRG computes a running setpoint of 850 units per minute and writes it. The duplicate still holds an early stop routine, and it writes 0 units per minute to the same variable.

Position in the task list POU Write to iSpeedRef Value after the write
1 PLC_PRG 850 850
2 Numbered duplicate 0 0

Carry the cycle through: 850 units per minute written, then 0 written to the same variable, so the value the drive reads at the end of the task list is 0. Two writes, one variable, and the second write replaces the first inside the same cycle. Repeat that for 500 cycles and the drive never sees 850; the line sits at standstill speed and PLC_PRG looks broken. The cross-reference for iSpeedRef shows two write locations. Remove the write in the duplicate, then download and watch one full machine cycle: iSpeedRef reads 850 units per minute with the line running, and the cross-reference shows one write location.

When do you genuinely need a second program POU?

You need a second program when you split the project into logical elements or give one job the language it fits; each becomes a named sub-program called from PLC_PRG, and for multiple copies of one logic you use function block instances instead. Ladder Diagram or Instruction List suits a job with many outputs driven by logic statements. Structured Text suits math on the same machine. Each piece becomes a separately named POU called from PLC_PRG, so the project keeps one entry point and a call tree a reader can follow. A PROGRAM is the wrong object for several copies of one logic, because exactly one instance of a program exists in memory: use a function block and declare one instance per copy. For that pattern, see creating CODESYS function block instances dynamically.

How do I clean the project up and confirm one entry point?

Cross-reference every variable the extra POU writes, decide delete or rename from the checks above, rebuild to zero errors, then download and monitor online until the kept POU's values track the machine through one full cycle.

  1. Record the write locations of every output the extra POU touches, from the cross-reference.
  2. Delete the POU when nothing references it; rename it to a functional name when you intend to keep the code.
  3. If you keep it, add its call inside PLC_PRG at the point where its outputs must be computed.
  4. Remove it from any task that also runs PLC_PRG, unless the split across tasks is deliberate.
  5. Rebuild, and confirm there is no missing-entry-point message.
  6. Download, go online, and check: the POU's values change with machine state, and each output shows one write location in the cross-reference.

Take the machine to a safe state before the download. A POU that starts running after your fix can command motion that was silent before, and one full cycle of observation tells you what changed.

Recommendation: treat a numbered PLC_PRG as an accident until the call context proves otherwise. Delete it when nothing references it, rename it to a functional name when you keep the code, and allow a second task-assigned program only after the cross-reference shows exactly one write location per output.

Questions

Why did CODESYS append a number to my new POU name?

Object names in one project must be unique. The new-object dialog offers a default name from the PLC_PRG family, and when that name is taken the editor appends a number to avoid the collision. Type a functional name in that dialog before you confirm, and the duplicate never appears.

Can I rename the extra POU instead of deleting it?

Yes, renaming keeps the code. After the rename, run a project-wide search for the old name and rebuild: any call still carrying the old name surfaces as an unknown identifier at build time, and the search tells you exactly where to fix it.

Does the unused duplicate cost the controller anything?

It runs no code, so the cycle time is unaffected. The object and its declared variables still download with the project and occupy controller memory until you delete them, and every reader of the project spends time working out what the object is for. Deleting it removes both costs.

What should I do if the download fails after the cleanup?

Stop editing and collect the facts first: the project archive, the target runtime version, and the exact build or download message. Take those to the controller manufacturer's official support channel. Do not keep retrying downloads on a running machine; each attempt changes what the controller executes.

Planning information, not legal or engineering advice. Rules differ by place: check codes and permits with your local authority and your licensed trades.

Keep reading

Build your signAsk for a quote