How to Avoid List Throttling in SharePoint

Worth a direct correction before anything else: the “indexed column” PowerShell example that circulates for this topic (`Set-PnPListItem` with a value) doesn’t create an index at all — it just sets a field’s value on one item, something entirely different. This post covers the real mechanism for indexing a column, the real cap that mechanism runs into on large lists, and a genuinely current change worth knowing about — SharePoint now creates some indexes automatically, without anyone running a script at all.

In this post: What throttling actually is · The real way to index a column · Automatic indexing: a genuinely current change · Pagination that actually works · CAML queries with a row limit · Power Automate-specific triggers · When would you actually use this? · Related reading


What throttling actually is

The list view threshold — 5,000 items — is a server-side limit on how many items a single query can scan, not how many items a list can hold. A list with 500,000 items works fine as long as no single query needs to touch more than 5,000 of them to produce its result. SharePoint Online enforces this at a flat 5,000 with no way to raise it; on-premises administrators can technically raise it, but doing so genuinely risks the database contention the limit exists to prevent in the first place, so it’s rarely a good idea in practice.


The real way to index a column

There’s no dedicated `Add-PnPIndexedField` cmdlet — the real pattern retrieves the field object directly and calls its own `EnableIndex()` method:

Connect-PnPOnline -Url "https://yourtenant.sharepoint.com/sites/yoursite" -Interactive

$field = Get-PnPField -List "LargeList" -Identity "Status"
$field.EnableIndex()
$field.Context.ExecuteQuery()

Worth knowing the two real caps on this before relying on it at scale: a list can carry at most 20 indexed columns, and a column can’t be indexed at all once the list already holds more than 20,000 items — indexing has to happen while the list is still small, or the index needs to be added before the list grows past that point. A filter built against a column that isn’t indexed forces SharePoint to scan the entire list to evaluate it, which is the single most common cause of a throttling error that otherwise looks unpredictable.


SharePoint now creates some indexes automatically — when a view is saved with a sort or filter column, or when a user sorts a large list in the modern experience. A list that behaves fine today can still hit an index cap later without anyone having run a single PowerShell command.

Automatic indexing: a genuinely current change

Worth knowing before assuming every index in a list was deliberately added: SharePoint now creates indexes on its own in two situations — when a view is saved with a column used for sorting or filtering, and when a user sorts a large list directly in the modern experience. For lists under 20,000 items this happens immediately; for larger lists it happens in the background, which means a query can behave inconsistently for a short window right after a new view gets saved, not because anything is broken but because the index genuinely hasn’t finished building yet. Practical effect worth knowing: the real 20-column index cap can get consumed by views built casually over time, not just by columns someone deliberately indexed — worth checking `Get-PnPField` for existing `Indexed` columns before assuming there’s room left for one more.


Pagination that actually works

REST’s `@odata.nextLink` pattern is the standard way to walk a large list in pages rather than one oversized query:

async function getItems(url, items = []) {
    const response = await fetch(url, {
        headers: { "Accept": "application/json;odata=nometadata" }
    });
    if (!response.ok) {
        throw new Error(`Query failed: ${response.status} ${response.statusText}`);
    }
    const data = await response.json();
    items.push(...data.value);
    if (data["@odata.nextLink"]) {
        return getItems(data["@odata.nextLink"], items);
    }
    return items;
}

Worth pairing this with `$top` set well under 5,000 per page (500-1,000 is a reasonable starting point) rather than relying on the default page size, since a page that’s too large can itself brush against the threshold on a list with wide or heavily-formatted columns.


CAML queries with a row limit

For CSOM/PowerShell contexts, a CAML query’s `RowLimit` attribute combined with a filter on an indexed column is the real, current way to keep a query under the threshold rather than pulling everything and filtering client-side:

$query = New-Object Microsoft.SharePoint.Client.CamlQuery
$query.ViewXml = "Open2000"
$items = Get-PnPListItem -List "LargeList" -Query $query.ViewXml

`RowLimit` caps how many items come back per call, not how many the query is allowed to scan — the filter on an indexed column (`Status` here) is what keeps the scan itself under the threshold. A `RowLimit` with no indexed filter behind it still throttles once the underlying scan crosses 5,000 items, which is a genuinely common mistake worth flagging directly.


Power Automate-specific triggers

The “Get items” action’s own OData filter and sort options only help if they target an indexed column — setting them against an unindexed one still forces the same full scan the threshold blocks, the flow just fails with a throttling error instead of a REST call doing the same thing. Worth checking the “Top Count” setting explicitly (it defaults to 100, not the list’s full contents) rather than assuming a “Get items” action already returns everything — a flow that appears to skip items intermittently is a common symptom of this default going unnoticed rather than an actual bug in the flow.


When would you actually use this?
  • A list is approaching or already past 5,000 items and reports/flows against it are starting to fail intermittently — index the columns actually being filtered or sorted on first, before reaching for pagination.
  • A migration or bulk export needs to walk an entire large list — REST pagination via `@odata.nextLink`, not a single unbounded query, is the pattern that actually completes.
  • A list is still small but expected to grow substantially — index the columns it’ll eventually be filtered on now, while it’s still under the 20,000-item indexing cap, rather than after growth makes it impossible.
  • The list is genuinely being used as a database with complex, ever-changing query needs rather than a collaboration list — worth treating that as a signal to move the high-volume data to an actual database and keep SharePoint for the collaboration layer, not as a problem pagination alone will keep solving indefinitely.


Throttling itself isn’t the problem to solve — it’s a symptom pointing at an unindexed filter or an unpaginated query somewhere upstream. Index the columns actually being queried, page anything that walks a full list, and keep an eye on the 20-column indexing cap now that some of those indexes get created automatically rather than only by deliberate action.

App Catalog Authentication Automation Backup Compliance Content Type CSS Flows Google Javascript Limitations List Metadata MFA Microsoft Node NodeJs O365 OneDrive Permissions PnP PnPJS Policy PowerApps Power Automate PowerAutomate PowerPlatform PowerShell React ReactJs Rest API Rest Endpoint Security Send an HTTP Request to SharePoint SharePoint SharePoint List SharePoint Modern SharePoint Online SPFX SPO Sync Tags Teams Termstore Versioning

Leave a Comment

Your email address will not be published. Required fields are marked *