DEV Community

Sofiane Mebchour
Sofiane Mebchour

Posted on

Delegation in Power Apps, Explained With Real Queries

If you've built more than two canvas apps, you've seen it: the blue squiggly underline in the formula bar, a yellow warning triangle, and a message about "delegation". Most makers hover it once, shrug, and move on — until a user reports that a record they know exists doesn't show up in the app.

That missing record is delegation. It's the single most common source of "the app is losing data" bugs in Power Apps, and it's entirely predictable once you understand what's happening under the hood.

What delegation actually is

When your gallery's Items property says:

Filter(Orders, Status = "Open")
Enter fullscreen mode Exit fullscreen mode

there are two ways Power Apps can evaluate it:

  1. Server-side (delegated): Power Apps translates your Power Fx into a query the data source understands (OData for SharePoint, FetchXML-ish for Dataverse, SQL for... SQL) and sends it over the wire. The server filters its million rows and returns only the matches. Fast, complete, correct.

  2. Client-side (not delegated): Power Apps can't translate part of the formula, so it downloads rows from the source and runs the filter locally on your device.

Here's the trap: in case 2, Power Apps does not download the whole table. It downloads the first 500 rows (the default data row limit, configurable up to 2000) and runs your formula on that subset only.

So Filter(Orders, Status = "Open") against a 10,000-row SharePoint list with a non-delegable condition doesn't error out. It returns the open orders among the first 500 rows — and silently ignores the other 9,500. No error message. No empty gallery. Just quietly wrong results.

That's what the blue underline is warning you about. It's not a style suggestion. It's "your query will return incomplete data on large tables."

The 500/2000 limit

The cap for non-delegable queries lives in Settings > General > "Data row limit for non-delegable queries". Default is 500, maximum is 2000.

Raising it to 2000 is a legitimate move when your table genuinely stays small — a reference list of 800 categories, say. But treat it as what it is: a stopgap. At row 2001 the bug comes back, and pulling 2000 rows on every screen load hurts performance on mobile. I wrote up the setting and its trade-offs in more detail in this guide on the data row limit.

The durable fix is always the same: make the query delegable so the cap never applies.

Delegation depends on the data source

This is the part most tutorials skip: delegability is per connector, per function, per operator. The same formula can be fully delegable against Dataverse and non-delegable against SharePoint.

Rough hierarchy, from most to least delegation support:

  • Dataverse — the widest support: Filter, Sort, LookUp, comparison operators, StartsWith, And/Or/Not, aggregates like Sum/CountRows in many cases.
  • SQL Server — very good: most comparisons, StartsWith, sorting.
  • SharePoint — noticeably narrower: =, <, >, StartsWith are fine, but Search, in on text, IsBlank in filters, and sorting by complex column types are not delegable. Choice and lookup columns have extra quirks.
  • Excel / imported static data — essentially nothing delegates. Fine for lookup tables under 500 rows, dangerous for anything else.

Practical consequence: if you're choosing a backend for a table that will grow past a couple thousand rows and you're on SharePoint "because it's free", delegation is the tax you'll pay. It's manageable — but only if you write your formulas around it.

Real queries that break, and their delegable rewrites

1. Search vs StartsWith

// ❌ Not delegable on SharePoint
Filter(Orders, Search(CustomerName, txtSearch.Text) > 0)

// ❌ Also not delegable on SharePoint (the "in" operator on text)
Filter(Orders, txtSearch.Text in CustomerName)

// ✅ Delegable
Filter(Orders, StartsWith(CustomerName, txtSearch.Text))
Enter fullscreen mode Exit fullscreen mode

Yes, StartsWith only matches from the beginning of the string. For a search box, that's usually acceptable — and it's infinitely better than a "contains" search that only searches the first 500 rows.

2. Functions inside the filter condition

// ❌ Not delegable — the source can't evaluate Text() or Year()
Filter(Orders, Text(OrderDate, "yyyy") = "2026")

// ✅ Delegable — compare against plain values the server understands
Filter(Orders, OrderDate >= Date(2026, 1, 1), OrderDate < Date(2027, 1, 1))
Enter fullscreen mode Exit fullscreen mode

