Power Apps App.OnStart: What Belongs There—and What Doesn’t
At a Glance
- Target Audience
- Power Apps canvas-app makers, support teams and Power Platform administrators
- Problem Solved
- Separates valid startup behaviour from named formulas, first-screen routing, screen-specific work and error handling using current Microsoft guidance.
- Use Case
- Repair a slow or unreliable canvas-app startup formula and remove timing dependencies around globals, navigation and connector calls.
App.OnStart is not a general-purpose drawer for every formula a Power Apps canvas app needs before somebody clicks a button.
Microsoft now warns that OnStart can slow app loading and recommends more declarative alternatives for several common jobs. That does not make OnStart useless. It means the right question is: which startup work genuinely has to run once, and which work belongs somewhere else?
Fact-checked against Microsoft Learn and its official Power Fx code samples on 7 August 2026.
The short answer
Use:
App.Formulasfor reusable named formulas and values that should stay up to date;App.StartScreento choose the first screen;- a screen’s
OnVisiblefor work that belongs to that screen; App.OnStartonly for startup behaviour that must set state or perform an action; andConcurrentonly when its connector calls are genuinely independent.
With nonblocking OnStart enabled—which Microsoft says is the default—a screen can become interactive before OnStart finishes. Any formula that assumes an OnStart variable is already populated deserves review.
What App.OnStart actually does
App.OnStart is a behaviour formula that runs when the user starts the app. Microsoft lists retrieving and caching data in collections and setting global variables as valid uses.
But startup is no longer necessarily a single blocking sequence. Microsoft documents that, with nonblocking OnStart, other app rules can run at the same time. A long connector call can still be working while the first screen is already visible.
That creates two common problems:
- the first screen waits for data it did not really need; or
- the screen reads a blank or old global variable because startup has not finished.
The fix is not a magic timer. It is to put each job in the property that matches its lifecycle.
The decision table
| Requirement | Best starting point | Reason |
|---|---|---|
| Reusable calculation or theme value | App.Formulas |
Declarative, always available and recalculated when needed |
| Choose the first screen | App.StartScreen |
Microsoft’s replacement for navigation in OnStart |
| Load data needed only by one screen | Screen OnVisible or direct formula |
Avoids paying the cost for every app start |
| Set a true global variable once | App.OnStart |
Behaviour and mutable global state may be appropriate |
| Load several independent sources | Concurrent(...) in a behaviour formula |
Connector calls can overlap when no argument depends on another |
| Handle a save failure | IfError(...) around the operation |
Can replace or respond to that formula’s error |
| Log unhandled app errors | App.OnError |
Global error-reporting hook, not a replacement value |
Move reusable values to App.Formulas
Named formulas are often a better home for colours, current-user lookups, feature flags and reusable calculations.
Microsoft describes named formulas as:
- available without waiting for
OnStart; - automatically updated when their dependencies change;
- immutable from elsewhere in the app; and
- eligible to be calculated only when needed.
Before: mutable values in OnStart
Set(AppPrimaryColour, ColorValue("#0F6CBD"));
Set(CurrentUserEmail, Lower(User().Email))
After: named formulas
Set the Formulas property of the App object:
AppPrimaryColour = ColorValue("#0F6CBD");
CurrentUserEmail = Lower(User().Email);
Then controls can refer to AppPrimaryColour or CurrentUserEmail directly.
Use a variable when the app must deliberately change a value. Use a named formula when the value should be derived from other values and remain consistent.
Do not move a large, non-delegable data load into a named formula and call the performance problem solved. The query still needs a sensible shape.
Use StartScreen for the opening screen
Microsoft has retired the use of Navigate inside App.OnStart. Existing apps can still work and a retired setting remains available for a limited time, but the supported design is App.StartScreen.
That is a specific retirement. The Navigate function is still used elsewhere in canvas apps.
For a simple launch parameter:
If(
Param("admin-mode") = "1",
AdminScreen,
HomeScreen
)
Set that formula on App.StartScreen; do not wrap it in Navigate.
StartScreen is a data-flow property, so it cannot contain behaviour functions. Microsoft also says global variables and collections created by OnStart are not available there. Named formulas are available.
If a connector lookup is essential to decide the first screen, keep it quick and handle errors. A safer pattern is often to open a neutral loading/home screen and resolve optional navigation after the app is usable.
Also remember: choosing a screen is not authorisation. A hidden admin screen does not protect the data source. Enforce access in SharePoint, Dataverse or the system that owns the data.
Use Concurrent only for independent calls
Concurrent can reduce waiting time when connector or Dataverse calls do not depend on one another.
An official Microsoft pattern looks like this:
Concurrent(
ClearCollect(colAccounts, Accounts),
ClearCollect(colUsers, Users),
ClearCollect(colSettings, AppSettings)
)
Do not copy that shape blindly. Microsoft says the start and finish order of concurrent arguments is unpredictable. These two calls therefore do not belong in separate arguments if the second depends on the first.
Bad dependency:
Concurrent(
ClearCollect(colDepartments, Departments),
ClearCollect(colPeople, Filter(People, DepartmentId in colDepartments.Id))
)
Make the dependency explicit instead:
ClearCollect(colDepartments, Departments);
ClearCollect(
colPeople,
Filter(People, DepartmentId in colDepartments.Id)
)
Or revisit whether both collections are needed at startup at all.
Concurrent changes scheduling; it does not remove connector throttling, delegation limits or the cost of downloading unnecessary rows.
Load screen-specific data when the screen needs it
If only the Orders screen uses recent orders, loading them on every app start is wasteful.
A screen’s OnVisible can load or refresh its own data. Guard repeated work if users navigate back and forth:
If(
IsEmpty(colRecentOrders),
ClearCollect(
colRecentOrders,
Filter(Orders, Status = "Open")
)
)
Whether that filter delegates depends on the data source, column type and operator. Test the real query with enough data to expose delegation warnings. A fast demo against 20 rows proves very little.
For a concrete example of preserving exact records and avoiding non-unique lookups, see the Power Apps time-tracker guide.
Keep OnStart small and observable
Valid OnStart work tends to be short and explicit. For example:
- initialise a mutable session variable;
- record a launch parameter that the app intentionally changes later;
- perform a small set of independent startup calls; or
- write a trace event that helps support identify the published app version.
Avoid:
- downloading entire lists “just in case”;
- serial connector calls with no dependency;
- hiding startup behind a fixed-duration timer;
- using UI variables as a security boundary;
- setting dozens of theme constants that belong in
App.Formulas; and - navigating from
OnStart.
Handle errors at the right level
Use IfError around an operation when the current formula must respond to a failure:
IfError(
Patch(
Orders,
Defaults(Orders),
{ Title: txtOrderTitle.Text }
),
Notify(
"The order was not saved. Try again or contact support.",
NotificationType.Error
)
)
Use App.OnError for global reporting or logging. Microsoft’s documented pattern uses FirstError and Trace:
Trace(
$"Error {FirstError.Message} in {FirstError.Source}"
);
Error(FirstError)
The final Error(FirstError) rethrows the error so the normal error behaviour is not silently swallowed.
OnError cannot turn a failed calculation into a successful value after the fact. That is the job of IfError at the operation boundary.
A practical OnStart refactor
Work through the current formula one statement at a time.
1. Inventory every statement
Label it as:
- constant or derived value;
- first-screen decision;
- screen-specific data;
- mutable global state;
- connector call;
- telemetry; or
- unexplained legacy code.
2. Move work to the narrowest lifecycle
- constants and derived values →
App.Formulas; - first-screen decision →
App.StartScreen; - one-screen work → that screen’s
OnVisibleor direct control formulas; - independent connector calls → a carefully reviewed
Concurrentblock; - unused legacy code → remove after proving it is unused.
3. Reduce data
Filter at the source, select only required columns and avoid materialising a collection when controls can read a delegable formula directly.
4. Add an explicit loading state only where needed
If a screen truly cannot work until a call completes, show a loading state tied to the actual operation—not an arbitrary number of milliseconds.
5. Measure the published app
Microsoft’s Monitor and browser network tools can show connector calls and timing. Compare cold starts, not only repeated Studio runs that benefit from cached data.
Test checklist
Test the published app with:
- a cold start in a private browser window;
- a normal user rather than only the maker;
- a slow network profile;
- an empty result set;
- a connector error;
- deep-link parameters;
- return navigation to screens with
OnVisiblelogic; - enough rows to expose delegation problems; and
- the actual mobile client if mobile use matters.
Record the app version, device, user type and timings. Do not publish an invented “X% faster” claim from one warm run.
Frequently asked questions
Is App.OnStart deprecated?
No. Microsoft still documents App.OnStart. It warns that the property can create load-time performance problems and recommends alternatives for several common uses.
Is Navigate retired in Power Apps?
Using Navigate inside App.OnStart is retired. Existing apps can still work, but new and repaired apps should use App.StartScreen to choose the opening screen. Navigate remains valid elsewhere.
Is Concurrent always faster?
No. It can reduce waiting time for independent connector calls. It cannot safely parallelise dependent work, remove throttling or make an oversized query efficient.
Should I put all user-profile logic in OnStart?
Not automatically. A reusable current-user value can be a named formula. Data-source permissions, not a screen-selection variable, must enforce access.
Build startup logic you can support
The Power Apps Builders Space focuses on the Power Fx, data and reliability decisions that separate a quick demo from an app your team can keep using.
Microsoft sources used
- App object in Power Apps
- Concurrent function
- Fast calculations in canvas apps
- Power Apps code optimisation guidance
- Power Fx error handling
Source boundary: The product behaviour and samples above are grounded in Microsoft Learn and its code-sample index. Table, connector, control and screen names are illustrative. Performance depends on the published app, data source, query, device, network and tenant configuration.
