All articles

Canvas App Delegation: Why Your Gallery Only Shows 500 Records

The yellow triangle isn't just a warning — it means your app is silently returning incomplete data. Here's the complete guide to delegation limits, which functions are safe, and five workarounds ranked by reliability.

· 10 min read

You build a canvas app. You add a gallery connected to a Dataverse table. You filter it. It works perfectly with your 50 test records. Then it goes to production with 3,000 records, and users start saying “I can’t find my record.”

The record is there. Your app just isn’t looking at it. Welcome to delegation.

What’s Actually Happening

When a canvas app queries a data source, some operations can run on the server (delegated) and some can’t. When an operation can’t be delegated, Power Apps pulls the first N rows to the device and runs the filter locally — on that incomplete subset.

The default N is 500 rows. You can increase it to 2,000 in app settings, but that’s the ceiling. If your table has 5,000 records and your filter isn’t delegable, your app evaluates only the first 500 (or 2,000) and ignores the rest. No error. No warning at runtime. Just silently missing data.

The yellow triangle in the formula bar is the only signal during development. In production, there’s nothing.

The Delegation Cheat Sheet

Not all functions delegate the same way across data sources. Here’s what actually works:

Filter and LookUp Functions

FunctionDataverseSharePointSQL Server
Filter (simple comparison)YesYesYes
Filter with And/OrYesYesYes
Filter with NotYesNoYes
LookUpYesYesYes
SearchYesNoYes

Text Functions Inside Filter

FunctionDataverseSharePointSQL Server
StartsWithYesYesYes
EndsWithNoNoNo
Contains (text column)No*NoNo
IsBlankYesNoYes
LenNoNoNo
Left, Right, MidNoNoNo

* Dataverse supports server-side Contains on certain column types through the in operator, but the standard Contains function in Power Fx is not delegable. Use Search for full-text queries against Dataverse.

Aggregate and Sort Functions

FunctionDataverseSharePointSQL Server
SortYesYesYes
SortByColumnsYesYesYes
CountRowsNoNoNo
Sum, Average, Min, MaxNoNoNo
FirstYes*Yes*Yes*
FirstNNoNoNo

* First on a delegable query is fine — it takes the first result from the server. First on a non-delegable query only looks at the local subset.

Operators Inside Filter

OperatorDataverseSharePointSQL Server
=, <>, <, >, <=, >=YesYesYes
exactinYesNoYes
in (text contains)YesNoNo

The 2,000-Row Limit

You can change the limit in App settings → General → Data row limit for non-delegable queries. The maximum is 2,000.

But this is a band-aid, not a fix. If your table grows past 2,000 records, you’re back to the same problem. Worse — raising the limit means your app downloads more data to the device on every query, which makes it slower for everyone.

I set this to 2,000 on apps where I know the data will stay small. For anything else, I fix the delegation issue properly.

Five Workarounds, Ranked

1. Use Dataverse Views (Best Option)

If your data is in Dataverse, create a personal or system view that applies your filter server-side, then use Filter(Accounts, "Active Accounts") to reference it. Views run entirely on the server with no delegation limits.

You can also use Filter with only delegable operators and let Dataverse handle everything. The key: stick to simple comparisons, StartsWith, and Search. If you need more complex filtering, build a view.

This is the right answer 80% of the time. No workarounds, no performance hit, no row limits.

2. Push Filtering to Power Automate

Create a flow that accepts filter parameters, queries Dataverse with FetchXML or the built-in list rows action (which has no delegation limits), and returns the results to the app.

sequenceDiagram participant App as Canvas App participant PA as Power Automate participant DV as Dataverse App->>PA: Send filter parameters PA->>DV: Query with FetchXML (no row limits) DV-->>PA: Filtered results PA-->>App: Return as JSON App->>App: Display results

This works for any data source and any filter complexity. The downside is latency — the round trip through Power Automate adds a few seconds. For search-as-you-type scenarios, this is too slow. For “click search and wait” scenarios, it works fine.

3. Use StartsWith Instead of Contains

If users are searching by text and you’re using Contains, switch to StartsWith. It’s delegable against Dataverse, SharePoint, and SQL.

// Non-delegable — downloads 500 rows and filters locally
Filter(Accounts, Contains(Name, SearchBox.Text))

// Delegable — server handles the filter
Filter(Accounts, StartsWith(Name, SearchBox.Text))

The tradeoff is obvious: searching for “Contoso” won’t match “Adventure Contoso Inc.” with StartsWith. But for most name-based searches, it’s good enough.

For Dataverse specifically, you can also use Search(Accounts, SearchBox.Text, "name") which does a server-side search across configured search columns.

4. Chunked Collection Loading

When you genuinely need all data locally (for client-side calculations, complex filtering, or offline scenarios), load it in chunks:

// Clear existing data
Clear(colAllAccounts);

// Load in pages of 2000
ForAll(
    Sequence(10),
    Collect(
        colAllAccounts,
        Filter(
            Accounts,
            ID > Last(colAllAccounts).ID
        )
    )
);

This loads up to 20,000 records (10 pages x 2,000) into a local collection. It’s slow on first load, but once the data is local, all filters and sorts work without delegation issues.

Use this sparingly. Loading thousands of records to a mobile device kills performance and battery. It’s a last resort, not a default pattern.

5. Accept the Limit and Design Around It

Sometimes the right answer is: don’t fight it. If your table has 10,000 records, maybe users shouldn’t be browsing all of them. Add required filters that narrow the results below the delegation threshold before showing anything:

  • Require a date range
  • Require a status filter
  • Require a text search (with StartsWith)
  • Show “too many results, please refine your search” if the result count is near the limit

This is a UX solution, not a technical one. But it often leads to a better app because it forces focused, intentional queries instead of “show me everything.”

The Decision Flowchart

When you hit a delegation warning, work through this:

graph TD A{"Is the data source\nDataverse?"} -->|Yes| B{"Can you rewrite with\ndelegable operators?"} B -->|Yes| C["Use comparisons,\nStartsWith, Search — done"] B -->|No| D{"Can you create\na Dataverse view?"} A -->|No| D D -->|Yes| E["Build a server-side\nview — done"] D -->|No| F{"Is data under\n2,000 rows?"} F -->|Yes| G["Raise row limit\nto 2,000"] F -->|No| H{"Is a few seconds\nof latency OK?"} H -->|Yes| I["Power Automate\nas data broker"] H -->|No| J{"Need all data\non the device?"} J -->|Yes| K["Chunked collection\nloading"] J -->|No| L["Switch to model-driven\nor custom page"]

When to Switch to Model-Driven

If you find yourself constantly fighting delegation — building workarounds, loading data in chunks, adding required filters to keep result sets small — stop and ask whether canvas is the right app type for this use case.

Model-driven apps run views on the server. Advanced Find, quick find, column sorting — all server-side, no row limits. If your app is “show a list of records, let users filter and sort, click to open a form,” model-driven does this out of the box with zero delegation concerns.

I covered the full canvas vs. model-driven decision in a separate article.

One tool that helps catch delegation issues: Power Apps Monitor (make.powerapps.com → Apps → select your app → Monitor). It shows you exactly which data calls are being delegated and which are running locally. Run your app with Monitor attached and watch the network calls — you’ll see which filters are hitting the server and which ones are downloading everything to the client.

Share this article LinkedIn X / Twitter

Related articles