5 min readRishi

Named Formulas and the With() Function: Power Fx Patterns for Faster Apps

Many canvas apps become slow because every screen carries old habits from the first prototype. OnStart fills variables that may never be used, formulas repeat the same lookups, and controls recalculate more than they need to. Named formulas and With() give you a cleaner way to express intent.

Named formulas make app state declarative

A named formula is a calculation, not a command. You define it in App.Formulas, and Power Fx recalculates it when its dependencies change. It is evaluated only when used, which makes it a strong replacement for many Set calls that run during app startup.

Traditional OnStart logic often tries to initialize everything just in case a screen needs it. That increases startup time and creates stale state problems. Named formulas let the app say what a value is instead of when to set it.

// App.Formulas
CurrentUser = User();
CurrentUserEmail = Lower(CurrentUser.Email);

MyOpenApprovals = Filter(
    Approvals,
    Approver.Email = CurrentUserEmail && Status = "Pending"
);

IsApprover = !IsBlank(
    LookUp(SecurityRoles, Email = CurrentUserEmail && Role = "Approver")
);

When a screen references MyOpenApprovals, the formula is evaluated. If no screen uses it, it does not need to run at startup. That is a better default for large apps.

OnStart should stop pretending to be a data layer

Use OnStart for actions that are truly actions. Authentication side effects, telemetry initialization, feature flag fetches, and one-time setup may still belong there. But values derived from connectors, controls, users, and other formulas usually read better as named formulas.

Before named formulas, a typical app might do too much up front.

// Old App.OnStart pattern
Set(varUser, User());
Set(varUserEmail, Lower(varUser.Email));
Set(
    varMyDepartment,
    LookUp(Employees, Email = varUserEmail).Department
);
ClearCollect(
    colMyRequests,
    Filter(Requests, Requester.Email = varUserEmail)
);
Set(varCanApprove, !IsBlank(LookUp(Approvers, Email = varUserEmail)));

The named formula version is more direct and less eager.

// App.Formulas replacement
CurrentUser = User();
CurrentUserEmail = Lower(CurrentUser.Email);
MyEmployeeProfile = LookUp(Employees, Email = CurrentUserEmail);
MyDepartment = MyEmployeeProfile.Department;
MyRequests = Filter(Requests, Requester.Email = CurrentUserEmail);
CanApprove = !IsBlank(LookUp(Approvers, Email = CurrentUserEmail));

This does not mean every collection disappears. Collections are still useful for local editing, offline work, staged rows, and small static reference data. The key is to stop using collections as a reflexive cache for every query.

With() removes repeated work inside a formula

Use With() when a calculation needs local names. It scopes intermediate values to one formula, which keeps the logic readable and avoids repeated lookups. This is especially helpful in display formulas, Patch calls, validation rules, and nested calculations.

Without With(), makers often repeat the same LookUp several times.

// Repeated lookup pattern
If(
    LookUp(Accounts, Account = galAccounts.Selected.Account).CreditHold,
    "Blocked",
    LookUp(Accounts, Account = galAccounts.Selected.Account).'Account Name'
)

With a local value, the formula reads like a small, intentional calculation.

// Scoped value pattern
With(
    {
        selectedAccount: LookUp(Accounts, Account = galAccounts.Selected.Account)
    },
    If(
        selectedAccount.CreditHold,
        "Blocked",
        selectedAccount.'Account Name'
    )
)

The performance gain depends on context, but the maintainability gain is immediate. If the lookup changes, there is one place to fix it.

Coalesce makes fallback logic boring in the best way

Fallback values should not become nested conditionals. Coalesce returns the first non-blank value. Use it with named formulas and With() to handle optional data, default labels, and missing configuration without sprawling If formulas.

// Display name fallback
With(
    {
        profile: LookUp(Employees, Email = CurrentUserEmail)
    },
    Coalesce(
        profile.PreferredName,
        profile.FullName,
        CurrentUser.FullName,
        "Unknown user"
    )
)

A small pattern like this reduces fragile blank checks across the app. It also makes review easier because the fallback order is explicit.

The migration path is incremental

Do not rewrite the whole app in one pass. Start with read-only variables in OnStart. Anything that looks like Set followed by a formula expression is a candidate. Move it to App.Formulas, update references, and test screens that depend on it.

Old patternBetter patternWhy it helps
Set for current user dataNamed formulaAuto-recalculates and avoids startup work
ClearCollect for large queriesDelegable named formulaKeeps filtering on the data source
Repeated LookUp in one formulaWith() local valueReduces duplication and clarifies intent
Nested blank checksCoalesceMakes fallback order explicit
Screen variables for derived labelsControl formula or named formulaRemoves stale state

Validate behavior after each move. Named formulas are declarative, so they should not contain behavior that depends on timing. If the original logic relied on an action happening once, keep it as an action or redesign the flow.

The gotchas are mostly about side effects and delegation

Named formulas cannot replace behavior formulas. They should not be used for Patch, Collect, Navigate, or notification side effects. Keep commands in behavior properties such as button OnSelect. Keep values in named formulas.

Delegation still matters. A named formula that filters a large Dataverse table must still be delegable. Moving a bad query from OnStart to App.Formulas improves startup behavior but does not make the query correct at scale.

Also be careful with naming. Use names that read like business concepts: CurrentUserEmail, MyOpenRequests, CanApprove, SelectedAccountRisk. Avoid prefixes that only describe storage mechanics. The point is to make formulas read like the app’s domain.

Review dependencies after migration. A named formula used by many screens becomes a shared contract, so keep it narrow, delegable, and predictable. If a value is expensive and only one screen needs it, keep the formula close to that screen instead of promoting it too early.

Faster apps usually come from less eager work and clearer dependency graphs. Named formulas remove unnecessary startup commands. With() makes local intent obvious. Coalesce makes fallback rules explicit. Together they turn Power Fx from scattered instructions into a maintainable model of the app.

Keep reading

Newsletter

New posts, straight to your inbox

One email per post. No spam, no tracking pixels, unsubscribe anytime.

Comments

No comments yet. Be the first.