Skip to content
Learni
View all tutorials
Microsoft 365

How to Create a Microsoft Teams Bot with Node.js in 2026

Lire en français

Introduction

Microsoft Teams is more than a collaboration tool: it's an extensible platform where bots automate daily tasks, like responding to messages or managing workflows. In 2026, with the rise of AI and hybrid integrations, creating a custom bot boosts team productivity. This intermediate tutorial walks you through building an advanced echo bot with Node.js and the Bot Framework SDK. It handles messages, slash commands, and Adaptive Cards for a rich UX.

Why it matters: Teams bots handle millions of interactions daily in enterprises. You'll learn to configure the app in Azure AD, craft a precise manifest, code a resilient bot, and deploy it to Azure. Result: a production-ready, scalable, and secure bot in under an hour. Ideal for full-stack devs wanting to monetize Teams extensions. (128 words)

Prerequisites

  • Node.js 20+ installed
  • Microsoft 365 Developer account (free at developer.microsoft.com)
  • Azure Portal access (free account is enough)
  • Basic knowledge of TypeScript/Node.js and REST APIs
  • Visual Studio Code with Node.js and Azure extensions
  • ngrok for local testing (optional but recommended)

Initialize the Node.js Project

terminal
mkdir teams-bot && cd teams-bot
npm init -y
npm install botbuilder restify dotenv @microsoft/teamsfx-cli
npm install -D @types/node @types/restify typescript ts-node
npx tsc --init
npm install -g @microsoft/teamsfx-cli

These commands create a dedicated Node.js project, install the Bot Framework SDK for handling Teams conversations, Restify as a lightweight HTTP server, and dotenv for secrets. TeamsFx CLI simplifies manifest config. Avoid npm audit fix to prevent breaking Bot Framework deps; always test locally first.

Configure the App in Azure AD

Go to portal.azure.com > App registrations > New registration.

  • Name: MonBotTeams2026
  • Supported account types: Accounts in any organizational directory
  • Redirect URI: Web, https://token.botframework.com/.auth/web/redirect
Note the Application (client) ID and generate a Client secret (Certificates & secrets). Create a Bot Channels Registration in Azure Bot Service:
  • Link the App ID and secret.
  • Messaging endpoint: https://votre-ngrok-url/api/messages (update later).
Copy the Microsoft App ID and App Password. Think of it as a passport for your bot in the Microsoft ecosystem.

Create the .env Configuration File

.env
MICROSOFT_APP_ID=votre_app_id_azure
MICROSOFT_APP_PASSWORD=votre_app_password
BOT_ID=votre_app_id_azure
BOT_PASSWORD=votre_app_password
PORT=3978

This file securely stores Azure secrets, loaded via dotenv. Replace with your real values. Common pitfall: forgetting to add .env to .gitignore; always do this to avoid credential leaks in production.

Develop the Bot's Main Code

src/bot.ts
import * as restify from 'restify';
import { BotFrameworkAdapter, MemoryStorage, ConversationState, UserState } from 'botbuilder';
import * as dotenv from 'dotenv';
import { TeamsActivityHandler, TeamsInfo, CardFactory, MessageFactory, AttachmentLayoutTypes } from 'botbuilder';

dotenv.config();

const server = restify.createServer();
server.use(restify.plugins.bodyParser());

const adapter = new BotFrameworkAdapter({
  appId: process.env.MICROSOFT_APP_ID,
  appPassword: process.env.MICROSOFT_APP_PASSWORD
});

const memoryStorage = new MemoryStorage();
const conversationState = new ConversationState(memoryStorage);
const userState = new UserState(memoryStorage);

class TeamsBot extends TeamsActivityHandler {
  constructor(conversationState: ConversationState, userState: UserState) {
    super();
    this.onMessage(async (context, next) => {
      const text = context.activity.text;
      if (text.includes('/help')) {
        const card = CardFactory.adaptiveCard({
          type: 'AdaptiveCard',
          version: '1.5',
          body: [{ type: 'TextBlock', text: 'Bot echo: répète vos messages ! /help pour aide.' }]
        });
        await context.sendActivity(MessageFactory.attachment(card));
      } else {
        await context.sendActivity(`Echo: ${text}`);
      }
      await next();
    });

    this.onMembersAdded(async (context, next) => {
      const membersAdded = context.activity.membersAdded;
      const welcomeText = 'Bonjour ! Tapez /help pour commencer.';
      for (let cnt = 0; cnt < membersAdded.length; ++cnt) {
        if (membersAdded[cnt].id !== context.activity.recipient.id) {
          await context.sendActivity(MessageFactory.text(welcomeText));
        }
      }
      await next();
    });
  }
}

const bot = new TeamsBot(conversationState, userState);

