How to add pre-configured SPFx Teams Tab to a channel

How to add pre-configured SPFx Teams Tab to a channel

Use case

Let’s consider following scenario. We developed a SPFx web part and added TeamsTab as a supported host. From now one, users can add our beautiful web part as a Teams Tab to any channel. That’s amazing! However we would like to be able to automate adding this web part with some initial configuration without any user interaction.

In this article I will not provide any code samples as I want to keep this tech agnostic. You can perform all of those steps from Power Platform, Powershell, .NET app or some other web part with javascript. To my best knowledge You can use both delegated and app only permissions to execute each provided requests.

Step one - finding the IDs

To proceed with our task we will need three ids. First two are team and chanel id which identifies the place we want to add our web part. Third one is teams app id associated with our web part.

Team and channel id

To find a team id You can use following endpoint:

GET: https://graph.microsoft.com/beta/teams

Sample response is:

{
    "@odata.context": "https://graph.microsoft.com/beta/$metadata#teams",
    "@odata.count": 16,
    "value": [
        {
            "id": "<team-id>",
            "createdDateTime": null,
            "displayName": "Test Team",
            "description": "Test Team",
            "internalId": null,
            "classification": null,
            "specialization": null,
            "visibility": "private",
            "webUrl": null,
            "isArchived": null,
            "isMembershipLimitedToOwners": null,
            "memberSettings": null,
            "guestSettings": null,
            "messagingSettings": null,
            "funSettings": null,
            "discoverySettings": null,
            "summary": null
        },
        ...
    ]
}

If You want to avoid using beta endpoints You can also use:

GET: https://graph.microsoft.com/v1.0/groups?$filter=resourceProvisioningOptions/Any(x:x eq 'Team')&$select=id,displayName

Which will return:

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#groups(id,displayName)",
    "value": [
        {
            "id": "<team-id>",
            "displayName": "Test Team"
        },
        ...
    ]
}

Once You select a team it’s time to find channel id. You can find it using:

GET: https://graph.microsoft.com/v1.0/teams/<team-id>/channels?$select=id,displayName

Sample response:

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams('<team-id>')/channels(id,displayName)",
    "@odata.count": 2,
    "value": [
        {
            "id": "<channel-1-id>",
            "displayName": "General"
        },
        {
            "id": "<channel-2-id>",
            "displayName": "Project 1"
        }
    ]
}

Teams App id

Now, as we have identified our team and channel, we need to identify our Teams App associated with SPFx web part. By default the externalId property of the app will be the same as web part id we developed. Note - this is “id” property in web part manifest. To find the actual id of the Teams App we can use this query:

GET: https://graph.microsoft.com/v1.0/appCatalogs/teamsApps?$filter=externalId eq '<web-part-id>'

Which should return following response:

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#appCatalogs/teamsApps",
    "value": [
        {
            "id": "<teams-app-id>",
            "externalId": "<web-part-id>",
            "displayName": "Test SPFx Web Part",
            "distributionMethod": "organization"
        }
    ]
}

Now we do have our app id and we can continue.

Step two - Preparation of SharePoint site

To make sure we can pre-configure our web part we need to perform two actions on site collection backing our selected team.

  • Activate TeamsHostedAppConfig feature
  • Add HostedAppConfig for our web part

With those, we will be able to construct a proper contentUrl for our Teams App configuration.

Find site associated with the team

To find the url of backing site we call:

GET: https://graph.microsoft.com/v1.0/groups/<team-id>/sites/root

Which returns:

{
   "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites/$entity",
   "createdDateTime": "2019-10-16T09:42:03.22Z",
   "description": "",
   "id": "<tenant-name>.sharepoint.com,<site-id>,<web-id>",
   "lastModifiedDateTime": "2022-06-13T17:08:00Z",
   "name": "TestTeam",
   "web-url": "https://<tenant-name>.sharepoint.com/sites/TestTeam",
   "displayName": "Test Team",
   "root": {},
   "siteCollection": {
       "hostname": "<tenant-name>.sharepoint.com"
   }
}

Setup Hosted App Configs list

To ensure HostedAppConfigs list exists let’s activate a feature using:

 POST: <web-url>/web/features/add('96e4ae8d-7cbb-4286-be06-8a688f61440a')

