The Ultimate Beginner’s Guide to Microsoft Graph

The Ultimate Beginner’s Guide to Microsoft Graph

Microsoft Graph has been around for quite a while already. However, new people are entering the ecosystem every day, so we can’t expect everyone to know what it is and how to use it. I still find myself preaching about the subject at conferences and consulting fellow developers on related topics.

I’ve been working with Microsoft Graph extensively since 2017 and accumulated a lot of hands-on knowledge regarding even the smallest quirks of the API. This blog post offers a well-structured and “chronologically proceeding” beginner’s guide in my way of explaining things. I’ll tell you of my real-world experiences when using the API in customer projects and cover situations when things haven’t initially gone quite neatly as I had hoped. There are also code samples that illustrate the concepts, which you can easily use in your applications!

I hope you’ll find this blog post valuable. I’ll add more information to it and keep the guide up to date as the technology evolves, I get ideas for more content to include or come across new situations worth mentioning through customer projects. Happy reading!

Table of contents

  1. What is Microsoft Graph
    1. Where does the name come from?
  2. Real-world examples of what you can do with it
  3. How to get started
    1. Beta vs. v1.0 endpoint
    2. Graph Explorer
  4. How to use Graph in your application
  5. Authentication
  6. Troubleshooting
    1. Use a HTTP traffic monitoring tool
    2. Test the query outside of your application
    3. Reporting bugs
  7. More advanced features and best practices
    1. Paging
    2. OData query string parameters (and utilizing request headers)
    3. Batching
    4. Throttling
    5. Change tracking
      1. Webhooks
      2. Delta queries
    6. Open and schema extensions
      1. Adding custom metadata to Microsoft 365 groups via schema extensions for categorizing teams
      2. Synchronizing on-premises AD user profile attributes to schema extensions for fast filtering
  8. When to use SharePoint Online REST API instead of Microsoft Graph
    1. Missing operations and optimal performance
    2. SharePoint Framework: No need for admin consent
    3. Power Automate: Easy to use and cost-effective
  9. Afterword

What is Microsoft Graph

I remember when Microsoft Graph was still sort of a buzzword. During conference keynotes, it was presented as something quite mystical, unique, and perhaps even revolutionary: “when you create such a strong data asset, what you truly enable is AI-first workloads”. Hmm.

Don’t get me wrong; I do think it is terrific. I love building integrations, and Microsoft Graph is my best buddy these days when it comes to that. However, it makes a lot more sense for us developers if we directly say what it is: a REST API! That statement probably doesn’t make an inspiring keynote (for business people, at least), but it is what it is!

So, with all the mystery aside, Microsoft Graph is a REST API that allows you to access data in the following systems programmatically.

  • Microsoft 365 core services (Microsoft Teams, SharePoint, Planner, etc.)
  • Enterprise Mobility and Security services (including Azure AD, Identity Manager, Intune, etc.)
  • Windows 10 services
  • Dynamics 365 Business Central

Or should I say instead: Microsoft Graph is an API that brings the APIs of all those different services together. If you are familiar with Azure API Management, Microsoft Graph works in a very similar manner. It offers a unified way of consuming the different APIs, and you only need to authenticate to it once to be able to use all of the service APIs. If you were to use several of the underlying APIs directly, you’d need to authenticate to all of them individually. What Microsoft Graph essentially does is that it makes the consumption of the APIs much easier for us developers.

You can quite clearly notice that there are different service APIs underneath. Even though the notation for using the operations is similar, some quirks can make you think that “this API is behaving in a slightly different manner” than what you’ve used to when using some other service operations. Different product teams develop different service APIs. For example, the Exchange Online operations available via Graph are created by the Exchange Online product team, the Teams team develops the Microsoft Teams operations, etc. People think about things in different ways, and hence the underlying APIs can also end up being a bit different, even if the method of consuming them is unified.

Where does the name come from?

I can’t be the only person in the world who has ever wondered where does the name Graph come from?

Via the Microsoft Graph API, we have access to different kinds of entities, such as a Microsoft 365 group, a Teams team, and files in a SharePoint document library, and they are all connected. For example, the SharePoint team site and its Microsoft 365 group share the primary document library, and when you fetch the information about the document library via its site object, you also find out the group’s ID. There are many ways to retrieve information on entities, either directly by querying them or accessing them through related entities. For example, you can get information on the same team via both of these URLs; the first one is a direct reference to the team object via its ID, and the second one is getting the underlying Microsoft 365 group and then its connected team (related groups and teams have the same ID).

https://graph.microsoft.com/v1.0/teams/11651789-3a6a-411a-af0f-50755a278636
https://graph.microsoft.com/v1.0/groups/11651789-3a6a-411a-af0f-50755a278636/team

If you’ve ever seen the Microsoft Graph logo/graphic, this is what it attempts to illustrate. The connections between the entities. And in mathematics, there’s a thing called Graph theory which means relationships between objects. So there you have it, that’s what the name comes from (or where I think it comes from).

Real-world examples of what you can do with it

