Learn how to architect Power Automate Desktop flows using subflows — named, callable units of logic with formal input and output parameters. Stop copy-pasting actions and start building automations you can actually maintain.

Picture this: you've built a 300-action Power Automate Desktop flow that automates your company's order processing system. It opens a legacy ERP application, logs in, navigates to the order queue, processes each order, updates a spreadsheet, and sends a confirmation. It works beautifully — until the business asks you to add the same ERP login logic to four other flows. Now you're copying and pasting dozens of actions, maintaining five copies of the same code, and the day someone changes the ERP password or the login screen layout, you're spending your afternoon updating every single one of them.
This is the problem that subflows solve. Subflows are Power Automate Desktop's mechanism for breaking a monolithic flow into named, callable units of logic — essentially functions or procedures within a single desktop flow. They let you write logic once, call it from multiple places, pass data in through input variables, and receive results back through output variables. The result is automation code that's easier to read, easier to test, easier to maintain, and far less likely to harbor subtle bugs from divergent copies.
By the end of this lesson, you'll be able to architect desktop flows like a professional: decomposing complex automations into clean, reusable subflows, passing parameters between them, returning values, handling errors at the subflow level, and applying naming and organizational conventions that will make your flows maintainable six months after you wrote them.
What you'll learn:
This lesson assumes you're comfortable with the Power Automate Desktop environment and have built at least a few flows of moderate complexity. You should understand variables, lists, and data tables at a practitioner level, since subflows communicate almost entirely through variables. Some familiarity with UI elements and selectors will also be useful, as many of the practical examples involve desktop UI automation.
Before diving into mechanics, let's be precise about what a subflow is in Power Automate Desktop, because the concept is sometimes confused with related features.
A subflow lives inside a single desktop flow file. It is not a separate flow — it doesn't appear in your flow list on the Power Automate portal as its own entity. Think of a desktop flow as a container, and subflows as named sections within that container. Every desktop flow starts with a special subflow called Main, which is the entry point that executes when you run the flow. Everything that happens, happens because Main either does it directly or calls a subflow that does it.
When Power Automate Desktop encounters a Run subflow action, it jumps execution to the first action of that named subflow, runs through it sequentially, and then returns control to the action immediately after the Run subflow call. It's exactly the function-call model you know from any programming language.
Note
Subflows are not the same as calling a separate desktop flow from the Power Automate cloud or from another desktop flow. That's a different mechanism involving the "Run a flow built with Power Automate Desktop" connector action. Subflows are intra-flow modularity; child flows are inter-flow orchestration.
What subflows are not:
That last point is critical and we'll return to it often.
The most important thing to internalize about subflows is that every variable in a desktop flow — whether created in Main, in a subflow named LoginToERP, or in a subflow named ProcessOrder — exists in the same shared namespace. There is no variable shadowing, no local scope, no garbage collection when a subflow returns.
This has implications in both directions:
It means subflows can read any variable set elsewhere. If Main sets %CurrentUser% before calling a subflow, that subflow can use %CurrentUser% without needing to receive it as a formal parameter. This is convenient but creates hidden dependencies that make subflows hard to test in isolation.
It means subflows can accidentally overwrite variables. If two subflows both create a loop counter variable called %Counter%, the second subflow to run will overwrite whatever value the first left behind. If Main also has a %Counter%, you now have a collision that produces subtle, hard-to-reproduce bugs.
Key insight
Design your variable naming scheme before you write a single action. Prefix subflow-specific variables with a short identifier matching the subflow name. If the subflow is ValidateOrderData, internal variables become %VOD_RowIndex%, %VOD_IsValid%, etc. This doesn't create true scope, but it makes collisions immediately obvious and subflow behavior easier to reason about.
The formal parameter mechanism (which we'll configure next) is the clean alternative — it makes data flow explicit and documents the interface of each subflow.
Let's build this concretely. Suppose you're automating a process that requires logging into a web portal, and you need to do that login from multiple points in your flow. Here's how you'd extract it into a reusable subflow.
In the Power Automate Desktop designer, look at the subflow panel on the left side of the canvas. By default you'll see Main listed there. Click the + button (Add subflow) at the top of that panel. A dialog appears asking for a name.
Name it something precise and action-oriented: LoginToPortal, not Login or Subflow1. The name should tell the next developer (or future you) exactly what this subflow does. Click OK.
You'll see an empty subflow canvas. The subflow panel now shows both Main and LoginToPortal.
Click on LoginToPortal in the subflow panel to make it the active canvas. Now add your login actions directly here — navigating to the login URL, filling the username field, filling the password field, clicking the login button, and waiting for the confirmation element.
A realistic login subflow might look like this in the action list:
Launch new Microsoft Edge
Navigate to: %PortalURL%
Wait for web page to load
Fill text field on web page
UI element: Username field
Text: %PortalUsername%
Fill text field on web page
UI element: Password field
Text: %PortalPassword%
Click link on web page
UI element: Login button
Wait for element to appear on web page
UI element: Dashboard header
Timeout: 30 seconds
Notice that this subflow references %PortalURL%, %PortalUsername%, and %PortalPassword%. Right now, those are just global variables that must exist before the subflow is called. In the next section, we'll formalize them as input parameters.
Switch back to Main by clicking it in the subflow panel. From the action search bar, search for "Run subflow." Drag it onto the canvas or double-click to add it. The action dialog shows a dropdown labeled Subflow name — select LoginToPortal.
At this point, if %PortalURL%, %PortalUsername%, and %PortalPassword% are set before this action in Main, your flow will work. Run it and observe that execution jumps into LoginToPortal, executes all those actions, then returns control to whatever follows the Run subflow action in Main.
Relying on global variables works for small flows but breaks down quickly. The professional approach is to define input parameters that a subflow expects and output variables that it produces. This makes the subflow's interface explicit and testable.
Tip
Think of subflow input/output variables as the function signature. They're the contract between the caller (Main or another subflow) and the called subflow. Document them carefully — your future self will thank you.
When you're viewing a subflow's canvas, look at the top of the subflow panel where you'll see the subflow name. Right-click on the subflow name and select Edit. This opens the subflow properties dialog.
Here you'll find two sections: Input variables and Output variables.
Click + Add input variable. A row appears where you specify:
UsernameAdd three input variables to LoginToPortal: PortalURL (Text), Username (Text), and Password (Text).
Now update the actions inside the subflow to reference %Username% and %Password% (the local parameter names) rather than %PortalUsername% and %PortalPassword%. Inside the subflow, these are indistinguishable from regular variables — Power Automate Desktop populates them when the subflow is called.
Go back to Main and double-click your Run subflow action to edit it. Now that LoginToPortal has defined input variables, the dialog expands to show fields for each one. Map them:
%PortalURL% (or type the URL literal directly)%ConfigUsername%%ConfigPassword%This is explicit. Anyone reading Main can see exactly what data LoginToPortal needs.
Output variables let subflows return data to their caller. Suppose your login subflow should return a boolean indicating whether login succeeded (useful if you're handling failures gracefully rather than throwing errors).
In the subflow properties, click + Add output variable. Name it LoginSucceeded, type Boolean.
Inside the subflow, after the login sequence, add a Set variable action that sets %LoginSucceeded% to True. If you have error handling in the subflow that catches a failed login, set %LoginSucceeded% to False in the error path.
Back in the Run subflow action in Main, you'll now see the output variables listed. Power Automate Desktop automatically creates a variable in the calling context (Main's scope) to receive the value — you can rename it to something contextually meaningful, like %PortalLoginSucceeded%.
After the Run subflow action in Main, add a condition:
If %PortalLoginSucceeded% = False then
Display message: "Login failed. Check credentials and retry."
Stop flow
End
Warning
Output variables from subflows are written into the global variable space under whatever name you specified in the Run subflow dialog. Be careful not to give them names that collide with existing variables. The prefixing convention mentioned earlier is your friend here.
Once you understand the mechanics, the real question is architectural: how do you design subflows so they're genuinely reusable, not just code that happens to live in a separate section?
The library subflow pattern treats certain subflows as utility functions — general-purpose logic that could be called from many different places. These subflows should:
FormatCurrencyValue should format a currency value. It shouldn't also validate it, log it, and update a spreadsheet.Consider a subflow that reads a row from a data table, validates the key fields, and returns a cleaned record. Here's what its interface might look like:
Input variables:
RawDataRow (DataTable row / Custom object)RequiredFields (List of text — field names that must be non-empty)Output variables:
CleanedRow (Custom object)IsValid (Boolean)ValidationErrors (List of text)This subflow can now be called from any flow that needs row validation — an invoice processor, an HR data importer, a product catalog updater. The caller doesn't need to know how validation works; it just passes a row and gets back a validity flag and any errors.
Key insight
The test of a good subflow is whether you can explain its interface in one sentence without mentioning the broader flow context. "Given a raw data table row and a list of required field names, returns a cleaned row, a validity flag, and any validation errors" passes the test. "Gets the data from the spreadsheet that was opened earlier" fails it.
In a complex flow with fifteen or twenty subflows, the subflow panel can get unwieldy. Power Automate Desktop doesn't yet support folders for subflows, so ordering and naming carry all the organizational weight.
Use a naming convention that groups related subflows visually:
Main
Setup_LoadConfiguration
Setup_InitializeVariables
Auth_LoginToPortal
Auth_LoginToERP
Auth_LogoutAll
Data_ReadOrderSheet
Data_ValidateOrderRow
Data_WriteResultsSheet
ERP_NavigateToOrderEntry
ERP_SubmitOrder
ERP_ConfirmSubmission
Util_FormatCurrencyValue
Util_LogToFile
Util_HandleCriticalError
The prefix groups them: Setup, Auth, Data, ERP, Util. Everything in the "Auth" group handles authentication; everything in "Util" is a general-purpose utility. This visual grouping is the next best thing to actual folders.
Subflows can call other subflows. Main can call ProcessOrders, which calls ValidateOrderRow and SubmitToERP, each of which calls Util_LogToFile. This nesting is powerful but comes with a warning.
Power Automate Desktop doesn't publicly document a hard limit on call depth, but in practice, deeply nested subflow chains (five or more levels deep) can make debugging significantly harder because the execution path becomes difficult to trace. More importantly, since all variables share global scope, deeply nested calls are fertile ground for naming collisions.
Warning
Avoid recursive subflow calls. Power Automate Desktop does not support recursion safely — a subflow calling itself (or a cycle of subflows calling each other) will eventually exhaust execution resources without a clear error message. If you need recursive-style logic, convert it to an explicit loop in Main.
For loops that process a list, the standard pattern is to keep the loop in the calling subflow (or Main) and call a processing subflow once per iteration:
Main:
Call Data_ReadOrderSheet → %OrderTable%
Call Setup_InitializeVariables
For each %OrderRow% in %OrderTable%
Call Data_ValidateOrderRow(RawDataRow: %OrderRow%) → %IsValid%, %CleanedRow%
If %IsValid% = True
Call ERP_SubmitOrder(OrderData: %CleanedRow%) → %SubmissionResult%
Call Data_WriteResultsSheet(Row: %OrderRow%, Result: %SubmissionResult%)
Else
Call Util_LogToFile(Message: "Validation failed for order " + %OrderRow%[OrderID])
End
End
Call Auth_LogoutAll
This structure reads almost like pseudocode, which is exactly the goal. Main becomes an executive summary of the automation — anyone can read it and understand what happens at the 10,000-foot level without knowing any implementation details.
Subflows interact with error handling in ways that require deliberate design. When an action inside a subflow fails, the default behavior is to stop the entire flow and report an error. That's often not what you want — you may want the subflow to handle the error internally, return a failure signal, and let the caller decide what to do.
Power Automate Desktop's On Block Error action wraps a group of actions in a try-catch construct. You can place this inside a subflow to catch errors locally:
SubFlow: ERP_SubmitOrder
Input: OrderData (Custom object)
Output: SubmitSuccess (Boolean), ErrorMessage (Text)
Set variable %SubmitSuccess% to False
Set variable %ErrorMessage% to ""
On Block Error:
Store error in variable %ERP_Error%
Set variable %SubmitSuccess% to False
Set variable %ErrorMessage% to %ERP_Error%[Message]
Go to end of block
End On Block Error
[Actions to navigate ERP and submit order]
Set variable %SubmitSuccess% to True
With this pattern, the subflow always returns — it never crashes the parent flow. The caller gets %SubmitSuccess% as False and %ErrorMessage% describing what went wrong. The caller (Main or a processing subflow) can then decide whether to retry, skip, or abort.
This is the subflow equivalent of the "parse don't validate" principle: the subflow is responsible for its own failure modes, and it communicates them through its defined outputs, not through unhandled exceptions.
Tip
For utility subflows that perform simple operations (like formatting a value), letting errors propagate naturally (i.e., not using On Block Error) is often fine — if the formatting logic fails, something is genuinely wrong that the developer should fix. Reserve the try-catch pattern for subflows that interact with external systems where partial failures are expected.
If a subflow opens a resource — a browser, an Excel file, an application window — it should close it before returning, whether it succeeded or failed. This is especially important in Excel automation where leaving file handles open causes corruption.
Structure your subflow to guarantee cleanup:
SubFlow: Data_ReadOrderSheet
Output: OrderTable (DataTable), ReadSuccess (Boolean)
On Block Error:
Store error in %ReadError%
Set %ReadSuccess% to False
Close Excel ← cleanup even on error
Go to end of block
End On Block Error
Open Excel with: %OrderFilePath%
Read all from worksheet into %OrderTable%
Close Excel ← cleanup on success
Set %ReadSuccess% to True
Let's build a meaningful, complete example. The scenario: your company processes 50-200 orders per day from a spreadsheet. Each order needs to be submitted to a legacy Windows ERP application. Results (success/failure for each row) are written back to the spreadsheet. Failed orders are logged to a text file.
This project ties together web automation patterns, Excel manipulation, and legacy application automation — and uses subflows to keep it manageable.
Main
├── Setup_LoadConfig (reads config file, sets global variables)
├── Setup_ValidateEnvironment (checks required apps are available)
├── Auth_LoginToERP (opens ERP, logs in)
├── Data_ReadOrderSheet (opens Excel, returns DataTable)
├── [Loop over orders]
│ ├── Data_ValidateRow (validates a single order row)
│ ├── ERP_SubmitOrder (submits one order to ERP)
│ └── Data_WriteResult (writes result back to Excel row)
├── Util_WriteErrorLog (writes failed orders to text file)
├── Auth_LogoutERP (closes ERP session)
└── Data_CloseOrderSheet (saves and closes Excel)
// === SETUP ===
Run subflow: Setup_LoadConfig
Run subflow: Setup_ValidateEnvironment
→ EnvReady (Boolean)
If %EnvReady% = False then
Display message: "Environment validation failed. See log."
Stop flow
End
// === AUTHENTICATION ===
Run subflow: Auth_LoginToERP
Input: ERPPath: %Config_ERPPath%
Username: %Config_ERPUser%
Password: %Config_ERPPass%
→ ERPLoginSuccess (Boolean)
If %ERPLoginSuccess% = False then
Run subflow: Util_WriteErrorLog
Input: Message: "ERP Login failed at " + %CurrentDateTime%
Stop flow
End
// === DATA LOADING ===
Run subflow: Data_ReadOrderSheet
Input: FilePath: %Config_OrderFilePath%
SheetName: %Config_SheetName%
→ OrderTable (DataTable), ReadSuccess (Boolean)
If %ReadSuccess% = False then
Run subflow: Util_WriteErrorLog
Input: Message: "Could not read order sheet"
Stop flow
End
// === ORDER PROCESSING ===
Set variable %FailedOrders% to [] // empty list
Set variable %ProcessedCount% to 0
For each %Row% in %OrderTable%
Run subflow: Data_ValidateRow
Input: OrderRow: %Row%
→ IsValid (Boolean), ValidationMsg (Text)
If %IsValid% = True then
Run subflow: ERP_SubmitOrder
Input: OrderRow: %Row%
→ SubmitSuccess (Boolean), ERPOrderID (Text), SubmitError (Text)
Run subflow: Data_WriteResult
Input: Row: %Row%
Success: %SubmitSuccess%
OrderID: %ERPOrderID%
ErrorMsg: %SubmitError%
If %SubmitSuccess% = False then
Add item %Row%[OrderNumber] to list %FailedOrders%
End
Else
Add item %Row%[OrderNumber] + ": " + %ValidationMsg% to list %FailedOrders%
Run subflow: Data_WriteResult
Input: Row: %Row%
Success: False
OrderID: ""
ErrorMsg: "Validation: " + %ValidationMsg%
End
Increase variable %ProcessedCount% by 1
End
// === CLEANUP ===
Run subflow: Auth_LogoutERP
Run subflow: Data_CloseOrderSheet
Input: Save: True
// === REPORTING ===
If Count of %FailedOrders% > 0 then
Run subflow: Util_WriteErrorLog
Input: Message: "Failed orders: " + Join(%FailedOrders%, ", ")
End
Display message: "Processing complete. " + %ProcessedCount% + " orders processed. " + Count of %FailedOrders% + " failures."
Reading Main, you understand exactly what the flow does without seeing a single UI interaction or Excel formula. That's the power of well-designed subflows.
Let's look at one subflow in detail to illustrate the pattern fully.
Subflow: Data_ValidateRow
Input variables:
- OrderRow (Custom object / DataTable row)
Output variables:
- IsValid (Boolean)
- ValidationMsg (Text)
// Initialize outputs
Set variable %IsValid% to True
Set variable %ValidationMsg% to ""
Set variable %VAL_Errors% to [] // local list, prefixed VAL_
// Check required fields
For each %VAL_Field% in ["OrderNumber", "CustomerID", "ProductCode", "Quantity", "UnitPrice"]
If %OrderRow%[%VAL_Field%] = "" or %OrderRow%[%VAL_Field%] is empty then
Add item "Missing: " + %VAL_Field% to list %VAL_Errors%
Set variable %IsValid% to False
End
End
// Check numeric fields
If %OrderRow%[Quantity] is not numeric then
Add item "Quantity must be numeric" to list %VAL_Errors%
Set variable %IsValid% to False
End
If %OrderRow%[UnitPrice] is not numeric then
Add item "UnitPrice must be numeric" to list %VAL_Errors%
Set variable %IsValid% to False
End
// Compile error message
If Count of %VAL_Errors% > 0 then
Set variable %ValidationMsg% to Join(%VAL_Errors%, "; ")
End
Notice the VAL_ prefix on internal variables — they won't collide with variables in Main or other subflows. The outputs %IsValid% and %ValidationMsg% have clean, descriptive names that make sense to the caller.
Symptom: A variable that worked correctly in one part of the flow has the wrong value later, and you can't figure out where it changed.
Cause: Two subflows are using the same variable name for different purposes, and the second one overwrites the first.
Fix: Apply a consistent prefix convention for all subflow-internal variables. Use the flow debugger to step through execution and watch variable values change — Power Automate Desktop's variable watcher pane shows all current values in real time.
Symptom: After calling a subflow, the expected variable doesn't exist or contains its old value.
Cause: You defined an output variable in the subflow properties but didn't map it in the Run subflow action configuration. Unmapped outputs are silently discarded.
Fix: Always open the Run subflow action and confirm that every output variable is mapped to a receiving variable name. The dialog makes this explicit — don't dismiss it without checking.
Symptom: A subflow works when called from one point in the flow but crashes when called from another, because a global variable it depends on hasn't been set yet at that call site.
Cause: The subflow reads a global variable that isn't a declared input parameter, making the dependency invisible to the caller.
Fix: Audit each subflow for any variable it reads that isn't one of its declared input parameters. Either add those as formal input parameters, or ensure they're set in a Setup subflow that always runs first.
Symptom: The flow fails partway through, and the next run immediately fails because an Excel file is locked or an application window is in an unexpected state.
Cause: A subflow that opened a resource failed before reaching the cleanup actions.
Fix: Wrap resource-opening subflows in an On Block Error block that guarantees cleanup runs. This is covered in the error handling section of our error handling deep dive.
Symptom: You have a subflow that does twenty-five different things. It takes ten minutes to understand what it does, and any change risks breaking something unrelated.
Cause: Treating "subflow" as "a section of code" rather than "a single coherent responsibility."
Fix: Apply the single-responsibility principle. If you can't describe what a subflow does in one sentence, it's doing too much. Split it.
Tip
A good rule of thumb: if a subflow has more than 30-40 actions, ask whether it should be two subflows. If it has more than 50, it almost certainly should be.
Symptom: The flow runs indefinitely or crashes with a vague execution error.
Cause: Subflow A calls Subflow B, which calls Subflow A (directly or through a chain).
Fix: Draw out your subflow call graph if you're not sure. It should be a directed acyclic graph — no cycles. If you think you need recursion, use a loop with an explicit stack (a List variable) instead.
Build a modular file-processing automation using the subflow architecture you've learned. Here are the requirements:
Scenario: You receive a CSV of employee timesheets daily. The file contains columns: EmployeeID, EmployeeName, HoursWorked, HourlyRate, and Department. Your flow should validate each row, calculate gross pay, and write a summary report to a new Excel file.
Build these subflows:
Setup_LoadConfig — sets the input file path, output file path, and minimum/maximum valid hours (e.g., 0–80)
Data_ReadTimesheetCSV — opens the CSV and returns a DataTable. Handle the case where the file doesn't exist gracefully.
Data_ValidateTimesheetRow — validates that EmployeeID is not empty, HoursWorked is numeric and within range, HourlyRate is numeric and positive. Returns IsValid and ValidationMsg.
Calc_ComputeGrossPay — takes HoursWorked and HourlyRate as inputs, applies overtime (1.5x for hours over 40), returns GrossPay and RegularPay and OvertimePay.
Report_WriteOutputRow — writes a row to the output Excel with columns: EmployeeID, EmployeeName, Department, RegularPay, OvertimePay, GrossPay, ValidationStatus.
Main — calls all of the above in the correct order, loops over the timesheet rows, handles failures gracefully.
What to observe:
Subflows transform Power Automate Desktop from a recording tool into a genuine automation development platform. The core principles to carry forward:
With subflows mastered, you're ready to take on complex production automation scenarios. If you're working on flows that interact with a web portal as part of a larger process, revisiting web automation patterns with a subflow-aware architecture will be immediately rewarding. For flows that are growing into enterprise-scale desktop automation programs, the patterns for managing variables and data structures in the complete practitioner's guide to variables and data tables will pair tightly with everything here.
When your desktop automation programs grow large enough that you're thinking about sharing logic across multiple separate desktop flows (not just within one), look at orchestrating child flows and scoped execution — it's the cloud-side counterpart to what you've built here, and many of the same architectural principles apply.
Build modularly. Test subflows independently. Document your interfaces. Your future self, and your colleagues, will thank you.