adapter.onTurnError = async (context, error) => {
  console.error(`\n [onTurnError]: ${error}`);
  await context.sendTraceActivity(
    'OnTurnError Trace',
    `${error}`,
    'https://www.botframework.com/schemas/error',
    'TurnError'
  );
  await context.sendActivity('Désolé, une erreur est survenue.');
};

server.post('/api/messages', (req, res) => {
  adapter.processActivity(req, res, async (context) => {
    await bot.run(context);
  });
});

server.listen(process.env.PORT || 3978, () => {
  console.log(`\nServer up at: http://localhost:${process.env.PORT || 3978}`);
});

This bot extends TeamsActivityHandler to manage messages, joins, and errors. It echoes text, responds to /help with an Adaptive Card, and uses MemoryStorage for temporary state. Tip: Always implement onTurnError for logging; in production, switch to CosmosDB storage for persistence.

Compile and Test Locally

Add to tsconfig.json: "module": "commonjs", "target": "es2020". Run npx ts-node src/bot.ts. Use ngrok: ngrok http 3978 for a public URL. Update the Messaging endpoint in Azure Bot Service with https://votre-ngrok.ngrok.io/api/messages.

Side-load the bot in Teams: Developer Portal > Apps > New app > Upload manifest (next code block). Test: the bot welcomes users, echoes, and shows cards. Analogy: like a virtual butler anticipating needs.

Generate and Customize the Teams Manifest

manifest/teams-manifest.json
{
  "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
  "manifestVersion": "1.16",
  "version": "1.0.0",
  "id": "votre_app_id_azure",
  "packageName": "com.monbot.teamsbot2026",
  "developer": {
    "name": "Learni Dev",
    "websiteUrl": "https://learni-group.com",
    "privacyUrl": "https://privacy.example.com",
    "termsOfUseUrl": "https://terms.example.com"
  },
  "icons": {
    "color": "color.png",
    "outline": "outline.png"
  },
  "name": {
    "short": "Bot Echo 2026",
    "full": "Bot Microsoft Teams Echo Avancé"
  },
  "description": {
    "short": "Bot qui écho vos messages",
    "full": "null"
  },
  "accentColor": "#FFFFFF",
  "bots": [
    {
      "botId": "votre_app_id_azure",
      "scopes": ["personal", "team", "groupchat"],
      "supportsFiles": false,
      "isNotificationOnly": false,
      "supportsCalling": false,
      "supportsVideo": false
    }
  ],
  "permissions": ["identity", "messageTeamMembers"],
  "validDomains": ["token.botframework.com", "votre-ngrok.ngrok.io"]
}

This manifest defines the Teams app: Azure ID, scopes (personal/team), and permissions for identity. Replace IDs and add icons (128x128 PNG). Zip it as .zip for side-loading. Tip: Validate JSON with Teams Validator; don't forget validDomains for CORS.

Deploy to Azure App Service

terminal-deploy
git init
git add .
git commit -m "Initial bot"

# Via Azure CLI (az login d'abord)
az group create --name rg-teamsbot --location westeurope
az appservice plan create --name plan-teamsbot --resource-group rg-teamsbot --sku B1 --is-linux
az webapp create --resource-group rg-teamsbot --plan plan-teamsbot --name monteamsbot2026 --runtime "NODE|20-lts"

# Déployer
az webapp deployment source config-local-git --name monteamsbot2026 --resource-group rg-teamsbot

# Mettre à jour endpoint Azure Bot
az bot channel update-teams --name MonBotAzure --resource-group rg-teamsbot --endpoint https://monteamsbot2026.azurewebsites.net/api/messages

These commands create a scalable Linux App Service, deploy via local Git, and update the Teams channel. Use B1 for starters (nearly free). Tip: Ensure NODE|20 runtime; test HTTPS endpoint before production.

Validate and Publish the App

Zip manifest/ (with icons) > Developer Portal > Apps > Upload > Publish. Test in production Teams. For Microsoft certification: submit via Partner Center (requires advanced tests).

Best Practices

  • Secure secrets: Use Azure Key Vault in production, never hardcode.
  • State management: Switch to CosmosDB or BlobStorage for >100 concurrent users.
  • Adaptive Cards Designer: Validate UI at adaptivecards.io.
  • Logging: Integrate Application Insights for traces.
  • Rate limiting: Add Restify middleware to prevent abuse.

Common Errors to Avoid

  • Non-HTTPS endpoint: Teams requires SSL; ngrok free is OK for dev, not prod.
  • Missing scopes: Check manifest for 'team' if multi-user.
  • App ID mismatch: Bot ID must match Azure App ID, double-check.
  • No onTurnError: Bot crashes without it; always catch exceptions.

Next Steps

Master Proactive Messages, AI with Azure OpenAI, or Graph API for users/teams. Check the official Bot Framework docs and our Learni Microsoft 365 training. Next level: bots with Power Virtual Agents.

How to Create a Teams Bot with Node.js in 2026 | Learni