Because you’ve found your way to my blog, you are most likely interested in the operations related to Microsoft 365 and Azure AD, like myself. So here are a few things I’ve personally done with Microsoft Graph.

  • Numerous Teams provisioning solutions with different configurations, all tailored to fulfill customer-specific needs. Customers typically want to ensure certain naming conventions, create teams with predefined channel and folder structures, add additional metadata via schema extensions, disable external sharing for internal teams, assign classification labels, etc., but all solutions are somehow different. Some of the applications have also included further configuration of the associated Microsoft 365 groups and SharePoint sites and copying Planner plans.
  • Different kinds of SPFx web parts that list Microsoft 365 workspaces. The views would allow users to see all the details of teams and sites in the tenant, filter the list based on different types of criteria (membership type and status, custom metadata, archive status, etc.), and join teams they are not yet members of (with an approval step in the case of private teams).
  • Web parts that allow end-users to efficiently manage the custom metadata associated with Microsoft 365 groups (via schema extensions).
  • Guest user self-service registration services that’d allow users from specific organizations to invite themselves as guests, and which would assign them appropriate permissions.
  • Invite guest users in bulk based on a CSV file and grant them permissions to required resources based on their organization.
  • Add people to different Azure AD security groups based on whether or not they’ve accepted specific Terms of Use set via Conditional Access Policies.
  • Custom Azure AD Access Reviews solutions when the built-in features have not been sufficient for customer purposes.
  • A background process that runs on a schedule and disables guest users who haven’t logged into the tenant for several months and then removes them entirely if they are not reactivated within three months of disabling.
  • A solution for processing Teams call record data to support reporting requirements.
  • A solution that periodically renews Intune enrollment profile tokens and sends them to a Teams channel in another tenant.
  • Different kinds of SPFx web parts that display filterable lists of users with helpful information such as which office the users work at, contact info, what languages they speak, etc., based on their AD profile information.
  • An SPFx extension that’d allow users to convert Word documents into PDF format with a confirmation dialog where they could choose the version type (minor or major).

How to get started

The Microsoft Graph API reference documentation is the best place to check what operations are currently available and how to use them. The operations are presented in a tree hierarchy, organized by the entities and services.

For each of the operations, you’ll always have the following information available:

  • General information on the operation and what it does
  • If there’s anything special, you should be aware of, for example, known issues.
  • What permissions (a.k.a. scopes) the operation requires. The scopes are listed in the order of least to most permissive. You should always follow the principle of least privilege.
  • What the HTTP request should be like; headers, method, URL, and the format of the body for constructing requests. Quite often, there are also one or more examples with dummy data to illustrate better how to use the operation.
  • What the HTTP response will look like; what’s the status code when the request succeeds, and what is the format of the response body. Typically, there are also examples of this.

New operations get added to Graph regularly. You should always check the docs when starting a new project in case something has changed or new operations have been added related to the service you are working with. You can also follow the Microsoft Graph changelog and its RSS feed to stay on track of new features as they are being introduced.

Sometimes you might also want to check the Graph metadata document. It is an Entity Data Model (EDM) XML file that you can get by making a GET request to Microsoft Graph with the following URL: https://graph.microsoft.com/v1.0/$metadata. You can also switch the v1.0 endpoint to beta if you’d rather check the data model for the beta endpoint. More about the differences between beta and v1.0 endpoints next!

Beta vs. v1.0 endpoint

As you can see in the Microsoft Graph documentation, there’s an API reference for two endpoints: Beta and v1.0. The v1.0 endpoint contains operations that have been thoroughly tested and are ready for production use. The Beta endpoint includes newer operations that have more recently been published for public testing. The beta endpoint is not advised to be used in production because the operations are not guaranteed to work. Of course, the production API can also have bugs (just like any software), but there’s a smaller chance for that because those operations have already been tested more thoroughly. Also, the beta endpoint operations can receive changes without advance notification, so there’s a risk that if you are relying on them in your application, your app might stop working in such a situation. The production endpoint is intended not to have this kind of breaking changes but always offer backward compatibility. Mistakes can, of course, always happen — Microsoft developers are also human, after all.

However, the reality is not quite as black and white as the above guideline leads you to expect. We quite often end up utilizing the Beta endpoint in production applications because some operation we need is only available in the beta endpoint, and we don’t want to wait for several months for it to be published in the v1.0 endpoint. In these situations, it is essential to communicate very clearly to the customer that we need to use a beta endpoint to achieve the customer requirements, and it may receive breaking changes and not work 100% time. Based on my experiences, though, the beta endpoint works really well for the vast majority of the time, and I’ve only ever needed to react to a couple of hiccups that I’ve been able to sort out with minor modifications in the application quickly. And because I’ve previously made the customer aware of us using the beta endpoint, they’ve not had any problem with this — they knew it might happen and accepted the risk before we began the implementation.

So, if you can’t find an operation you need in the v1.0 endpoint, you might want to look at the Beta endpoint documentation and check if it is available there. And sometimes, even if the operation is available via both endpoints, the Beta endpoint might have some additional properties that have not yet been published to the production-ready endpoint. Just communicate the consumption of the beta endpoint to your customer very clearly to avoid any unpleasant situations.

Graph Explorer

Graph Explorer is a browser-based tool that is there to help you to get to know Graph better. It allows you to either use the sample tenant or log into your own tenant and conveniently make calls to Graph. You can quickly construct the appropriate HTTP requests and see the responses straight in the browser. You can use it to, for example, promptly ensure that you are formatting your requests correctly, to check if the information you are expecting to receive actually exists, or to see if the above-mentioned beta and v1.0 endpoints behave in the same way or if there are differences between the versions.

