Skip to main content

From SDK 2.0 to 2.1

info

Use this guide to move an existing Teams SDK for .NET 2.0 app to SDK 2.1.

If you're starting a new app, target SDK 2.1 directly — no migration needed.

Migration highlights​

AreaSDK 2.0SDK 2.1 (current)
App modelPlugin-orientedStandard ASP.NET Core app
Startupbuilder.AddTeams() + app.UseTeams()builder.Services.AddTeamsBotApplication() + app.UseTeamsBotApplication()
Auth configTeams:* settingsMSAL-native AzureAd + BotFramework sections
Messaging helpersSend/Reply/Quote, MessageActivity, app.Send/app.ReplySendAsync/ReplyAsync/QuoteAsync, MessageActivityInput, app.SendAsync/app.ReplyAsync
Cross-cutting pipelinePlugin/event-bus patternsITurnMiddleware + DI
Local unauthenticated devskipAuth: trueAzureAd:DangerouslyAllowUnauthenticatedRequests=true

1. Move to SDK 2.1 packages​

Update package references to use SDK 2.1 packages.

Example .csproj change:

<ItemGroup>
<PackageReference Include="Microsoft.Teams.Apps" Version="2.1.*" />
</ItemGroup>

2. Move to MSAL-native auth configuration​

SDK 2.1 moves from SDK-specific Teams:* auth settings to MSAL-native Microsoft Entra config under AzureAd (plus BotFramework service endpoints), aligned with standard ASP.NET Core configuration.

appsettings.json
{
"AzureAd": {
"TenantId": "your-tenant-id",
"ClientId": "your-client-id",
"ClientCredentials": [
{
"SourceType": "ClientSecret",
"ClientSecret": "your-client-secret"
}
]
}
}

For sovereign cloud or non-public endpoints, add the corresponding service settings:

appsettings.json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.us/"
},
"BotFramework": {
"OpenIdMetadataUrl": "https://login.botframework.azure.us/v1/.well-known/openid-configuration",
"BotTokenIssuer": "https://api.botframework.us"
}
}

3. Replace startup/bootstrap code​

Update Program.cs to the SDK 2.1 hosting model:

Program.cs
using Microsoft.Teams.Apps;

WebApplicationBuilder builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddTeamsBotApplication();

WebApplication app = builder.Build();
TeamsBotApplication teams = app.UseTeamsBotApplication();

app.Run();

4. Update helper calls to SDK 2.1 APIs​

Keep simple text helpers (Send/Reply) and update async call patterns for SDK 2.1.
When sending typed activities, use MessageActivityInput.

Context helpers

teams.OnMessage(async (context, cancellationToken) =>
{
await context.SendAsync("Acknowledged", cancellationToken);
await context.ReplyAsync("Got it!", cancellationToken);
await context.QuoteAsync("1772050244572", "Referencing an earlier message", cancellationToken);

await context.SendAsync(new MessageActivityInput("Hello from bot"), cancellationToken);
await context.ReplyAsync(new MessageActivityInput("Reply with activity object"), cancellationToken);
await context.QuoteAsync(
"1772050244572",
new MessageActivityInput("Quoted reply with activity object"),
cancellationToken);
});

App helpers:

await teams.SendAsync(
conversationId,
"Welcome",
cancellationToken: cancellationToken);
await teams.SendAsync(
conversationId,
new MessageActivityInput("Welcome with activity object"),
serviceUrl: "https://smba.trafficmanager.net/teams/",
cancellationToken: cancellationToken);

await teams.ReplyAsync(
conversationId,
"Flat chat update",
cancellationToken: cancellationToken);
await teams.ReplyAsync(
conversationId,
"1772050244572",
"Thread update",
cancellationToken: cancellationToken);

5. OAuth migration​

In SDK 2.0, OAuth was typically handled through app-level sign-in events after invoking context.SignIn(...).
In SDK 2.1, OAuth is modeled as named flows (AddOAuthFlow + GetOAuthFlow) with explicit success and failure callbacks per flow.

Use these end-to-end OAuth patterns with three key handlers: /signin, sign-in success, and sign-in failure.

builder.Services.AddTeamsBotApplication(options =>
{
options.AddOAuthFlow("graph", oauth =>
{
oauth.OAuthCardText = "Sign in to Microsoft Graph";
oauth.SignInButtonText = "Sign in to Graph";
});
});

OAuthFlow graphAuth = teams.GetOAuthFlow("graph");

graphAuth.OnSignInComplete(async (context, tokenResponse, cancellationToken) =>
{
await context.SendAsync($"Signed in ({tokenResponse.ConnectionName}).", cancellationToken);
});

teams.OnMessage("/signin", async (context, cancellationToken) =>
{
string? token = await graphAuth.SignInAsync(context, cancellationToken);
if (token is not null)
{
await context.SendAsync("You are already signed in.", cancellationToken);
}
});

graphAuth.OnSignInFailure(async (context, failure, cancellationToken) =>
{
await context.SendAsync($"Sign-in failed: {failure?.Code} - {failure?.Message}", cancellationToken);
});

6. Migrate to built-in state management​

SDK 2.1 state is cleaner: call options.UseState() and use context.State.ConversationState / context.State.UserState in handlers.

It is also easy to scale. UseState() uses the standard ASP.NET Core IDistributedCache abstraction under the hood with an in-memory default for local development, and you can move to shared state by registering a distributed cache provider (for example Redis) without changing your handler state logic.

builder.Services.AddTeamsBotApplication(options =>
{
options.UseState();
});

teams.OnMessage(async (context, cancellationToken) =>
{
context.State.ConversationState.Set("lastText", context.Activity.Text ?? string.Empty);
string? lastText = context.State.ConversationState.Get<string>("lastText");
await context.SendAsync($"Saved state: {lastText}", cancellationToken);
});

