Migrating a Bot Framework v4 bot to the Teams SDK

I’ve been building Teams bots since 2017. It started with my post about the SPAdminBot, then I went deeper with authenticated webhooks in Teams, and at some point I even wrote an entire Apress book on Teams development. Safe to say I’ve spent a fair amount of time with the Bot Framework SDK. So when Microsoft announced that Bot Framework SDK v4 would reach end-of-support on December 31, 2025, I knew I had some work ahead of me. No more security patches, no more bug fixes. Time to migrate.
The SDK landscape is… confusing
First off, let me try to untangle the current state of things because it took me a while to figure out what goes where. There are now essentially THREE SDKs floating around.
The Teams SDK (which used to be called the Teams AI Library before Microsoft renamed it in November 2025) is what you want for Teams-only bots. Then there’s the M365 Agents SDK which is the multi-channel successor to Bot Framework, so use this if your bot needs to live in Outlook, Copilot, and other surfaces beyond Teams. And finally there’s the Azure AI Foundry Agent SDK which is more about building AI agents on Azure infrastructure.
For most of us who just have Teams bots that do Teams things, the Teams SDK is the way to go.
Now, if you’re wondering about TeamsFx, that’s deprecated too. It’ll get community support until September 2026 but don’t start anything new with it. The replacement is the M365 Agents Toolkit (formerly Teams Toolkit). Yes, Microsoft renamed everything. I know. I’m confused too.
What actually changes
The biggest mental shift is that you’re no longer inheriting from ActivityHandler and overriding methods. There’s no more ITurnContext<IMessageActivity> generic gymnastics. No more registering IBotFrameworkHttpAdapter and IBot in your DI container.
The Teams SDK wraps all of that under the hood but gives you a much higher-level abstraction. Think of it as going from ASP.NET MVC to minimal APIs. The plumbing is still there, but you don’t have to touch it anymore. You register message handlers directly, pattern-match on commands, and the SDK takes care of the rest.
The old way
Let me show you what a typical Bot Framework v4 bot looked like. I’m sure this will feel familiar if you’ve been building bots for a while.
// OLD: Bot Framework v4 echo bot
public class LegacyTeamsBot : ActivityHandler
{
protected override async Task OnMessageActivityAsync(
ITurnContext<IMessageActivity> turnContext,
CancellationToken cancellationToken)
{
var userMessage = turnContext.Activity.Text;
userMessage = turnContext.Activity.RemoveRecipientMention();
if (userMessage.Contains("site info", StringComparison.OrdinalIgnoreCase))
{
var siteDetails = await _sharePointService.GetSiteInfo(userMessage);
await turnContext.SendActivityAsync(
MessageFactory.Text($"Site: {siteDetails.Title}, URL: {siteDetails.Url}"),
cancellationToken);
}
else
{
await turnContext.SendActivityAsync(
MessageFactory.Text("I didn't understand that. Try asking for 'site info [url]'."),
cancellationToken);
}
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
services.AddTransient<IBot, LegacyTeamsBot>();
}
You had to create a class, inherit from ActivityHandler, override the right virtual method, remember to strip the recipient mention, wire up the adapter and bot in Startup.cs… it worked, but there was a lot of ceremony.
The new way
Let’s get started with the Teams SDK version. This is the same bot, same functionality, but notice how much less scaffolding there is.
using Microsoft.Teams.AI;
using Microsoft.Teams.AI.State;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTeamsAI(options =>
{
options.AppId = builder.Configuration["MicrosoftAppId"]!;
options.AppPassword = builder.Configuration["MicrosoftAppPassword"]!;
});
var app = builder.Build();
var teamsApp = app.Services.GetRequiredService<TeamsApp>();
teamsApp.OnMessage("/siteinfo", async (context, state, cancellationToken) =>
{
var query = context.Activity.Text.Replace("/siteinfo", "").Trim();
var siteDetails = await sharePointService.GetSiteInfo(query);
await context.SendActivityAsync(
$"Site: {siteDetails.Title}, URL: {siteDetails.Url}",
cancellationToken: cancellationToken);
});
teamsApp.OnMessage(async (context, state, cancellationToken) =>
{
await context.SendActivityAsync(
"I didn't understand that. Try /siteinfo followed by a URL.",
cancellationToken: cancellationToken);
});
app.Run();
No class hierarchy. No generics. No adapter registration. You just register handlers for the messages you care about and a fallback for everything else. The recipient mention stripping? Handled for you. This way you can focus on your actual bot logic instead of the plumbing.
The AI bonus
Of course, the real reason Microsoft built the Teams SDK (remember, it used to be called Teams AI Library) is to make AI integration trivial. With the old Bot Framework v4, adding natural language understanding meant setting up LUIS, configuring dialog management, maintaining prompt flows… it was a whole project on its own.
With the Teams SDK, you can wire up AI capabilities in a few lines.
teamsApp.AI.Prompts.AddFunction("getSiteInfo", async (context, state, parameters) =>
{
var url = parameters["siteUrl"]?.ToString() ?? "";
var info = await sharePointService.GetSiteInfo(url);
return $"Title: {info.Title}, Storage: {info.StorageUsed}MB, Lists: {info.ListCount}";
});
You define functions that the AI model can call, and the SDK handles the orchestration. No LUIS app to train, no waterfall dialogs, no prompt management. It’s a pretty big deal if you’ve ever wrestled with the old approach.
What about the emojis?
If you’ve read my post about emojis in Teams bots, you know that Teams sends emojis as image attachments rather than text, which used to blow up your bot if you weren’t careful. I spent an embarrassing amount of time debugging that one. The good news is that the Teams SDK handles this more gracefully. It doesn’t completely eliminate the quirk (it’s a Teams thing, not a framework thing), but the higher-level message handling makes it much harder to accidentally crash your bot when someone sends a 😊.
A practical note to wrap up
If your bot works fine on Bot Framework v4 today, it won’t suddenly stop working. The Azure Bot Service infrastructure isn’t going away. But you won’t get security patches anymore, and that’s the kind of thing that keeps me up at night. My advice: migrate when you can, don’t wait until something breaks. The migration itself isn’t that painful once you understand the new model. It took me about a day for a medium-complexity bot.
Feel free to reach out if you run into issues. Hope it helps…