However, it is not a toy — the things you do with it truly happen, so consider carefully if you really want to run some operation, especially if you log into a production tenant. To play around safely and without restrictions (you can’t make any modifying requests against the sample tenant), I highly recommend you to get a free Microsoft 365 Developer tenant by joining the Microsoft 365 Developer Program. It comes with 25 E5 licenses, so you can also create plenty of test users. Also, if you get any free Azure credits, e.g., via a Visual Studio Enterprise subscription, check out another one of my blog posts on how you can use them in your developer tenant.

Because the operations do get executed against the tenant where you are logged in, Graph Explorer can also be used as a tool to run occasional administrative operations. I’ve personally used it to disable external sharing for a few Microsoft 365 groups, create a schema extension, and add additional profile card properties. You can only perform these actions via Microsoft Graph; there’s no GUI for them. Still, if you find yourself executing the same operations repeatedly, it’s better to write a script — that way, there’s less chance for human error, and you will probably save time in the long run.

Using Graph Explorer requires a tenant administrator to consent to the usage of the application in the tenant and often also to the needed permissions for performing the desired operations via the tool. An administrator may only consent to the usage for themselves (recommended) or to all users.

Note that with Graph Explorer, you are calling the API as the signed-in user (using delegated permissions), so you can only perform operations you are allowed to do. Another tool similar to Graph Explorer called Postman will enable you to also execute operations as an app (using application permissions). Also, it doesn’t require an admin to consent to the usage of the application as Graph Explorer does. I’ll talk about it in more detail later in the Troubleshooting section.

How to use Graph in your application

The cool thing about Microsoft Graph and REST APIs, in general, is that you can use them from pretty much anywhere. The only requirement is that your app can make an HTTP request. So it doesn’t matter if you are implementing an Azure Function in C#, a PowerShell script, a Power automate flow, or a mobile app. Whatever your chosen language and platform is, you can call Graph.

You have the following ways of using Graph in your application:

  • Construct an HTTP request in your code and call Graph directly. I’ve built a couple of GraphService templates/samples and shared them on Github to illustrate this. Feel free to check them out if you are interested in seeing how you can call Graph from your applications with a minimal amount of lines of code.
  • Use an SDK in between your application and Graph. The idea behind the SDKs is to make using the API even more effortless than it already is. Currently, there is an SDK available for the following platforms and languages: Android, Angular, ASP.NET, iOS, Javascript, Node.js, Java, PHP, PowerShell, Python, and Ruby.
  • There’s also a package called Microsoft Graph Toolkit that contains UI components that have Microsoft Graph integration built-in.

I prefer to call Graph directly from my application without using any Graph SDKs. Whenever I’ve given the SDKs a whirl, I’ve encountered problematic situations or bugs in which the culprit has always been the SDK and not the Graph API itself. In those situations, I’ve wasted a lot of development time banging my head against the wall, which I would have been able to avoid altogether if I had just stuck to calling the API directly. Making HTTP requests in an app is a simple task, and when I typically need to utilize only a few specific Graph operations, why would I want to install a full-fledged Graph SDK with all kinds of useless (from my project’s point of view) methods as a dependency? But this is only my opinion. To be diplomatic here, give both approaches a go and see which one you prefer.

Authentication

Regardless of whether you are using Microsoft Graph directly or via an SDK, you need to handle authentication before you can use any of the Graph operations. The goal is to obtain an access token from Azure AD, which you then need to include in the Authorization header of every HTTP request you make to Microsoft Graph. The access token contains information on what you are allowed to do with Microsoft Graph. Before performing the operation you are requesting, Microsoft Graph will first check the token to ensure it is valid and whether you are allowed to do what you are attempting to do.

Authenticating to Microsoft Graph is no different from authenticating to any other API protected by Azure AD, so if you already know how to do that, this bit will be easy-peasy for you. And it also works the other way around: when you learn to authenticate to Microsoft Graph, you’ll be able to leverage that newly acquired knowledge when you need to authenticate to some other API; your own custom one or provided by another party.

You can authenticate in many different ways, using one of the OAuth authorization flows, depending on the type of application you are developing and what permissions you need to use (application or delegated — a.k.a. user — permissions). Here are a few simple rules that help you choose the correct method for the most common scenarios.

  • Does your application only have a user interface but no back-end logic? Use the implicit grant flow.
  • Does your application have a user interface and back-end logic, and do you wish to access Graph as the user? Use the authorization code flow.
  • Do you wish to access Microsoft Graph from server-side code without user context, and does the Microsoft Graph operation you need support application permissions? Use client credentials flow. Preferably use Azure Managed Identities, then certificate (production environments), and finally client secret (test environments).
  • Is your app a daemon app (background process), and the Microsoft Graph operation you wish to use does not support application permissions? Use the resource owner credentials flow with a service account. Some services, such as Azure Logic Apps, support using service accounts with MFA through custom connectors, but for some scenarios, you need to disable MFA for the service account. Because of this security risk, using resource owner credentials flow should be avoided unless there is no other way of achieving the required result.

Before performing authentication, we need to set up an application registration in Azure AD first. I’ve described the steps for creating and configuring an application registration in another blog post; check it for detailed instructions.

When configuring the permissions in the application registration settings, you should always use the principle of least privilege, meaning only consent to the necessary permissions, nothing more. Therefore, when you check the required scopes from the Graph documentation, the first option is always the one that offers the least permissions.

