Skip to main content

Sites.Selected Permissions what is it, and how do I use it

·3 mins

Why is it even needed

For the longest time we were using service accounts when making background jobs to run on SharePoint online, which overall was… let’s call it functional, then we moved to App Registrations with Client Secrets which were just as simple to implement as the service accounts, awesome - however we could only do two permission levels

  • Read everything
  • Read/Write everything

Instead of the app just having the same permissions as the service accounts.

This (at least for me) caused a lot of concerned clients, (in fact this reddit post is a great example of just that, and is what prompted me to write this post).

So initially we moved to using X.509 certificates, as that was a bit more secure, and “the best we can do” - now as time went on, more and more clients started to bug us about this, and FINALLY - in back in February of 2021, Microsoft announced a new permission level was on the way, it would be all the way up until August 11th, 2022 before we saw the actual permission level, but now we have it!

So as a developer, what do I do?

Honestly surprisingly little!

You’ll need to grant the app the Sites.Selected permission instead of whichever permission you currently have i.e. Sites.ReadWrite.All

Now your app will get an Access Denied, this is what we want!

Now we need to grant our app access to the sites we want it to use, you could easily do this with PnP.PowerShell using the Grant-PnPAzureADAppSitePermission cmdlet.

Grant-PnPAzureADAppSitePermission -AppId <Guid> -DisplayName <String> -Permissions <Read|Write> [-Site <SitePipeBind>]

However I find clients are reluctant to give me the Sites.FullControl.All permission required to do this.

So I generally do a screenshare with the client and get them to go to the Graph Explorer

And first we need the ID of the site - I use this query to get it with a minimal JSON as possible, as to help the client understand what’s going on

GET |,Title,Id&$search="<Name of the site>*"
    "@odata.context": "$metadata#sites",
    "value": [
            "id": ",361cafff-021d-4889-81e1-302ab581cf53,3c2ab6a9-dd75-48b5-82bf-ed8bb59105ba",
            "webUrl": "",
            "displayName": "Your site"

Now we’ll simply copy the id for the site we wish to grant our app permission to and run the following requests on that:

POST |<SiteId from above>/permissions


POST |,361cafff-021d-4889-81e1-302ab581cf53,3c2ab6a9-dd75-48b5-82bf-ed8bb59105ba/permissions


    "roles": [
        "write" //or "read" - depending on your needs
    "grantedToIdentities": [
            "application": {
                "id": "cfa6b32c-acaa-462b-b7b2-113c72c9faa7", //ClientId for your app registration
                "displayName": "My super cool yet limited app" //A display name the app, only used to identify it later on this site

That’s it, run your code as normally!

Closing thoughts

Honestly it’s hard to find any good reasons NOT to use Sites.Selected every time, but I’ve tried anyways

You need Sites.FullControl.All permission to add more sites to your allowed sites, and there isn’t any easy way to see which sites an app registration has access to. It would be really nice to see a centralized way to manage this - and a way to create new sites and automatically have access to it for provisioning cases, so you don’t need a separate app with the Sites.FullControl.All just to assign permissions to your other apps.


Sites.Selected gives you all the permissions you need, but limited to a single site, requires a bit more setup, but should probably be preferred.