7. AI migration​

With 2.1, Teams SDK no longer includes dedicated AI libraries and has tightened its focus on providing a developer-friendly interface to Teams. Keep Microsoft.Teams.Apps as your Teams transport/runtime layer and plug in a dedicated AI stack (for example Microsoft Agent Framework, OpenAI SDK, MCP, or A2A) from your handlers.

For migration guidance and rationale, see the AI libraries to agent frameworks blog.

// SDK 2.1: Teams runtime handles transport; bring your own AI stack.
// This example uses the OpenAI SDK directly — swap for any framework.
using OpenAI.Chat;

var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddTeamsBotApplication();

// Register OpenAI client via DI
builder.Services.AddSingleton(_ =>
new ChatClient("gpt-4o", builder.Configuration["OpenAI:ApiKey"]));

var app = builder.Build();
TeamsBotApplication teams = app.UseTeamsBotApplication();

teams.OnMessage(async (context, cancellationToken) =>
{
ChatClient chatClient = context.Services.GetRequiredService<ChatClient>();

ChatCompletion completion = await chatClient.CompleteChatAsync(
[new UserChatMessage(context.Activity.Text ?? string.Empty)],
cancellationToken: cancellationToken);

await context.SendAsync(completion.Content[0].Text, cancellationToken);
});

app.Run();

8. Cards migration​

You can still use Microsoft.Teams.Cards in SDK 2.1. Build cards with the cards package, then serialize to JsonElement before attaching to MessageActivityInput.

using System.Text.Json;
using Microsoft.Teams.Cards;

teams.OnMessage(async (context, cancellationToken) =>
{
var card = new AdaptiveCard
{
Version = "1.5",
Body = new List<CardElement>
{
new TextBlock("Hello from SDK 2.1") { Wrap = true }
}
};

JsonElement cardElement = JsonSerializer.SerializeToElement(card);

await context.SendAsync(
new MessageActivityInput()
.WithText("Card message")
.WithAdaptiveCardAttachment(cardElement),
cancellationToken);
});

9. Streaming migration​

In SDK 2.0, streaming used context.Stream.Emit(...) to push chunks.
In SDK 2.1, TeamsStreamingWriter manages chunk updates and requires an explicit finalize step to complete the response.

teams.OnMessage(async (context, cancellationToken) =>
{
TeamsStreamingWriter writer = TeamsStreamingWriter.CreateFromContext(context);
await writer.AppendResponseAsync("hello", cancellationToken);
await writer.AppendResponseAsync(", ", cancellationToken);
await writer.AppendResponseAsync("world!", cancellationToken);
await writer.FinalizeResponseAsync(cancellationToken: cancellationToken);
});

10. Message extensions, task modules, files, and meetings​

This area has the biggest shape change beyond startup/auth. The handler names stay familiar (OnQuery, OnTaskFetch, OnFileConsent, OnMeetingStart), but the payload types and namespaces move to new feature-specific modules in SDK 2.1.

Key updates to expect:

  • Microsoft.Teams.Apps.Activities.* invoke/event payloads move to feature namespaces like Microsoft.Teams.Apps.MessageExtensions, TaskModules, Files, and Meetings.
  • Invoke handlers now use typed InvokeActivity<T> values and return typed response objects (MessageExtensionResponse, TaskModuleResponse, AdaptiveCardResponse) instead of older activity-wrapper types.
  • Meeting events use typed event values like MeetingStartValue in EventActivity<T>.
using Microsoft.Teams.Apps.Files;
using Microsoft.Teams.Apps.Meetings;
using Microsoft.Teams.Apps.MessageExtensions;
using Microsoft.Teams.Apps.TaskModules;

teams.OnQuery(async (context, cancellationToken) =>
{
MessageExtensionQuery? query = context.Activity.Value;
return MessageExtensionResponse.CreateBuilder()
.WithType(MessageExtensionResponseTypes.Message)
.WithText($"query: {query?.CommandId}")
.Build();
});

teams.OnTaskFetch(async (context, cancellationToken) =>
{
return TaskModuleResponse.CreateBuilder()
.WithType(TaskModuleResponseTypes.Message)
.WithMessage("Task fetch handled.")
.Build();
});

teams.OnFileConsent(async (context, cancellationToken) =>
{
var consent = context.Activity.Value;
await context.SendAsync($"File received: {consent?.FileInfo?.Name}", cancellationToken);
});

teams.OnMeetingStart(async (context, cancellationToken) =>
{
var activity = context.Activity.Value;
await context.SendAsync($"'{activity.Title}' has started.", cancellationToken);
});

11. Plugin/event-bus patterns → turn middleware​

SDK 2.1 removes plugin/event-bus style extensibility. Use ITurnMiddleware for cross-cutting behavior.

using Microsoft.Teams.Apps;
using Microsoft.Teams.Core;
using Microsoft.Teams.Core.Schema;

internal sealed class LoggingMiddleware : ITurnMiddleware
{
public async Task OnTurnAsync(
BotApplication botApplication,
CoreActivity activity,
NextTurn nextTurn,
CancellationToken cancellationToken = default)
{
Console.WriteLine($"Processing activity: {activity}");
await nextTurn(cancellationToken);
}
}

teams.UseMiddleware(new LoggingMiddleware());

12. Update local dev auth behavior​

Use this setting when testing locally with the Playground or another client that sends unauthenticated requests.

Properties/launchSettings.json
{
"profiles": {
"YourBot": {
"commandName": "Project",
"environmentVariables": {
"AzureAd__DangerouslyAllowUnauthenticatedRequests": "true"
}
}
}
}