After you’ve set up the application registration in the way required by the OAuth flow you’ve decided to use, it is time to add some authentication-related logic to your app. You can use libraries that offer methods for authentication or authenticate with direct REST requests in the case of more straightforward flows.

I’m currently working on a comprehensive authentication-related blog post containing more information on this topic. Meanwhile, check out the following blog posts I’ve previously written about different authentication scenarios. They include further instructions and sample code you can copy to your app.

Troubleshooting

So far, we’ve talked about how to authenticate and use Graph in your application. But what if everything does not go according to plan? How do you troubleshoot?

Use a HTTP traffic monitoring tool

When an error happens during a Graph call (or any other API call, for that matter), you typically don’t get to see the complete error information in the code editor. Instead, you want to use an HTTP traffic monitoring tool like the free Telerik Fiddler to see what’s happening behind the scenes. With Fiddler, you can inspect what kind of an HTTP request your code sent to the API and what kind of an error Graph responded with. Quite often, the response body contains more information on what went wrong, which should help you nail down the cause of the problem.

Typical errors I’ve encountered when using Graph are the following:

Response status Description
400
Bad Request
You are attempting to do something the API does not support. First, ensure the request URL and method are correct. Then, check that the request body is in the expected format. I’ve also got this error when I’ve tried to perform a write-request to an object that has not been fully provisioned yet. To be exact, I was attempting to add a team to a newly created Microsoft 365 group, but it was taking a while for the owners to get added to the group, which prevented me from performing the ‘add team’ operation (apparently, you can’t do that to a group without owners). So, to avoid the issue, I added some logic that would check for the status code 400 and then retry the operation after a while. This is a common approach for handling many similar error situations.
401
Unauthorized
You have not authenticated successfully to Graph in your app. The request Authorization header has either a missing, invalid, or expired access token.
403
Forbidden
The access token is valid, but it does not have the required permissions/scopes to perform the operation you request. Sometimes the API documentation has had incorrect or unclear information on the required permissions, and I’ve had to do a bit of testing to find what permissions are truly needed while following the principle of least privilege. You can check what scopes are included in the access token by decoding it.
404
Not Found
The resource you are attempting to get does not exist. You should ensure the request URL is valid. Alternatively, if you’ve just created a new object and are trying to get a resource related to it, there’s a chance that the entity you are attempting to retrieve is not yet ready. This often happens when you provision Microsoft 365 groups and then immediately try to, e.g., further modify it or get its drive (the primary document library on the related SharePoint team site). In such a situation, wait for a few seconds and try again until you no longer get the 404 status code.
429
Throttled
You or someone else in the same “request quota pool” has made so many requests to Microsoft Graph within such a short period that Graph refuses to handle any more requests from you for a while. Often the optimal number of seconds you need to wait before trying again is presented in the Retry-After header of the response, but this is not always the case, so you should be prepared to fall back to using an arbitrary — short but sufficient — wait time instead. We’ll talk more about throttling later in this guide.
502
Bad Gateway
The server is overloaded by requests and has reached its memory capacity. Treat this situation much the same way as throttling, but be aware that there’s no Retry-After header.

In general, you should put your code within a try-catch block whenever you are executing an HTTP request. You should never expect everything to just always go according to plan. There may be a temporary problem with the API, and you need to be prepared for that in your application so it fails gracefully. Log the error details for troubleshooting purposes, and display a user-friendly message in the user interface (which does NOT reveal too much information on what’s happening behind the scenes — for security reasons).

Test the query outside of your application

I already previously mentioned that you should check out Graph Explorer and possibly also Postman when you first get acquainted with Microsoft Graph. However, they are also good tools for quickly testing out problematic API calls outside of your application.

You can use Graph Explorer if you want to authenticate as a user (delegated permissions), and a tenant admin has consented to allow you to use the application. However, if you’re going to use application permissions, you need to use Postman.

If you don’t want to ask an admin to provide consent for Graph Explorer, you can also perform operations using delegated permissions with Postman. On the other hand, using Postman always requires an Azure AD application registration in the tenant, and quite often, the permissions configured there require administration consent. However, if such an application registration already exists in the tenant and you have access to the secret/certificate, this route can be less hassle than asking an admin to consent to use Graph Explorer.

The Microsoft Graph team has created a Postman collection which you can optionally import into Postman. It contains pre-defined HTTP requests for calling some of the Graph operations. Using the collection is entirely optional; you can always configure the HTTP requests for the operations you want to use by yourself.

You can find detailed instructions on using Postman to call Graph, optionally using the request collection, on the Microsoft docs.

Reporting bugs

If you discover a bug, you should report it through the following means:

  • Is the operation related to Azure AD? In that case, report it via Azure Portal.
  • Is the operation related to Microsoft 365 services? In that case, report it via the Microsoft 365 Admin Center. There are two annoying aspects to reporting bugs this way, though:
    1. The bug reports are only accepted if you have Premier tier support. If you don’t have that, you’ll receive a reply that you are not eligible for developer support. This sucks because all you want to do is to give feedback for improving a product and they refuse to receive it.
    2. Instead of simply reporting the bug and moving on with your life, you’ll have to deal with Microsoft support personnel who will want you to reproduce the issue on a call before they are willing to pass on the bug report to the development team. It is a lot of extra hassle I generally don’t have time for unless it is related to a customer project, and we are desperate to get the feature working. This is why I often do not report Microsoft Graph bugs related to M365 services via this method: I just can’t be bothered to deal with the support.
  • Alternatively, you can report issues in the API or its documentation via the microsoft-graph-docs Github repository. You can find a handy button at the bottom of each operation page in the API reference docs, which will direct you to submit an issue and automatically fill in the information on what operation your issue concerns.
  • Oh, and if you find a bug in an SDK, you can create an issue in the SDK’s repo on Github.

