Get SharePoint Site Users: Implementations and Use Cases


Knowing who actually has access to a SharePoint site — not who you think has access — is the starting point for any real audit, cleanup, or automation built around user roles. This covers the ways to pull that list programmatically: REST via plain JavaScript or jQuery, PnPjs for SPFx solutions, and PowerShell for admin-scale reporting.


In this post: The quick way: people.aspx · JavaScript (REST API) · jQuery · PnPjs · PowerShell · What Get-PnPUser doesn’t tell you · Where Microsoft Graph fits in (and where it doesn’t) · Tradeoffs · When would you actually use this? · Related reading


The quick way: people.aspx

For a one-off look with no scripting, this URL (swap in your own site) shows the site’s membership directly in the browser:

https://yoursitedomain.sharepoint.com/_layouts/15/people.aspx?MembershipGroupId=0

Fine for a quick manual check. Everything below is for when you need it programmatically — for a report, an audit script, or logic inside an app.


JavaScript (REST API)
const siteUrl = _spPageContextInfo.webAbsoluteUrl;

fetch(`${siteUrl}/_api/web/siteusers`, {
  method: 'GET',
  headers: {
    Accept: 'application/json;odata=verbose'
  }
})
  .then(response => response.json())
  .then(data => {
    console.log(data.d.results);
  })
  .catch(error => console.error('Error fetching users:', error));

No dependencies, works anywhere you can run script against the page context — the tradeoff is you’re on your own for error handling and pagination on larger sites.


jQuery

Only relevant if jQuery is already loaded on the page (classic SharePoint pages, older customizations) — not something worth adding as a new dependency in 2026 just for this:

const siteUrl = _spPageContextInfo.webAbsoluteUrl;

$.ajax({
  url: `${siteUrl}/_api/web/siteusers`,
  method: 'GET',
  headers: {
    Accept: 'application/json;odata=verbose'
  },
  success: function(data) {
    console.log(data.d.results);
  },
  error: function(error) {
    console.error('Error fetching users:', error);
  }
});

PnPjs

The right choice inside an SPFx solution. Current PnPjs (v3/v4) uses the spfi() factory rather than the older global sp import:

import { spfi, SPFx } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/site-users/web";

const sp = spfi().using(SPFx(context));

sp.web.siteUsers().then(users => {
  console.log(users);
}).catch(error => {
  console.error('Error fetching users:', error);
});

PowerShell

For admin-scale work — reports, audits, anything run outside a browser session:

# Connect to SharePoint Online
Connect-PnPOnline -Url https://yourtenant.sharepoint.com/sites/yoursite -Interactive

# Get all site users
$users = Get-PnPUser

# Output users
$users | ForEach-Object {
    Write-Host "Display Name: $($_.Title), Email: $($_.Email)"
}

What Get-PnPUser doesn’t tell you

Worth being precise about this, since the intro frames this post around real audits: Get-PnPUser (and the REST/PnPjs equivalents above) returns the site’s direct principals — individual users and groups as single entries. If access is granted to a SharePoint group or an Entra security group rather than named individuals, that group shows up as one row, not as the people actually inside it. An audit that stops at the flat user list will under-report who really has access, sometimes badly, if the site leans on group-based permissions the way most well-governed sites do.

Getting the real membership needs an extra step per group type:

# Expand a SharePoint group's membership
Get-PnPGroupMember -Identity "Site Members"

# Expand an Entra (Azure AD) security group -- -Transitive includes members of groups nested inside it
Get-PnPEntraIDGroupMember -Identity "Marketing Team" -Transitive

For a genuinely complete audit, loop Get-PnPUser‘s results, check each principal’s type, and call the matching expansion cmdlet for anything that’s a group rather than an individual — recursing into nested Entra groups via -Transitive rather than assuming one level of membership is the whole picture. Skipping this step is the single most common reason a “who has access” report undercounts real access.


Where Microsoft Graph fits in (and where it doesn’t)

Worth addressing directly, since Graph covers most other Microsoft 365 lookups and it’s a reasonable assumption it covers this one too: it doesn’t, cleanly. Graph has no direct equivalent to /_api/web/siteusers or a site’s SharePoint groups (Site Owners, Site Members, Site Visitors, or any custom group). Those are SharePoint-specific principals scoped to the site collection, not Entra ID objects, so there’s no /sites/{id}/users endpoint to call.

What Graph can do is answer a narrower, adjacent question — for a modern, group-connected Team Site specifically, the site’s membership is really the underlying Microsoft 365 Group’s membership, which Graph does expose directly:

GET https://graph.microsoft.com/v1.0/groups/{group-id}/members

That only works for the site’s core Members group on a group-connected site, though — it says nothing about Owners/Visitors as separate roles, custom SharePoint permission groups, or a Communication Site (which has no backing Microsoft 365 Group at all). For those, the REST endpoint used earlier in this post is still the real answer, scoped to a specific group by name: /_api/web/sitegroups/getbyname('Site Members')/users. If Graph is the only API already wired into a project and adding SharePoint REST feels like scope creep, the site’s hidden User Information List is queryable through Graph as a fallback (/sites/{site-id}/lists/User Information List/items), though it’s a workaround rather than a purpose-built endpoint, and slower to work with than either of the other options.


Tradeoffs

Regularly pulling user lists gives real security value — catching stale accounts, verifying access matches policy — but it’s not free. Fetching users requires real permissions, which can bottleneck non-admins trying to self-serve a report. On sites with thousands of users, naive queries strain performance, so paginate or filter server-side rather than pulling everything and filtering in script. And building this into custom scripts or workflows means someone on the team needs enough SharePoint API familiarity to maintain it — it’s not zero-maintenance once it’s automated.


people.aspx answers “who has access” for a quick manual check. Everything else here is for when the answer needs to feed a report, an audit, or logic in an app.

When would you actually use this?
  • A compliance review needs a current export of who has access to a sensitive site — PowerShell, scheduled or run on demand.
  • An SPFx web part needs to show or act on the current user’s group membership — PnPjs, inline in the component.
  • You’re building a one-off admin dashboard in classic SharePoint or a page with jQuery already loaded — REST via jQuery is fine there, just not worth adding fresh.


Try these out and see what fits your setup. Questions or edge cases? Comment below.


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 *