Adding a custom content provider to Viva Learning with Microsoft Graph
Published: 2023-10-31 10:07:10

---
title: Adding a custom content provider to Viva Learning with Microsoft Graph
published: true
date: 2022-11-28 10:29:16 UTC
tags: 
canonical_url: 
---

Since the pandemic hit, improving the employee experience in this new hybrid world has become a priority for Microsoft. One of the tools to support this mission is [Microsoft Viva](https://www.microsoft.com/en-us/microsoft-viva), a suite of apps available through Teams which can help employees to stay more connected, to better manage their time and to work more efficiently.

 

Today we'll focus on one of the apps of the Viva suite called [Viva Learning](https://www.microsoft.com/en-us/microsoft-viva/learning), which helps employees to grow and learn. Through Viva Learning, enterprises can make available learning content in a variety of forms (videos, articles, books, etc.) that employees can consume at their own pace. Through the platform, administrators can also dispatch learning assignments to employees, which is very useful for scenarios like compliance trainings or learning experiences that are required for your role.

 

![viva-learning-intro.png](https://techcommunity.microsoft.com/t5/image/serverpage/image-id/421575i51DBB31F22ABF7B1/image-size/large?v=v2&px=999 "viva-learning-intro.png")

 

Microsoft has partnered with many popular content providers, like Pluralsight or Coursera, to bring their content into Viva Learning. If the company has a license for one of these providers, employees can easily consume their learning content directly from Teams, without having to open an external portal. But what if you want to bring your own custom content into Viva Learning? In this article, we're going to explore the new preview feature which enables adding custom content providers into Viva Learning through the usage of Microsoft Graph. This is a developer focused scenario, which you can leverage when you already have a content system you would like to bring into the platform. For more casual scenarios (like having a curated list of content you want to share with your employees), [you can leverage the SharePoint integration](https://learn.microsoft.com/en-us/viva/learning/configure-sharepoint-content-source), which enables to turn a SharePoint list into a content source for Microsoft Viva.

 

The APIs are currently in preview, which are the ones we'll see today, are focused on ingesting custom content into Viva Learning. There's an additional set of APIs to manage assignments and track completion which are on the roadmap, and they will be released in the next year.

 

**Important!**  If you're a content provider who wants to make your content available to all the Viva Learning customers around the world, this article isn't for you, since this scenario isn't publicly available yet. This article focuses on building your own custom provider, so that you can make it available to employees who are part of a specific Microsoft 365 tenant.

 

To walk through the usage of these APIs, we're going to build a custom content provider based on the content published on this blog. We're going to create a learning provider called  **Modern Work App Consult** , which will host as content some of the articles that have been published here.

 

Let's start!

### Working with Microsoft Graph

The way Microsoft has opted to enable the integration experience for Viva Learning is through the [Microsoft Graph](https://learn.microsoft.com/en-us/graph/overview). Specifically, the team has introduced two concepts into the platform:

 

- **Learning Provider** : it's the source of the learning content. Pluralsight or Coursera, for example, are learning providers.
- **Learning Content** : it's content that is made available to employees. It can be a video, an article, a book, etc. A learning provider can host one or more learning contents.

These concepts are exposed through a new endpoint in the Microsoft Graph called `employeeExperience`. Being the feature still in preview, it's currently part of the beta endpoint: `https://graph.microsoft.com/beta/employeeExperience/`

As for any scenario in which you need to work with Microsoft Graph, you will need to register an application in Azure Active Directory to handle the authentication and the required permissions. However, in case of Viva Learning, there's an extra challenge, due to the different way permissions are managed:

 

- The APIs to work with a learning provider are supported only with  **delegated permissions**. This is the scenario where you are authenticated as a user in the tenant, so every operation against the Microsoft Graph is performed as if you were the user. These are the permissions that usually enable Microsoft Graph to retrieve information about the logged user, like their profiles, their appointments, or their contacts.
- The APIs to work with learning content are supported only with  **application permissions**. This is the scenario where you are operating as a service or a daemon, so there isn't a specific user logged in. These are the permissions that usually enable Microsoft Graph to retrieve information about the entire tenant, like the profiles of all the users. Under these permissions, you will find most of the APIs which you can use to interact with the whole organization, like the Intune APIs, the Teams administration APIs, or the Windows Update management APIs.

In the next section, we're going to setup the Azure Active Directory application in the correct way, then we'll use Postman to start working with the Viva Learning APIs.

 

### Setting up the application on Azure

Before working with the Viva Learning APIs, we will need to register an application on Azure Active Directory to perform the authentication. As such, open a browser and go to [the Azure portal](https://portal.azure.com). Make sure to login with an administrator account who belongs to the tenant which is hosting your Microsoft 365 subscription. Now open the Azure Active Directory section and, from the blade on the left, choose  **App registrations**  and click on  **New registration**.

 

![register-app.png](https://techcommunity.microsoft.com/t5/image/serverpage/image-id/421577iC6E01A82B1594D0B/image-size/large?v=v2&px=999 "register-app.png")

Give it a meaningful name (like Viva Learning app), then under  **Supported account types**  choose  **Accounts in this organizational directory only**. As  **Redirect URI** , select  **Web**  as platform and add the following URL: `https://oauth.pstmn.io/v1/browser-callback`. It's the one required by Postman, which we're going to use later to experiment with the APIs. Once you have configured everything, click  **Register**. As the first step, let's configure the API permissions we need to work with Viva Learning. Click on  **API permissions** , choose  **Add a permission**  and select  **Microsoft Graph**. Now add the following permissions:

 

- Under  **Delegated permissions** , choose  **LearningProvider.ReadWrite**. This is the permission required to create and list content providers, as described [in the official docs](https://learn.microsoft.com/en-us/graph/api/employeeexperience-post-learningproviders?view=graph-rest-beta&tabs=http).
- Under  **Application permissions** , choose  **LearningContent.ReadWrite**. This is the permission required to create and list learning content, as described [in the official docs](https://learn.microsoft.com/en-us/graph/api/learningcontent-update?view=graph-rest-beta&tabs=csharp).

Once you have added the required permissions, make sure to click on the button  **Grant admin consent for xyz** , where xyz is your company's name. The Viva Learning APIs, in fact, requires admin's approval to be used. This is how your dashboard should look like:

 

![api-permissions.png](https://techcommunity.microsoft.com/t5/image/serverpage/image-id/421578iD08FD05E59F247C6/image-size/large?v=v2&px=999 "api-permissions.png")

 

Now we need to generate a secret, which we'll need to manage the authentication. Click on  **Certificates & secrets** , click on  **New client secret** , choose optionally a name and an expiration and click  **Add**. The secret will be added, and you'll be able to see its value in the table.  **Make sure to copy it somewhere!**  We'll need it later, but this is the only time it will be displayed. If you lose it, you'll need to generate another one.

We have everything we need now. To learn the basics about the Viva Learning APIs, we're going to use Postman to perform requests against the Microsoft Graph.

 

### Set up Postman

The Microsoft documentation already provides an excellent article on how to setup Postman with the Microsoft Graph. The process is made easier by the availability of a dedicated Postman collection, which already includes all the Microsoft Graph APIs and a dedicated environment to simplify the authentication process. The documentation [is available here](https://learn.microsoft.com/en-us/graph/use-postman), you just need to follow it step by step. At the end of Step 4, titled  **Configure authentication** , you will get an environment like the one in the image below set up in your Postman client:

 

![postman.png](https://techcommunity.microsoft.com/t5/image/serverpage/image-id/421579i4EC3019D5EE46D68/image-size/large?v=v2&px=999 "postman.png")

 

Let's customize the variables based on the AAD app registration we have just created:

 

- **ClientID** : copy the value under **Application (client) ID** from the Overview page.
- **TenantID** : copy the value under **Directory (tenant) ID** from the Overview.
- **ClientSecret** : copy the client secret you have previously generated in the  **Certificates & secrets**  section.

Now we're ready to start working with the Viva Learning APIs. If you have properly followed the tutorial to add the Microsoft Graph collection into Postman, you will have two subfolders: Delegated and Application.

 

![collections.png](https://techcommunity.microsoft.com/t5/image/serverpage/image-id/421581i3E6C7077ABF257CC/image-size/large?v=v2&px=999 "collections.png")

Let's start with the creation of a new Learning Provider, which is a delegated operation.

 

### Create a learning provider

The first step to working with delegated operations is to get the proper access token, which is required to authenticate against the Microsoft Graph. Click on  **Delegated**  and you should automatically see the  **Authorization**  tab. Everything should already be set up in the right way. All you must do is to click on the  **Get New Access Token**  button at the end of the page:

 

![get-new-access-token.png](https://techcommunity.microsoft.com/t5/image/serverpage/image-id/421582i07E694BBC5BB2A09/image-size/large?v=v2&px=999 "get-new-access-token.png")

 

Since we are in a delegated scenario (so we're going to call the APIs with the identity of a logged user), Postman will open a popup to walk you through the authentication flow with the Microsoft Identity platform. Once you have logged in with an administrator account from your Microsoft 365 tenant, you will be issued an access token, that will be saved in Postman and that we can use to work with all the Graph APIs which require delegated permissions.

 

![postman-authentication.png](https://techcommunity.microsoft.com/t5/image/serverpage/image-id/421583iD5B59515BB81FB13/image-size/large?v=v2&px=999 "postman-authentication.png")

 

The Microsoft Graph collection for Postman you have forked doesn't contain the Viva Learning APIs yet, so we'll need to manually create a new request. We'll create it under the Delegated folder, so that we can retain the same authentication flow. Right click on  **Delegated**  and choose  **Add request**. Name it  **Create learning provider**  and configure it like this:

- As method, choose  **POST**.
- As endpoint, set `https://graph.microsoft.com/beta/employeeExperience/learningProviders`.

Finally, we must specify the body of the request, which is the JSON payload that describes the content provider we want to create. This is an example payload we can use to create our learning provider called  **Modern Work App Consult** :

```
{
    "displayName": "Modern Work App Consult",
    "squareLogoWebUrlForDarkTheme": "https://support.content.office.net/en-us/media/4c531d12-4c13-4782-a6e4-4b8f991801a3.png",
    "longLogoWebUrlForDarkTheme": "https://support.content.office.net/en-us/media/4c531d12-4c13-4782-a6e4-4b8f991801a3.png",
    "squareLogoWebUrlForLightTheme": "https://support.content.office.net/en-us/media/4c531d12-4c13-4782-a6e4-4b8f991801a3.png",
    "longLogoWebUrlForLightTheme": "https://support.content.office.net/en-us/media/4c531d12-4c13-4782-a6e4-4b8f991801a3.png",
    "isEnabled": true
}

```

We must provide the name of the content provider (`displayName`) and four different versions of the provider's logo, which will be used based on the scenario and the theme of the user. Now hit  **Send** : if everything goes well, we'll get back as response a JSON payload with the same information we have just submitted, plus some additional ones that have been created as part of the ingestion process:

```
{
    "@odata.context": "https://graph.microsoft.com/beta/$metadata#learningProviders/$entity",
    "id": "ba9790ef-21d5-4c17-808c-acda55230253",
    "displayName": "Modern Work App Consult",
    "squareLogoWebUrlForDarkTheme": "https://support.content.office.net/en-us/media/4c531d12-4c13-4782-a6e4-4b8f991801a3.png",
    "longLogoWebUrlForDarkTheme": "https://support.content.office.net/en-us/media/4c531d12-4c13-4782-a6e4-4b8f991801a3.png",
    "squareLogoWebUrlForLightTheme": "https://support.content.office.net/en-us/media/4c531d12-4c13-4782-a6e4-4b8f991801a3.png",
    "longLogoWebUrlForLightTheme": "https://support.content.office.net/en-us/media/4c531d12-4c13-4782-a6e4-4b8f991801a3.png",
    "isEnabled": true
}

```

The most important one is `id`. This is the unique identifier of the content provider, we're going to need it to perform any additional operation with the provider, like adding some content or changing some of its properties. If we want to double check that the provider was created successfully, we can perform the same request against the same endpoint, but this time with a  **GET**  operation. The API will return the list of all the custom content providers:

```
{
    "@odata.context": "https://graph.microsoft.com/beta/$metadata#employeeExperience/learningProviders",
    "value": [
        {
            "id": "303da5b7-dce3-4998-a76f-7a18849fc697",
            "displayName": "Modern Work App Consult",
            "squareLogoWebUrlForDarkTheme": "https://support.content.office.net/en-us/media/4c531d12-4c13-4782-a6e4-4b8f991801a3.png",
            "longLogoWebUrlForDarkTheme": "https://support.content.office.net/en-us/media/4c531d12-4c13-4782-a6e4-4b8f991801a3.png",
            "squareLogoWebUrlForLightTheme": "https://support.content.office.net/en-us/media/4c531d12-4c13-4782-a6e4-4b8f991801a3.png",
            "longLogoWebUrlForLightTheme": "https://support.content.office.net/en-us/media/4c531d12-4c13-4782-a6e4-4b8f991801a3.png",
            "isEnabled": true,
            "loginWebUrl": null
        }
    ]
}

```

**IMPORTANT!**  At the time of authoring this article, you can have only one custom provider per tenant. If you try to submit multiple POST requests to the `/employeeExperience/learningProviders` endpoint after you have already created a custom provider, they will update the existing one, instead of creating new ones.

Now we're ready to add some content to our learning provider.

### Adding content to a learning provider

To add content to a learning provider, we need to move from the  **Delegated**  folder in Postman to the  **Application**  one since, as we mentioned at the beginning of the article, the APIs to add content are available only with this type of permission. As such, we must repeat the authentication process to get the proper access token. The starting point is the  **Application**  folder under the Microsoft Graph collection in Postman. Also in this case, the configuration will be already defined in the right way, all you must do is to click on the  **Get New Access Token**  button. This time, however, you won't see any pop up asking you to authenticate with a user. We are in an Application permission scenarios, so we're going to use the [Client Credentials authentication flow](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow). We won't authenticate as a user, but as an application, through the AAD app we have created in the Azure portal. As such, you will directly get an access token, which will be stored in Postman for later usages.

 

![postman-application-authentication.png](https://techcommunity.microsoft.com/t5/image/serverpage/image-id/421585iC161B5C73FAAB99F/image-size/large?v=v2&px=999 "postman-application-authentication.png")

As we did before for the creation of the content provider, also in this case we will need to manually create a new request under the  **Application**  folder, since the Viva Learning APIs are missing in the collection. Right click on the  **Application**  folder, click on  **New request**  and name it  **Add learning content**. This API is a bit peculiar, because it doesn't differentiate between adding new content (which is typically done with a POST operation) and updating an existing one (which is typically done with a PATCH operation). The only supported operation is PATCH and, based on the id we're going to provide, the API will understand if it needs to create new content or update an existing one. As such, make sure to set PATCH as operation to perform in Postman.

 

This is the endpoint we must use the PATCH operation with: `https://graph.microsoft.com/beta/employeeExperience/learningProviders/{{LearningProviderId}}/learningContents(externalId='externalId')`.

There are couple of placeholders we must replace:

 

- `LearningProviderId` is the unique identifier of the learning provider this content belongs to. This is the ID that the `learningProviders` API has previously returned to us when we have created the new provider.
- `externalId` is an ID which uniquely identifies our content. This is an external id, meaning that it's typically the ID that our learning provider uses to identify this content, which is different from the internal one used by the Viva Learning APIs.

It's critical to specify the `externalId` as part of the Microsoft Graph URL. Without it, the PATCH operation will fail to create new content. If you try to submit a request only by using the URL `https://graph.microsoft.com/beta/employeeExperience/learningProviders/{{LearningProviderId}}/learningContents`, it will fail. In the  **Body**  tab of the request, we must specify the JSON with the information about the content we want to make available. The full payload can contain a lot of information, as highlighted [in the official docs](https://learn.microsoft.com/en-us/graph/api/learningcontent-update?view=graph-rest-beta).

 

Let's use the knowledge we have acquired to add as content the [following article](https://dev.to/qmatteoq/create-viva-connections-extensions-using-spfx-with-windows-and-wsl-4a3a-temp-slug-8504440) from this blog. We're going to add the content to our existing learning provider (so we're going to use `303da5b7-dce3-4998-a76f-7a18849fc697` as `learningProviderId`) and we're going to use, as external id, the unique identifier of the post which is contained in the url (in this case, it's `3617543`). Based on this information, this is the endpoint we're going to reach:

`https://graph.microsoft.com/beta/employeeExperience/learningProviders/303da5b7-dce3-4998-a76f-7a18849fc697/learningContents(externalId='3617543')`

``

And this is the JSON payload we're going to submit as body, with the minimum set of information required by the API: title, URL of the content and language.

```
{
    "title": "Create Viva Connections extensions using SPFx with Windows and WSL",
    "contentWebUrl": "https://techcommunity.microsoft.com/t5/windows-dev-appconsult/create-viva-connections-extensions-using-spfx-with-windows-and/ba-p/3617543",
    "languageTag": "en-us",
}

```

When you hit send, if the request is successful, you will get back a response with the same JSON, plus some additional information generated by the Viva Learning APIs, like the internal unique identifier of the content. If you want to double check that the content was indeed added, you can get the list of contents which belong to a content provider by performing a  **GET**  operation against the following endpoint: `https://graph.microsoft.com/beta/employeeExperience/learningProviders/{{learningProviderId}}/learningContents`. Also in this case, you must replace `learningProviderId` with the unique id of your learning provider. If we perform a GET operation against our learning provider (`https://graph.microsoft.com/beta/employeeExperience/learningProviders/303da5b7-dce3-4998-a76f-7a18849fc697/learningContents`), we're going to get the following response:

```
{
    "@odata.context": "https://graph.microsoft.com/beta/$metadata#employeeExperience/learningProviders('303da5b7-dce3-4998-a76f-7a18849fc697')/learningContents",
    "value": [
        {
            "id": "3ff3c4ff-1e34-4738-aa31-3de087ba7ab3",
            "externalId": "3617543",
            "title": "Create Viva Connections extensions using SPFx with Windows and WSL",
            "contentWebUrl": "https://techcommunity.microsoft.com/t5/windows-dev-appconsult/create-viva-connections-extensions-using-spfx-with-windows-and/ba-p/3617543",
            "languageTag": "en-us",
            "description": "",
            "sourceName": "Modern Work App Consult",
            "thumbnailWebUrl": null,
            "numberOfPages": 0,
            "duration": "PT0S",
            "format": null,
            "createdDateTime": "0001-01-01T00:00:00Z",
            "lastModifiedDateTime": "0001-01-01T00:00:00Z",
            "contributors": [],
            "additionalTags": [],
            "skillTags": [],
            "isActive": true,
            "isPremium": false,
            "isSearchable": true
        }
    ]
}

```

If we want to update the content (for example, to add some of the fields we missed out, like `duration` or `contributors`), we just need to repeat the same **PATCH** operation again. The only difference is that this time, we have two options:

 

- Keep using the endpoint with the `externalId`, like `https://graph.microsoft.com/beta/employeeExperience/learningProviders/303da5b7-dce3-4998-a76f-7a18849fc697/learningContents(externalId='3617543')`
- Switching to the internal id, which can be passed after the `learningContents` portion of the URL: `https://graph.microsoft.com/beta/employeeExperience/learningProviders/303da5b7-dce3-4998-a76f-7a18849fc697/learningContents/3ff3c4ff-1e34-4738-aa31-3de087ba7ab3`

### Testing the work in the Viva Learning app

To test the outcome of your work, you can use the Viva Learning app which is available in Microsoft Teams. If you don't have it, you can search for it in the Apps section and install it.

 

![viva-learning.png](https://techcommunity.microsoft.com/t5/image/serverpage/image-id/421586i0C455683E076E575/image-size/large?v=v2&px=999 "viva-learning.png")

Once you have opened it, you should be able to see in the catalogue the new content provider with the related content:

 

![custom-provider-viva.png](https://techcommunity.microsoft.com/t5/image/serverpage/image-id/421587i5FE65F63D0306042/image-size/large?v=v2&px=999 "custom-provider-viva.png")

**Very important!**  The Viva Learning engine takes a while to synchronize the content coming from external providers. As such, if you open the Viva Learning app immediately after having added the content with the Microsoft Graph, you won't be able to see it. You must be patient and wait for a bit.

 

### Manage the AAD app registration

There's a very important caveat to keep in mind when you are working with the Viva Learning APIs in Microsoft Graph. When you create a content provider, you must use the same AAD app registration to perform any operation related to the content of the provider, like adding new content or listing the existing one. If you, let's say, create a content provider with an application, but then you try to add some content to it with another one, you will get back a forbidden error, like the following one:

```
{
    "error": {
        "code": "forbidden",
        "message": "Forbidden",
        "innerError": {
            "date": "2022-11-21T19:21:39",
            "request-id": "c244018f-ab2a-4de8-9358-ca69835b2bbd",
            "client-request-id": "c244018f-ab2a-4de8-9358-ca69835b2bbd"
        }
    }
}

```

As an example, this happened to me when I created the Learning Provider using [Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer) (which is backed by its own AAD app registration), but then I tried to add some content using Postman, backed by the AAD app registration I created on my tenant.

 

### Wrapping up

In this post we have learned the basics of how to use the Microsoft Graph APIs to expose custom learning paths to the Viva Learning application for our employees. This post was useful to understand the basic concepts behind these APIs but, in a real scenario, you won't use Postman to work with them. In the next article, we're going to explore a more realistic implementation: we're going to build a web application based on Blazor that we can use to manage our custom learning provider and its content.

 

Stay tuned and happy coding!