More advanced features and best practices

There is a difference between simply using Graph and following the best practices while ensuring your application has optimal execution times.

Paging

Whenever you request Microsoft Graph to return several entities (e.g., “list all users”), there is a chance that the list is so long that it does not fit into a single response body. So what will happen is that Microsoft Graph will split the list into multiple pages. You’ll then need to browse through all the different pages in your code to compile a complete list of the returned results.

You can notice paging happening when there is a @odata.nextLink property present in the response body. The property contains the URL, which allows you to get the next page of results. So, to turn the page, all you need to do is make another GET request to that exact URL.

It is a good idea to be prepared for paging whenever you make a get request. That way, you’ll never have to worry about the result set being too large for your code to be able to handle.

Here’s an example of implementing paging in TypeScript.

And below is another example implemented in C#.

OData query string parameters (and utilizing request headers)

When performing GET requests against Graph, you should consider using the OData query string parameters to optimize your requests and responses. With their help, you can do the following:

Parameter Functionality
$filter When using these OData query string parameters, I use $filter the most by far. It allows you to fetch a subset of entities that match your filter criteria. For example, if you wish to get only Microsoft 365 groups that have the Teams feature enabled, you can do that by making a GET request to the following URL (and yes, currently filtering by the resourceProvisioningOptions is only possible via the beta endpoint): https://graph.microsoft.com/beta/groups?$filter=resourceProvisioningOptions/Any(x:x eq 'Team')
$select This parameter allows you to explicitly specify which ones of the entity properties are returned in the response. Using $select, you can leave out the properties you don’t need. Ideally, you should always select only the properties you need as it improves the performance of your application. Also, for some entities, $select allows you to get properties that are not returned by default. For example, when fetching users, you don’t normally see properties like onPremisesExtensionAttributes, but if you make a GET request to the following URL, the properties do get returned, along with the user ID, display name, and no other info: https://graph.microsoft.com/v1.0/users?$select=id,displayName,onPremisesExtensionAttributes

Just as you should minimize the response content by using the $select parameter when you GET information from Graph, you can also ask the API not to return a response payload when you make a write-request to Graph by adding Prefer: return=minimal header to the request.
$expand Using the $expand parameter allows you to get the properties whose values are, in fact, references to other entities. Examples of such properties are owners and members for groups. Normally, these properties are not returned when you get groups, but if you add $expand=owners, you’ll also get the group owners returned. Unfortunately, you can only expand one property per request, so if you wish to get groups with both owners and members, you need to make two separate requests (or use $batch, which we will talk about in the next section!).
$orderby This parameter is self-explanatory: it allows you to sort the returned entities by a property value alphabetically (default) or in descending order. So, for example, when you GET /users?$orderby=displayName desc, you’ll get users in descending order by their display name property value.
$count In theory, this parameter should return the entity count in the response body. There’s two ways of using the parameter: you can either define it as a query string parameter /users?$count=true or as the last URL fragment /users/$count. However, whenever I’ve tried to use it explicitly, it has not done anything, or I’ve received an error $count is currently not supported. Even the examples in the Microsoft documentation don’t work. Luckily, some operations return the count automatically when you get a list of entities.
$top Again, quite a self-explanatory one. With the $top parameter, you can define the maximum number of entities to return at once. You should use it if you ever want to get the first n objects from the returned collection or implement paging together with the next presented $skip parameter. But it also, in some cases, allows you to increase the page size. For example, Graph returns a maximum of 100 groups per page by default. However, if you make a request /groups?$top=999, you can increase the page size to almost a thousand groups per page!
$skip The previous section explained how Microsoft Graph operations automatically split results on multiple pages if the response contents don’t fit into a single response body. However, it is also possible to implement custom paging with the help of these $top and $skip parameters if you don’t want/need to utilize the complete result set at once. There was also one beta operation I had to implement custom paging for because the operation did not return all objects at once nor automatically page the results back at the time. So, it is a good idea to also keep this option in mind.
$search Sometimes you wish you could $filter, e.g., groups or users by name which contains a certain word. Unfortunately, contains is not a supported operation by $filter. However, the $search parameter allows you to essentially do a similar thing! When you, for example, GET /groups?$search="displayName:test", you’ll get only groups which displayName property contains the string ‘test’. Awesome, huh?!
$format Supposedly, this parameter allows you to specify an alternative MIME type for the response, such as XML. I’ve never used it, but you’ll probably love it if you dislike change and modern things like JSON.

Then there are also a few other OData features which you sometimes use but don’t really need to think about:

Parameter Functionality
$skiptoken This query string parameter contains a token for retrieving a subset of results when the response body expands on multiple pages. It is automatically included in the @odata.nextLink property on the response, so if you are merely browsing through the page as described in the paging section of this guide, you don’t need to think about it.
$ref This is included as the last URL fragment when you add a reference to another entity. For example, in Graph, you use it when you add members and owners to groups because you don’t add the user objects inside the group; you merely reference that these users (specified in the body with their unique identifiers) are members/owners of the group. The usage of $ref is included in the documentation when needed, so you don’t need to remember it yourself; just follow the docs.
$value When $value is the last fragment of a URL, the query will retrieve the actual contents of a file in a binary format. You can then read and use the file’s actual contents in your code. For example, in the case of image files, you can display the image in your application’s user interface. If $value is not present in the URL, only the file properties are returned. Again, the usage of $value is mentioned in the docs when required, so this is just to let you know what it does.

