Power Apps App.OnStart: What Belongs There—and What Doesn’t

M
Mark JonesAuthorPublished Jun 23, 2022
3,630

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.Formulas for reusable named formulas and values that should stay up to date;
  • App.StartScreen to choose the first screen;
  • a screen’s OnVisible for work that belongs to that screen;
  • App.OnStart only for startup behaviour that must set state or perform an action; and
  • Concurrent only 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:

  1. the first screen waits for data it did not really need; or
  2. 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 OnVisible or direct control formulas;
  • independent connector calls → a carefully reviewed Concurrent block;
  • 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 OnVisible logic;
  • 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

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.