Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Note
Apps in shared channel are currently in public developer preview. Apps in private channels are coming soon!
Shared and private channels in Microsoft Teams enable flexible collaboration within teams and across organizations. Currently, bot and tab apps are supported in shared and private channels. With this update, you can experience multiple benefits:
Shared channels: Allow seamless communication with internal or external members, without changing the user’s context. These channels ensure secure granular access control and real-time membership syncing.
Private channels: Provide secure space for selected team members to collaborate on sensitive or confidential content, ensuring privacy and focused discussions within the team.
Understand channels for app integration
Different channels determine app visibility, user access, and data storage behavior:
| Channels | Access | Collaboration | File storage location |
|---|---|---|---|
| Standard | All team members by default | Ideal for team-wide collaboration where bots or tabs must be available to everyone | Team’s SharePoint site |
| Private | Only to selected team members | Suitable for scenarios requiring restricted access to bots, connectors, or files | Private channel’s SharePoint site |
| Shared | Cross-team and cross-organization | Enables interaction with users outside the host team without requiring them to join the team | Shared channel’s SharePoint site |
Capabilities across channels
Here’s an outline of the different channels and their capabilities, across various parameters:
| Model | Channel capabilities | Standard channel | Shared and private channels |
|---|---|---|---|
| Membership | Can add people to the channel without adding to the host team | ❌ | ✔️ (not supported for private channels) |
| Channel membership can be limited to a subset of the host team | ❌ | ✔️ | |
| Channel can be shared with other teams to inherit members | ❌ | ✔️ (not supported for private channels) |
|
| Channel can be shared directly with its parent team | N/A | ✔️ (not supported for private channels) |
|
| External users can participate in the channel | ✔️ (B2B collaboration users) |
✔️ | |
| Channel is hosted under a host team | ✔️ | ✔️ | |
| Storage | Each channel has a dedicated SharePoint site | ❌ (inherits team site) |
✔️ |
| App Model | App must be installed in the host team | ✔️ | ✔️ |
| App installed to host team automatically available in channel | ✔️ | ❌ | |
| App must be added to each channel | ❌ | ✔️ |
Important
Check your app’s capabilities such as membership boundaries, storage location, and external access. Don't make any code changes, based on channel type.
Understand how different channels determine app functionality
Ensure that you understand that how different channels determine app functionality, membership, storage, or privacy, else can lead to broken functionality or unintended data exposure:
Use channel-specific membership APIs
Don't assume that team membership is equal to channel membership. Only members who are added to the channel can participate in shared and private channels.
Distinguish between users and roles
Channel members might include in-tenant users, guests, or cross-tenant users (external users from other tenants). If your app needs to distinguish between various users to manage access, data visibility, and feature availability, then you must validate user roles and tenant IDs before granting permissions.
Don't assume a single SharePoint site tied to a team
Unlike standard channels, which share SharePoint site with the team, private and shared channels have their own SharePoint sites. Always use the correct URL for each channel, to avoid missing files or unauthorized access errors.
Keep data scoped to channels
Aggregate or cross-post data across channels only when necessary, to prevent accidental leaks. For example, analytics apps shouldn't include private channel data in team-wide reports unless permissions are clearly defined.
Enable apps for shared and private channels
Most apps can support shared and private channels with a simple manifest update. Based on either of the following scenarios, you can decide the approach:
Apps with no dependence on specified parameters
If your app doesn’t:
- Use channel or team membership to determine message delivery, task assignment, or permissions
- Access or manage files stored in Teams or SharePoint
- Combine or share data across multiple channels or teams
- Customize experience, based on users (internal, guests, or external members)
Then, you only need to:
- Add
supportsChannelFeatures:tier1to your app manifest - Verify expected behavior, and test your app across channels
There's no dependence on classical and admin access for supportsChannelFeatures: tier1.
Apps with dependence on specified parameters
If your app handles advanced scenarios, or depends on the specified parameters listed in the Apps with no dependence on specified parameters section, then read through this guide for targeted updates and the best practices. Don't rewrite your code.
Note
- Tab and bot apps in shared and private channels are available in Government Community Cloud (GCC), GCC High, Department of Defense (DoD), and Teams operated by 21Vianet environments.
- SharePoint and the SharePoint pages apps aren't supported for shared channels in GCC, GCC High, DoD, and Teams operated by 21Vianet environments.
Get context for shared and private channels
When loading the user experience in a shared or private channel, use the data received from the getContext call for shared or private channels. The getContext call publishes two new properties, hostTeamGroupID and hostTenantID, which are used to retrieve channel membership using Microsoft Graph APIs. Every channel is created within a host team. For more information, see Get context in shared channels and Get context for your tab for private channels.
Manage channel membership
Use the allMembers API that manages and monitors channel memberships across standard, shared, and private channels. It enhances accuracy by reflecting direct and indirect members correctly. For more information, see List allMembers.
GET /teams/{team-id}/channels/{channel-id}/allMembers
Identify members
- Direct members: Users who are added directly to the channel, including users from other tenants (cross-tenants).
- Indirect members: Users who are members of the team, with which the channel is shared, including teams in the same tenant or in a cross-tenant.
You can identify whether a member of a shared or private channel is direct or indirect by checking the @microsoft.graph.originalSourceMembershipUrl annotation. This property identifies the source of a member’s access the channels:
| Member Type | Annotation scope |
|---|---|
| Direct member | The @microsoft.graph.originalSourceMembershipUrl property shows that the user is directly added to the channels |
| Indirect member | The @microsoft.graph.originalSourceMembershipUrl property includes a URL that points to the source team and indicates indirect membership. |
Note
You might receive duplicate notifications when a member is added to a shared channel. This scenario can happen if the member is already part of the shared channel directly or indirectly. Use the allMembers API to view all the direct and indirect members. Ignore the notification if the member already exists, either directly or indirectly.
Manage indirect membership across channels
You can manage indirect membership in channels using the following Microsoft Graph APIs:
Use the
allMembersAPI to retrieve all users who are members of a specific channel.GET /teams/{team-id}/channels/{channel-id}/allMembersUse the
doesUserHaveAccessAPI to determine whether the user is removed from the channel and can view all user accesses and relevant permissions. Apps with classic application permissions and RSC permissions can use this API.GET /teams/{team-id}/channels/{channel-id}/doesUserHaveAccess(userId='@userid',tenantId='@TenantID',userPrincipalName='@UserPrincipalName')Use the
sharedWithTeamsAPI to list all teams a channel is shared with.GET /teams/{team-id}/channels/{channel-id}/sharedWithTeamsUse the
allowedMembersAPI to retrieve users from a shared team who can access a shared channel.GET /teams/{team-id}/channels/{channel-id}/sharedWithTeams/{sharewithteamsId}/allowedMembersNote
The
allowedMembersAPI returns only newly associated users and doesn't apply to unshared events.
Get app notifications for graph membership changes
Apps installed in shared and private channels receive notifications when users are added to or removed from a team that shares the channel.
To receive app notifications, you must:
- Install the app in a host team and enable it for the shared or private channel. For more information on installing the app, see Install the app.
- Create a valid Microsoft Graph change notification subscription to monitor associated team membership changes and shared or unshared events using supported APIs.
To receive both direct and indirect member update notifications, you must include both the query string parameters when creating a subscription. If the query strings aren't provided, the subscription only delivers notifications for direct member updates. For more information, see Channel membership access.
/teams/{team-id}/channels/getAllMembers?notifyOnIndirectMembershipUpdate=true&suppressNotificationWhenSharedUnsharedWithTeam=true
This subscription enables apps to monitor membership changes in channels and its associated teams. For more information on how to create a Microsoft Graph change notification subscription, see Create a subscription.
Get app notifications for bot membership changes
The conversationUpdate event is sent to your bot when it receives notifications on membership updates for teams where it's added. To receive both direct and indirect member update notifications, configure your bot with the following prerequisites:
Update the app manifest. Add
supportsChannelFeatures:tier1to declare app readiness.Request Resource-Specific Consent (RSC) permission
Your app must request the following RSC permission to access channel membership information:
{ "authorization": { "permissions": { "resourceSpecific": [ { "name": "ChannelMember.Read.Group", "type": "Application" } ] } } }Ensure the bot is added in the shared channel
To receive member event notifications, install the bot at the team level and manually allow it in the shared channel.
This process ensures the bot is active and authorized to receive notifications for both direct and indirect members.
Manage member added and removed events
A member added event is sent to your bot in the following scenarios:
- When the bot, itself, is installed and added to a conversation
- When a user is added to a conversation where the bot is installed
A member removed event is sent to your bot in the following scenarios:
- When the bot, itself, is uninstalled and removed from a conversation.
- When a user is removed from a conversation where the bot is installed.
For more information, see Conversation events.
If the bot is installed in the team or channel, the Agents SDK receives a conversationUpdate activity through the OnConversationUpdateActivityAsync method, when a shared channel is added to another team.
When a new member is added to a shared channel, the OnMembersAddedAsync method is called. This method provides the context and details of the user who was added, allowing the bot to respond accordingly.
The following Agents SDK examples apply to both direct and indirect member add and remove events.
Member added event
public async Task OnMembersAddedAsync(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken)
{
var membersAdded = turnContext.Activity.MembersAdded;
List<string> addedMembers = new List<string>();
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
addedMembers.Add($"Member {member.Name} (ID {member.Id}) added.");
}
}
await ActivityUtils.SendAdaptiveCard(
"Member Added",
addedMembers,
new List<object> { "membersAdded", membersAdded },
turnContext,
cancellationToken).ConfigureAwait(false);
Member removed event
public async Task OnMembersRemovedAsync(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken)
{
var membersRemoved = turnContext.Activity.MembersRemoved;
List<string> removedMembers = new List<string>();
foreach (var member in membersRemoved)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
removedMembers.Add($"Member {member.Name} (ID {member.Id}) removed.");
}
}
await ActivityUtils.SendAdaptiveCard(
"Member Removed",
removedMembers,
new List<object> { "membersRemoved", membersRemoved },
turnContext,
cancellationToken).ConfigureAwait(false);
}
Handle bulk membership changes for graph
If there are bulk membership changes, Teams curbs individual membership update notifications when a channel is shared or unshared with a team. To reduce notification overload during membership updates, such as when a shared channel is added to or removed from a team with thousands of members, use thesharedWithTeams subscription resource:
/teams/{team-id}/channels/{channel-id}/sharedWithTeams
The sharedWithTeams subscription sends a single notification when a channel is shared or unshared with a team. It avoids thousands of per-user notifications and improves performance for apps that monitor membership changes. Ensure that you update the shared channel member list using the allMembers API after receiving a shared with or unshared from team notification.
Validate user access for graph membership updates
When an app receives a member removed notification for an indirect membership update, it’s important to verify whether the user is removed from the channel, especially since the same user might have both direct and indirect membership. For example, if a user is removed from a team that shares a channel, your app must confirm whether the user's access to the shared channel is revoked. Use the doesUserHaveAccess API to determine whether the user is removed from the shared channel. See doesUserHaveAccess API to learn more about user accesses and relevant permissions.
GET /teams/{team-id}/channels/{channel-id}/doesUserHaveAccess(userId='@userid',tenantId='@TenantID',userPrincipalName='@UserPrincipalName')
When an app receives a member added notification for an indirect membership update, see the allMembers API to refresh the list of all members.
GET /teams/{team-id}/channels/{channel-id}/allMembers
Classify members as in-tenant or out-tenant
You can classify members as in-tenant or out-tenant by comparing the 'TenantId' of the member or team with ownerTenantId as follows:
Get the 'TenantId' of the member you wish to compare.
GET /teams/{host-team-group-id}/channels/{channel-id}/allMembersCall
microsoftTeams.app.getContext()in your tab from the Teams JavaScript client library. The getContext() call returns context of the shared channel, which contains the details such asdisplayName,membershipType,ownerGroupId, andownerTenantId.Compare the
TenantIdof the member to theownerTenantIdproperty and determine if the member is an in-tenant or out-tenant.
Understand app permissions in shared channels
You can collaborate with external members outside of your organization using shared channels. App permissions in shared channels follow the host team's app roster and host tenant's app policy.
Note
The activity feed notification API doesn't support cross-tenant notifications for apps in a shared channel.
Verify bot installation in a channel
When a shared channel is added to another team, the Agents SDK receives a conversationUpdate activity through the OnConversationUpdateActivityAsync method, only if the bot is installed in the team. There’s no dedicated API to check if your app is part of a channel. Bots can detect when your app is added to a channel indirectly.
Use this channelMemberAdded event to trigger app-specific logic such as:
- Sending a welcome message
- Fetching the channel roster
- Configuring tabs
- Starting scheduled jobs
protected override async Task OnConversationUpdateActivityAsync(
ITurnContext<IConversationUpdateActivity> turnContext,
CancellationToken cancellationToken)
{
var tcd = turnContext.Activity.GetChannelData<TeamsChannelData>();
var eventType = tcd?.EventType?.ToLowerInvariant();
var extended = turnContext.Activity.GetChannelData<SharedChannelChannelData>();
var raw = turnContext.Activity.ChannelData as JObject
?? (turnContext.Activity.ChannelData != null
? JObject.FromObject(turnContext.Activity.ChannelData)
: new JObject());
_logger.LogInformation("ConversationUpdate eventType={EventType}, channelId={ChannelId}, teamId={TeamId}",
eventType, tcd?.Channel?.Id, tcd?.Team?.Id);
switch (eventType)
{
case "channelshared":
{
var hostTeam = extended?.Team;
var sharedWith = extended?.SharedWithTeams ?? new List<TeamInfoEx>();
_logger.LogInformation("ChannelShared: hostTeam={HostTeamId}, sharedWithCount={Count}",
hostTeam?.Id, sharedWith.Count);
foreach (var team in sharedWith)
{
_logger.LogInformation("SharedWithTeam: id={Id}, name={Name}, aadGroupId={AadGroupId}, tenantId={TenantId}",
team.Id, team.Name, team.AadGroupId, team.TenantId);
}
await turnContext.SendActivityAsync(
MessageFactory.Text($" Channel shared with {sharedWith.Count} team(s)."),
cancellationToken);
break;
}
case "channelunshared":
{
var unsharedFrom = extended?.UnsharedFromTeams ?? new List<TeamInfoEx>();
_logger.LogInformation("ChannelUnshared: unsharedFromCount={Count}", unsharedFrom.Count);
foreach (var team in unsharedFrom)
{
_logger.LogInformation("UnsharedFromTeam: id={Id}, name={Name}, aadGroupId={AadGroupId}, tenantId={TenantId}",
team.Id, team.Name, team.AadGroupId, team.TenantId);
}
await turnContext.SendActivityAsync(
MessageFactory.Text($" Channel unshared from {unsharedFrom.Count} team(s)."),
cancellationToken);
break;
}
default:
break;
}
await base.OnConversationUpdateActivityAsync(turnContext, cancellationToken);
}
Authenticate external users to access app content in SharePoint
You need to complete this step when your app stores content in the SharePoint site of the tenant that hosts the channel and requests a SharePoint token.
- Save host tenant ID of shared channel where tab is configured.
- Retrieve the host tenant ID using
channel.ownerTenantIdin JSv2 or from thegetContextcall in JSv1.
Now, send saved host tenantId inside tenantId parameter of getAuthToken call to allow cross-tenant users to access content hosted inside SharePoint site attached to the shared channel.
Identify guest users in channels using Graph API
You can identify if a member of a channel is a guest user, invited to your tenant from external organization, using roles property received for each object in List members of a channel response.
For guests, 'roles' = 'guest'
To accurately retrieve the all guest users in a channel, use the following allMembers API:
GET /teams/{team-id}/channels/{channel-id}/allMembers
This API works across standard and other channels and is recommended for reliably identifying guest members.
Access SharePoint data in shared and private channels
If you're building an app using SharePoint Framework, you need to use the SharePoint Online (SPO) site linked to the shared channel, not the one linked to the host team group. Both shared and private channels have their own SPO site that is only accessible to members of that specific shared or private channel.
Use the Microsoft Graph invite API to access the document library of the SPO site linked to a shared or private channel.
Note
See Feature request and general help for any requirements on Mailbox or Calendar scenarios.
Access SharePoint storage for channel files using Graph API
To access a channel’s SharePoint files root, use the following API:
GET /teams/{teamId}/channels/{channelId}/filesFolder
This API returns a DriveItem object for that channel's files root. For more, see channel files
Use the following properties for all subsequent file operations:
parentReference.driveId: The SharePoint driveId for the channel’s site.itemId: The folderId for the channel’s root.
The expected drive behavior of the channels is as follows:
- Standard channels use the team site’s driveId.
- Other channels use a separate
driveIdfor their individual sites.
Note
Always store and reuse the driveId and itemId returned by the API.
Don't hardcode library names or URLs based on assumptions about the team site, as the team site location can change.
Use this GET /teams/{teamId}/channels/{channelId}/filesFolder API for all channel types.
Manage file access for external or guest users using Graph API
External users remain in their tenant while accessing the host channel’s sharepoint site. To enable access:
- Configure cross-tenant access on both sides.
- Ensure your app is multitenant and receives consent in the host tenant.
Authenticate external users in tabs or task modules
When your tab or task module needs to access sharepoint resources in the channel’s home tenant, perform the following steps:
Detect external users Use getContext() to retrieve channel context. Compare
user.tenant.idwithchannel.ownerTenantId or channel.hostTenantId. If they differ, the user is external.Request token from home tenant Call getAuthToken() with the external user's tenant ID (
user.tenant.idortid) to ensure the token is issued from their home tenant.
Test your app across channels
Before publishing updates, ensure your app works correctly across all channel types in real situations.
Standard channel
Confirm that the existing functionality remains intact after your changes. Ensure tabs, bots, and messaging extensions continue to work as expected.
Shared channel
Private channel
Create a private channel in Team A with atleast two members (owner and member).
Perform the following steps to validate:
- Add the app to Team A then add it to private channel.
- Verify that your tab loads correctly in the private channel.
- Test bot responses for different user types:
- In-tenant member
- Guest user or external user
- If your app lists members or assigns tasks, confirm it only uses channel members and not the complete team.
- Add a new member to the private channel and check:
- Whether your app receives a membership change event
- Whether your membership API reflects the new member
Testings across these scenarios help you spot any issues with functionality, permissions, and user experience.
Note
Private channel is not yet available in Developer preview and will be available soon.
Best practices for supporting all channels
Dos
- Always retrieve the current channel’s member list and roles before performing actions. For example, when sending notifications or assigning tasks, target only the actual channel members and not the entire team.
- Control data access and sharing based on channel membership and permissions. For more information, see Manage channel membership.
- Determine whether users are internal, guests, or external (cross-tenant), and authenticate them in their home tenant. Always validate permissions for cross-tenant scenarios, especially when accessing files. For more information, see Identify guest users in channels using Graph API.
- Update help text and user guides to explain how your app behaves in different channel types, including any limitations for guests or external users.
- Review Microsoft Teams documentation and changelogs to stay aligned with the latest updates to APIs, permissions, and channel configurations.
Don'ts
- Restrict sensitive actions to owners or internal users and offer limited features to guests or external participants.
- Never include private-channel data in broader reports or public channels unless explicitly authorized.
Frequently asked questions
Why isn’t the app visible when trying to add it to a channel?
The app might not appear if the manifest is missing required support, such as supportsChannelFeatures: tier1. Additionally, the installer might not have sufficient permissions, only team members or owners can add apps, and local policies must allow app installation. If the channel is an incoming shared channel (shared into a team), apps can't be added directly from that location. In such cases, switch to the host team to add the app to the channel. You can detect whether a channel is shared-in by checking the channel metadata for the host team ID.
Why am I getting a 403 error stating 'app not enabled in this channel' when calling channel APIs?
This error occurs if the app is installed at the team level but isn't added to the channel. To resolve this issue, confirm that the app is added to the channel. If your app uses resource-specific consent (RSC), verify that the permissions declared in the manifest match the API calls being made, for example, ChannelMember.Read.Group for reading channel membership. After adding the app, retry the operation. For bots, initiate channel-specific logic when the bot receives the channelMemberAdded event to verify successfully addition to the channel.
Why does the channel roster appear incomplete, showing only owners or missing users?
The channel roster appears incomplete because the team members API is used instead of the correct channel-specific API. To resolve this issue, use the /channels/{id}/allMembers API to retrieve the full channel roster. If the response still shows only owners, the app likely isn't added to the channel. Prompt the user to add the app to the channel, then retry the request to fetch the updated roster.
Why does file access fail for some users even though they're part of the channel?
This failure can happen if the app is using the team’s main SharePoint site instead of the channel's specific site. Your organization’s sharing policies might block the type of link, or external users might lack the necessary permissions. To resolve this issue, make sure your app uses the channel’s filesFolder property to get the correct driveId and itemId for file operations. When you're sharing files, use people with existing access links or the invite API to give access to specific users or groups.
Why are external users experiencing authentication issues in tabs or task modules?
Authentication issues often occur when the app requests a token for the host tenant instead of the user’s home tenant. To resolve this issue, check whether the user is external by comparing context.user.tenant.id with the host or owner tenant ID. If they're different, the user is external, and your app must request the token for the user’s home tenant. You can do this step by passing the correct tenant ID (tid) when calling getAuthToken.
How do I know my app was added to a channel?
This issue might occur if the app expects a centralized list of installed apps at the channel level or relies on team-level installation behavior. Currently, there's no channel-level installedApps list available. Instead, bots must listen for the channelMemberAdded event within the channel to detect when they're added. When the app gets a 403 error and misses the event, it asks the user to add the bot to the channel and manages the error.
Why is my app failing to create message change notifications in shared or private channels?
Message change notifications might fail in shared or private channels because subscriptions to /channels/{id}/messages are blocked when using resource-specific consent (RSC) in these types of channels. If your app receives a 403 error when attempting to create a subscription, this behavior is expected. To resolve this issue, use on-demand message reads after the app is successfully added to the channel.
Why do file links still fail for external users even after the app is added to the channel?
The message change notification failure happens when the tenant’s sharing policy blocks the link type, or when the user doesn’t have access to the item, even if they’re a member of the channel. Another common cause is that the app might generate links pointing to the team drive instead of the channel’s dedicated drive. To resolve this issue, reissue the links using the 'people with existing access' option or use the invite API to grant access to specific users. Also, ensure the links reference the channel drive, which can be identified using the filesFolder property, rather than the team site.
Code samples
| Sample Name | Description | .NET | Node.js | Python |
|---|---|---|---|---|
| Bot Shared Channel Events | This sample app displays the Microsoft Teams bot transitive member add and remove events in shared channels. | View | NA | NA |
| Membership Change Notification | The sample application demonstrates how to send notifications for shared channel events in Microsoft Teams. Scenarios include users being added, removed, or membership being updated and when channel is shared or unshared with a team. | View | View | View |
See also
- Manage channel membership
- Understand app permissions in shared channels
- Build tabs for Teams
- Shared channels in Microsoft Teams
- Channel resource type
- Retention policy for Teams locations
- Use guest access and external access to collaborate with people outside your organization
- Manage external meetings and chat with people and organizations using Microsoft identities
Platform Docs