Note that not all entities and properties support all of the parameters. Therefore, you should always check to be sure if you can, for example, filter by a particular property when you are planning to implement a certain logic. The previously mentioned tools — Graph Explorer and Postman — are great for testing if the query you are planning on using works.

Batching

Whenever you find yourself making many similar requests to Microsoft Graph, it’s a good idea to utilize batching. Batching allows you to bundle up to 20 Graph requests together and send them to the API in a single HTTP request. Doing this will decrease the execution time of your application tremendously because performing HTTP requests is a relatively time-consuming task. So what you’d do is to create a function like the following (in TypeScript).

And here’s the implementation of the batch function.

Whenever you create a batch, you need to give each of the requests an ID (unique to the batch) between 1 and 20. The batch response will contain the same IDs for the individual responses, which allows you to map them to the original requests. This is an essential feature because the response body does not necessarily clearly indicate what kind of request led to the result. Graph responses often contain a property called @odata.context, which gives you information on the related entities, but for example, in case of error situations, this piece of information is missing.

In the code above, I’ve used the IDs to add the original request URL to the responseMap object, including the response body and status code. This way, I can check later in my application logic which request URL resulted in which response body.

The code for the makeBatchRequest function is presented in the next section because I wanted to use it as an example when talking about throttling. Actually, without further ado, let’s do just that!

You can find the complete code sample solution in one of my Github repos.

Throttling

Whenever you make a call to Graph, there’s a chance that your request will get throttled. You’ll notice you are being throttled when you receive an HTTP status code 429 in the response.

Throttling is a mechanism that essentially prevents Microsoft Graph (or any API, it is not Graph-specific) from being DoS’ed. It ensures the service will remain functional even if someone were to bombard it with requests. When you get throttled, it can at first feel quite annoying, but it is actually a good thing. It also makes sure that you can still call Graph even if someone else is attempting to overload it with requests from the other side of the world.

I’ve found that particularly the operations related to Microsoft Teams are extremely prone to throttling. I noticed a remarkable change in the throttling “sensitivity” at the beginning of the COVID-19 pandemic when the usage of Teams suddenly grew exponentially. I suspect the service had to start throttling requests more often to ensure computation resources in datacenters were enough for all Teams users.

The Microsoft Graph docs say that throttling can occur when there’s a large number of requests are made across all applications in a single tenant or from a particular application across all tenants. However, based on my experience, I swear there are other limits in place as well. For example, I’ve been throttled after sending a single request from a tenant with no other custom applications running. Either there’s a service-related “requests received” limit (e.g., the Teams API can handle n requests from all sources in total before it starts throttling) or some additional limitations related to where the request is coming from (e.g., all applications that are running on the same machine in a data center share a limit). Note that this is my speculation based on my real-world experiences; I have no verified information on how things truly work behind the scenes.

So, what to do when you get throttled? You wait for a little while and then try again. The API often returns information on the optimal amount of time you need to wait (seconds) in the Retry-After header of the response. However, unfortunately, not all operations include this header in the response, in which case you need to use some arbitrary value, e.g., a few seconds. You probably need to do some testing to discover the minimum sufficient wait time in your case.

You can sometimes also get the HTTP status code 502 in a “throttling situation.” The status means Bad Gateway, and it happens when the server is overloaded by requests and has reached its memory capacity. In such a situation, Graph doesn’t have enough computing resources even to perform throttling.

You can handle the 502 status code in much the same way you react to status 429: wait a bit and try again. However, because no actual throttling has happened, there’s also no Retry-After header, which means using an arbitrary wait time is required for handling the situation.

Throttling can happen for both individual requests as well as batch requests. Below is a code sample for making batch requests while being prepared for throttling.

Change tracking

There may be times when you are interested in reacting to changes in a collection of entities. For example, you want to do something when a new group is created, or the properties of an existing group are modified. At first, you might think that figuring out what has changed is to store the original group data somewhere outside of Graph, regularly get the current state of groups, and then compare the up-to-date data to the previously stored data. However, this is not how you should do it. This approach is highly time and computation resource-consuming compared to the much better and more punctual alternatives available to us.

Microsoft Graph has two mechanisms that allow you to track changes that happen in a collection of entities:

  • Webhooks, a.k.a, change notifications
  • Delta queries, a.k.a., retrieval of new/changed objects

Webhooks

If you need to get an immediate notification when a change happens in a collection of entities, webhooks are your friend. They make it possible for you to promptly trigger processes when needed without constantly (and often unnecessarily) polling the data source for possible changes.

The notification you receive via the webhook does not contain much information on the changed object. Instead, it only includes the object’s ID that triggered the change, which you are then free to use to fetch further details on the entity separately if needed.

I’ve personally used change notifications to trigger a Microsoft Teams call record data-fetch for PSTN and Direct Routing calls from Microsoft Graph and insert the data into an Azure SQL database in a specific format. The purpose of the solution was to make the data easily usable by a reporting technology other than Power BI. The data needed to be visible on the report as quickly as possible (employees monitored the information constantly), and utilizing the webhooks offered us the optimal way to make that happen.