The rule of thumb: the column stays naked on the left side. The moment you wrap the column in a function (Text(), Lower(), Year(), arithmetic), the server can't translate it and evaluation falls back to the client.

Same idea with computed values — pre-compute them into a variable before the query:

// ❌ Function call evaluated per-row inside the filter
Filter(Tasks, DueDate < DateAdd(Today(), 7, TimeUnit.Days))

// ✅ Compute once, filter on the plain value
Set(varWeekOut, DateAdd(Today(), 7, TimeUnit.Days));
Filter(Tasks, DueDate < varWeekOut)
Enter fullscreen mode Exit fullscreen mode

(Whether the first version delegates varies by connector — the second version removes the doubt everywhere.)

3. LookUp on a non-delegable condition

// ❌ IsBlank inside a filter is not delegable on SharePoint
LookUp(Orders, IsBlank(ClosedDate))

// ✅ Compare to Blank() directly (delegable on most sources)
LookUp(Orders, ClosedDate = Blank())
Enter fullscreen mode Exit fullscreen mode

4. Filter server-side first, then do the fancy stuff locally

You don't have to make everything delegable — you have to make the part that touches the big table delegable. Once the result set is small, client-side evaluation is harmless:

// Delegable date-range filter reduces 50,000 rows to ~200,
// then the non-delegable sort runs safely on the small result
SortByColumns(
    Filter(Orders, OrderDate >= varMonthStart, OrderDate < varMonthEnd),
    "CustomerName",
    SortOrder.Ascending
)
Enter fullscreen mode Exit fullscreen mode

This "narrow on the server, refine on the client" pattern solves 80% of real-world delegation problems without any exotic workaround.

The collections workaround (and its caveats)

The classic hack for "I need all 8,000 rows locally" is chunked ClearCollect:

ClearCollect(colAll, Filter(Orders, ID >= 1, ID <= 2000));
Collect(colAll, Filter(Orders, ID >= 2001, ID <= 4000));
Collect(colAll, Filter(Orders, ID >= 4001, ID <= 6000));
Collect(colAll, Filter(Orders, ID >= 6001, ID <= 8000));
Enter fullscreen mode Exit fullscreen mode

Each chunk is a delegable query under the row limit, so you end up with the full table in a collection. It works. I've used it. And you should know exactly what you're signing up for:

  • It's a snapshot. The collection is stale the moment it's built; edits by other users won't appear until you re-collect.
  • Load time and memory. Thousands of rows over the wire on app start, on every device.
  • The ID ranges are a maintenance trap. Gaps are fine, but growth isn't — at row 8,001 you're silently dropping data again, which is the exact bug you were trying to fix.
  • It caps out. Somewhere in the tens of thousands of rows this stops being viable at all.

Use it for genuinely bounded, read-mostly datasets (a product catalog, a location list). For anything that grows unbounded, fix the query instead — or move the table to Dataverse where the query can delegate.

A workflow that keeps you honest

  1. Never ignore the blue underline. Hover it — Studio tells you which part of the formula is non-delegable.
  2. Test with realistic volume. Delegation bugs are invisible with 50 test rows. Load 1,000+ rows before you trust a screen.
  3. Know your connector's delegable list. Check Microsoft's per-connector documentation when in doubt — SharePoint's list is short enough to memorize.
  4. Keep the column bare on the left side of every comparison inside Filter/LookUp.

I keep a fuller checklist of fixes in the delegation warning guide — it's the write-up I send to teammates when the blue underline shows up in a code review.

Wrap-up

Delegation isn't a Power Apps quirk to memorize around — it's the fundamental question of where your query runs. Server-side means complete and fast; client-side means the first 500-2000 rows and a silent data bug. Write filters the server can translate, narrow before you refine, and treat the row limit setting as a last resort.

For quick syntax reference while rewriting formulas (including the delegable-friendly patterns above, in both EN and FR locale syntax), I maintain a free Power Fx expression cheatsheet as part of PowerBlocks, my Power Apps component library — no login needed.


What's the nastiest delegation bug you've shipped to production? Share yours in the comments.

Top comments (0)