Now, to find the HostedAppConfigs list id we call:

 GET: <web-url>/_api/web/getList('<web-server-relative-url>/lists/hostedappconfigs')?$select=id

Expected response is:

{
    "odata.metadata":"https://<tenant-name>.sharepoint.com/sites/TestTeam/_api/$metadata#SP.ApiData.Lists/@Element&$select=id",
    "odata.type":"SP.List",
    "odata.id":"https://<tenant-name>.sharepoint.com/sites/TestTeam/_api/Web/Lists(guid'<list-id>')",
    "odata.etag":"\"0\"",
    "odata.editLink":"Web/Lists(guid'<list-id>')",
    "Id":"<list-id>"
}

Create Hosted App Config item for Your tab

Finally we want to add hosted app config representing our pre-configuration. First, we need to generate our new web part instance id, which must be a valid guid. We will mark it as <instance-id>. Next we need to prepare our configuration based on properties defined by our web part definition. A sample would look following:

   {
       "description": "Test description",
       "webPartProp1": "Test 1",
       "WebPartProp2": "Test 2"
   }

Now we can call an endpoint to add our pre-configuration:

POST: <web-url>/_api/web/hostedapps/add
BODY: {
    webPartDataAsJson: '{"id":"<web-part-id>","title":"Amazing web part","instanceId":"<instance-id>","properties":{"description":"Test description","webPartProp1":"Test 1","WebPartProp2":"Test 2"}}'
}

Note webPartDataAsJson is already serialized!

This call should return:

{
    "value": <list-item-id>
}

Now we have everything we need from SharePoint side. We have site url, HostedAppConfigs list id, list item id of associated Teams Tab configuration and instance id of our web part in Teams Tab configuration item.

Build tab content url

With those we can build contentUrl of our Teams Tab which will look like this:

 https://<tenant-name>.sharepoint.com/_layouts/15/TeamsLogon.aspx?SPFX=true&dest=<web-server-relative-url>/_layouts/15/teamshostedapp.aspx%3Fteams%26webPartInstanceId=<instance-id>%26list=<list-id>%26id=<list-item-id>

I will refer to this url as \<content-url> This url will be interpreted by Teams in following way:

  • Ensure login with TeamsLogon.aspx page
  • Once User is authenticated, redirect to teamshostedapp.aspx page in backing site collection
  • Get CanvasContent1 property from list \<list-id> from item \<list-item-id>
  • In CanvasContent1 find a tag for web part with \<instance-id>
  • Render the web part instance (with configuration stored in CanvasContent1)

Deploy to Teams Channel

With everything ready on SharePoint we can move forward with adding the app as a Teams Tab.

Ensure app is available

First let’s ensure the app is available in selected Team. You can add it using:

POST: https://graph.microsoft.com/v1.0/teams/<team-id>/installedApps
BODY: {
    "teamsApp@odata.bind", "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/<teams-app-id>"
}

With app available in our team we can finally execute the final step, adding Teams Tab.

Final call

POST: https://graph.microsoft.com/v1.0/teams/<team-id>/channels/<channel-id>/tabs
BODY: {
    "teamsApp@odata.bind", "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/<teams-app-id>",
    "displayName": "Amazing web part",
    "configuration": {
        "contentUrl": "<content-url>",
        "websiteUrl": "<web-url>",
        "entityId": "<instance-id>",
        "removeUrl": "<content-url>%26removeTab"
    }
}

With this step the pre-configured web part will be added to provided channel as a Teams Tab.

Closing hacks

If You want to provide Your users with configurable personal app You can execute steps from Step two - Preparation of SharePoint site and use the \<content-url> as a static tab content url in your teams app manifest. In my experience this will not work for a root site collection but will work for every other. To update the configuration You can call:

POST: <web-url>/_api/web/hostedapps/getbyid(<list-item-id>)/updatewebpartdata
BODY: {
    webPartDataAsJson: '{"id":"<web-part-id>","title":"Amazing web part","instanceId":"<instance-id>","properties":{"description":"Test description","webPartProp1":"Test 1","WebPartProp2":"Test 2"}}'
}

Hope You will find this useful Thank You and have fun :)