As a general rule, you should not replicate data outside of Graph. However, sometimes there are situations when you need to do it, like in the circumstances I described above. Another case when you would be required to do it is if data is available via Microsoft Graph for only, e.g., the past 30 days, and you needed to report data from a period longer than that.

Another situation in which I’ve thought of using webhooks has been for configuring teams after they’ve been created. Instead of creating a full-fledged provisioning solution where a user fills in a custom form, and a separate background process does the creation of a team and further configurations, we could have a webhook that detects when a user creates a group via the native Teams user interface. The triggered logic would then perform the necessary configurations, such as deleting the unwanted Wiki tab. However, I’m yet to implement this because my customers have always wanted to provide additional metadata or perform configurations conditionally (instead of applying them for all teams). But, if you ever find yourself in a situation where your customer wants to do some additional automation the same for all teams that get created, you could wire up a webhook for the group entity and trigger performing those configurations that way.

You can check which entities offer webhooks from the Microsoft Graph documentation.

Delta queries

When you do not need to get notified immediately when a change happens in a collection of entities and are more interested in the new or updated objects themselves, you should rather use delta queries than webhooks. Instead of always retrieving all entities of a particular type, delta queries allow you to get only the objects that have changed since the last time you performed a query. This means that the first time you perform a delta query, you get all the existing entities, and the next time you complete the query, you only get the entities that have been added, modified, or deleted since then.

A situation in which I’ve used Microsoft Graph delta queries was when a customer wanted to display a list of all Teams in their tenant on SharePoint Online. They also wanted to add a lot of different types of additional metadata to those teams, which users would be able to edit on demand.

There are multiple ways to fulfilling these requirements. You could, for example, create schema extensions (explained in the next section) to add additional metadata to the related Microsoft 365 group entities and a custom SPFx web part for displaying the teams and editing the schema extension values. However, in this case, we opted for simply creating a Power Automate flow that runs on a schedule and maintains an up-to-date list of the teams on a SharePoint list (based on the group IDs). The SharePoint list then has additional columns for all the different types of metadata the customer wants to add to those teams. The customer can easily change the metadata values and even add other columns without any help from a programmer.

Thanks to Microsoft Graph delta queries, every time the flow runs, it only needs to process the teams (or groups, to be exact) that have changed since the last run. As a result, the run completes much faster than if it were to loop through all teams on the tenant every single time and compare if their properties have changed.

You can check which entities support delta queries from the Microsoft Graph documentation.

Open and schema extensions

Open and schema extensions allow you to add custom properties to entities. Think of it as adding additional metadata to objects. You can currently add open and schema extensions for the following entity types: administrative unit, calendar event, device, group, group calendar event, group conversation post, message, organization, personal contact, user, task, and task list.

The difference between open and schema extensions is that schema extensions are strongly typed objects while open extensions don’t have any type restrictions. You can add data to open extensions in any format, while the data inserted into schema extensions must always be in a specific predefined format; it must adhere to the extension schema. As a result, open extensions are great when you want to insert data of varying formats into the same property, while schema extensions are better suited when you want to ensure that the data inside the property is always provided in the same format.

You can easily create a schema extension, e.g., via Graph Explorer.

Adding custom metadata to Microsoft 365 groups via schema extensions for categorizing teams

A customer wanted to add business area information on Teams teams to better track which organization business areas were using which particular team. They wanted to use the information in custom SPFx views for filtering teams based on the selected business area. The business area hierarchy had multiple levels, and teams with a “child” business area were required to show up when filtering by a parent business area. A single team could also belong to multiple business areas simultaneously. The business area hierarchy was subject to change almost every year, which meant that the hierarchy needed to be described in a data source that’d be easy to read programmatically and easily modified by the customer. The teams should also not become “orphaned” if the organization changes a business area name.

We created a string-type schema extension called ‘businessArea’ for the group entity and added all the different business areas to a SharePoint list (until now, the business areas had been simply listed in an Excel file). Then, with the help of a Parent lookup column (that pointed to the same list), we were able to describe the hierarchy between them.

The organization had a custom Teams provisioning solution in use. The order form rendered the business area hierarchy on the form based on the data in the SharePoint list and allowed the user to select one or more business areas for their future team. The provisioning logic then inserted the IDs of the selected business area list items separated by semicolons to the ‘businessArea’ schema extension property. Using the ID allowed the customer to freely change the names and the hierarchy of the business areas while ensuring the teams would still show up correctly when filtered.

Note that the schema extension values can be set and updated only programmatically. This means that if the end-users need to be able to modify the values (in this case, change the team business area), you need to offer some kind of a user interface for that purpose. For example, it could be a simple SPFx web part (this is what we ended up doing) or even just a Logic App/Power Automate flow which users could trigger by providing the necessary data via a SharePoint list.

Synchronizing on-premises AD user profile attributes to schema extensions for fast filtering

Another customer wanted to offer a view that people could use for checking which users work at a certain office/store. The office/store ID was stored in an on-premises AD extensionAttribute5. You can get the information contained in these extension attributes via Graph even without schema extensions if you use the $expand OData query string parameter ($expand=extensionAttributes. However, you can’t $filter users by those nested properties directly in the GET request. You’d need to get all users with their extension attributes and filter them based on the extension attribute value in your code (unless you were to use schema extensions)!

In addition to adding brand-new properties to objects, you can also synchronize on-premises AD properties as schema extensions. The schema extensions support filtering directly in the GET request, significantly improving the execution time of your code compared to the earlier described alternative. Using schema extensions for this purpose sped up the loading time of our solution (SPFx web part) tremendously!

When to use SharePoint Online REST API instead of Microsoft Graph

Ideally, one day all we need is Microsoft Graph. However, today there are still a few situations where using the SharePoint REST API is still the better option.

Missing operations and optimal performance

Currently, Microsoft Graph does not yet include all of the same operations available via the SharePoint Online REST API. So you can’t, for example, create a new site or list all sites in a tenant (although, you can execute a search query that returns all sites: https://graph.microsoft.com/v1.0/sites?$search=sharepoint — because all site URLs contain the word). Until those operations are added to Microsoft Graph, we’ll need to use the SharePoint Online REST API to perform those tasks.

Also, remember the existence of the SharePoint Search API! The search API can offer the fastest experience for retrieving SharePoint content because the search crawler has already indexed everything, and you can quickly search the index for the data you want with KQL queries.

Before you are able to use Microsoft Graph in a SharePoint Framework solution, you need to do the following:

  • Specify the required Microsoft Graph permissions in the solution package-solution.json file.
  • Package and install the solution to the tenant.
  • Ask a SharePoint administrator to consent to the permissions via the SharePoint Online Admin Center.

If you only need to use SharePoint-related operations, this seems like an overly tedious task when you compare it to simply sticking with the SharePoint API. When users use SharePoint Framework solutions, they are already automatically authorized to consume the SharePoint Online REST API. There is no need to consent to any additional permissions, you can just call the API in your code, and it will work. Also, you can test your solution on the tenant workbench with the API before you even deploy it to the tenant for the first time because you don’t need to trigger that admin consent process for any permissions.

Power Automate: Easy to use and cost-effective

With Power Automate, it is easier and more cost-efficient to use the SharePoint REST API than Microsoft Graph. Power Automate has an action called Send an HTTP request to SharePoint action that is free and offers the usual built-in, easy-to-perform sign-in experience to form an authorized connection to SharePoint. If you want to use the SharePoint operations of Microsoft Graph in a Power Automate flow, you need to do the following:

  • You need to create and configure an Azure AD application registration.
  • You need to handle the authentication in some way. Either create a custom connector for delegated permissions or setup an Azure key vault + secret/certificate for application permissions.

It seems quite a lot of work in comparison. Also, there’s the licensing. You need to use the HTTP premium action to call Microsoft Graph. You’ll be spending $15 per month at a minimum (1 license for the flow author) while using the Send an HTTP request to SharePoint action is free to use.

There is a change on the horizon regarding this, however. Already at this time, there’s a preview action called Send an HTTP request which can be found in Power Automate under the Office 365 groups connection. The action allows you to make requests to Microsoft Graph in much the same way as the Send an HTTP request to SharePoint action allows you to execute requests to the SharePoint REST API. This is great news and a much-awaited feature by the community! However, the action does not yet work for all operations because of the lack of consent for some scopes, including SharePoint-related operations. So, for the time being, the Send an HTTP request to SharePoint action continues to have its uses. Microsoft is working on solving the scope issue, and they will most likely resolve it by the time the action comes out of preview.

Afterword

I often feel like almost anything is possible with Microsoft Graph, and it’s a great feeling! I love creating integrations, and there are so many things you can do via Graph these days. Of course, some operations are still missing, but I am sure they’ll find their way to the API soon enough. I hope you, too, now feel empowered to create cool things with Graph.

Did you find this guide helpful? Are you excited to use Microsoft Graph in your applications? Or did you feel like there’s something essential missing? Leave a comment about your thoughts below and let me know!

If you enjoyed reading this blog post and would like to read more similar content in the future, feel free to follow me on your favorite social media platforms, and sign up for my Insider newsletter. Other than that, thank you for reading; I always appreciate it. Until next time!

Laura



47 thoughts on “The Ultimate Beginner’s Guide to Microsoft Graph”

  • Have you come across a protected pdf? example getting content of file: ….drive/root:/General/{file_name}:/content

    I am using GraphClient to get the file content, but because of IRM or similar protection, the content is returning message:”The PDF is protected. You will need a different reader in order to view this content. The reader you are using does not support opening files protected by Microsoft Office”

    This is the same message one would get if one viewed/download from SharePoint. I was hoping there was a workaround in MS Graph API or some other sharepoint sdk.

    Thanks in advance.

    • Hi Aymiee,

      Unfortunately, I have not encountered the scenario you describe in my work and hence can’t give you any detailed advice regarding the issue. 🙁

      Laura

  • Great blog.
    I can’t seem to find any way to generate a list of all users who have their Out of Office enabled, that is “mailboxsettings” “automaticRepliesSetting”: {“status”: “alwaysEnabled”} or “status”: -ne “disabled”

    via msgraph.

    I know in powershell we can use Get-Mailbox -ResultSize Unlimited | Get-MailboxAutoReplyConfiguration | Where-Object { $_.AutoReplyState -ne “Disabled” } but in msgraph??

    Much appreciated

  • Hi, Can you please tell me how to retrieve more than 999 records from O365 group to the Power Apps Gallery?

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.