runtime.app-init-ts
purpose
Teams SDK v2 App initialization, constructor options, plugins, logger setup, storage config, OAuth, activity context, and startup lifecycle.
rules
- Always import
Appfrom@microsoft/teams.apps,ConsoleLoggerfrom@microsoft/teams.common, andDevtoolsPluginfrom@microsoft/teams.devas the minimum bootstrap triple. These three packages are always present independencies. github.com/microsoft/teams.ts - Pass
clientId,clientSecret, andtenantIdto theAppconstructor when the bot requires Azure Bot registration credentials. All three come from environment variables (CLIENT_ID,CLIENT_SECRET,TENANT_ID). Omit them only for local-only development withskipAuth: true. learn.microsoft.com -- Bot registration - Configure logging with
new ConsoleLogger(name, { level })wherelevelis one of'error' | 'warn' | 'info' | 'debug' | 'trace'. Usepattern: '-azure/msal-node'to suppress noisy child loggers. Child loggers inherit settings vialogger.child('sub-name')and prefix output as[parent/child]. github.com/microsoft/teams.ts -- common - Register plugins via the
pluginsarray in the constructor or dynamically withapp.plugin(instance). Every development project should includeDevtoolsPlugin. Plugin lifecycle follows: register ->onInit()->onStart({ port })-> activity loop (onActivity()/onActivitySent()) ->onStop(). github.com/microsoft/teams.ts -- dev - Configure OAuth by adding
oauth: { defaultConnectionName: 'graph' }toAppOptions. This enablesctx.isSignedIn,ctx.signin(),ctx.signout(), andctx.userGraphon every handler context. RequiresclientId,clientSecret, andtenantIdto also be set. github.com/microsoft/teams.ts -- apps - Storage defaults to in-memory. Pass a custom
IStorageimplementation to thestorageoption for persistence across restarts. UseLocalStoragefrom@microsoft/teams.commonfor development with optional LRU eviction via{ max: N }. github.com/microsoft/teams.ts -- common - Call
app.start(port)(default3978) as the final step. It returns aPromise-- always attach.catch(console.error)or useawait. The bot endpoint ishttp://localhost:{port}/api/messagesand DevTools UI runs on{port + 1}. github.com/microsoft/teams.ts - Set
skipAuth: trueonly during local development without Azure credentials. This disables JWT validation on inbound activities. Never use this in production. github.com/microsoft/teams.ts -- apps - The full
AppOptionsreference includes:clientId,clientSecret,tenantId,token(custom token factory),managedIdentityClientId('system'or string),client(custom HTTP client),logger(ILogger),storage(IStorage),plugins(IPlugin[]),oauth(OAuthSettings),manifest(Partial<Manifest>),skipAuth(boolean), andactivity.mentions.stripText(boolean). github.com/microsoft/teams.ts -- apps - Use
app.event('start', ...)for post-listen setup,app.event('error', ...)for global error handling,app.event('signin', ...)for post-authentication logic, andapp.event('activity', ...)/app.event('activity.sent', ...)for observing all inbound/outbound activities. These are lifecycle events, not activity route handlers. github.com/microsoft/teams.ts
patterns
Minimal App with DevTools and logger
import { App } from '@microsoft/teams.apps';
import { ConsoleLogger } from '@microsoft/teams.common';
import { DevtoolsPlugin } from '@microsoft/teams.dev';
const app = new App({
logger: new ConsoleLogger('echo-bot', { level: 'debug' }),
plugins: [new DevtoolsPlugin()],
});
app.on('message', async ({ reply, activity }) => {
await reply({ type: 'typing' });
await reply(`You said: "${activity.text}"`);
});
app.start(3978);Full production App with credentials, OAuth, and storage
import { App } from '@microsoft/teams.apps';
import { ConsoleLogger, LocalStorage } from '@microsoft/teams.common';
import { DevtoolsPlugin } from '@microsoft/teams.dev';
interface AppState {
conversationIds: string[];
}
const app = new App({
// Azure Bot registration credentials
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
tenantId: process.env.TENANT_ID,
// Custom logger with noise filtering
logger: new ConsoleLogger('prod-bot', {
level: 'info',
pattern: '-azure/msal-node',
}),
// Persistent storage (in-memory with LRU for dev)
storage: new LocalStorage<AppState>({}, { max: 1000 }),
// OAuth for Microsoft Graph
oauth: { defaultConnectionName: 'graph' },
// Plugins
plugins: [new DevtoolsPlugin()],
});
// Lifecycle events
app.event('start', (logger) => {
logger.info('Bot is running');
});
app.event('error', ({ error, log }) => {
log.error('Unhandled error:', error);
});
app.event('signin', async ({ send, userGraph }) => {
// Fired after successful OAuth sign-in
await send('You are now signed in.');
});
app.event('activity', ({ activity }) => {
// Fired for every inbound activity
});
app.event('activity.sent', ({ activity }) => {
// Fired after every outbound activity
});
app.start(process.env.PORT || 3978).catch(console.error);Activity context usage in a handler
app.on('message', async (ctx) => {
// --- Properties ---
ctx.appId; // Bot app ID
ctx.activity; // The inbound Activity object
ctx.ref; // ConversationReference for proactive messaging
ctx.log; // Scoped logger
ctx.api; // Teams API client
ctx.appGraph; // Graph client (app credentials)
ctx.userGraph; // Graph client (user credentials, after signin)
ctx.storage; // Persistent storage
ctx.stream; // Streaming response helper
ctx.isSignedIn; // Whether user has authenticated
ctx.userToken; // User's OAuth access token
ctx.connectionName; // OAuth connection name
// --- Methods ---
await ctx.send('Hello!'); // Send a new message
await ctx.reply('Reply to this'); // Reply to the current message
await ctx.signin(); // Trigger OAuth sign-in flow
await ctx.signout(); // Sign the user out
ctx.next(); // Pass to next middleware/handler
});pitfalls
- Missing
.catch()onapp.start(): The method returns a Promise. Unhandled rejections crash the process in Node 20+. Always add.catch(console.error)or wrap in an async IIFE with try/catch. - Using
skipAuth: truein production: This disables JWT validation entirely. Any HTTP client can send fake activities to your bot endpoint. Only use it for local DevTools testing. - Forgetting
DevtoolsPluginduring development: Without it, there is no DevTools UI atlocalhost:3979/devtoolsand no WebSocket-based activity inspection. Always include it in thepluginsarray for local dev. - Setting OAuth without credentials: Adding
oauth: { defaultConnectionName: 'graph' }withoutclientId/clientSecret/tenantIdcauses silent auth failures. All four options must be present together. - Calling
app.start()before registering handlers: Handlers registered afterstart()may miss early activities. Register allapp.on(),app.message(), andapp.use()calls before callingapp.start(). - Logger level too verbose in production: Using
'debug'or'trace'floods logs with internal SDK chatter. Use'info'or'warn'for deployed bots. - Hardcoding the port: Always read from
process.env.PORTwith a fallback (process.env.PORT || 3978). Azure App Service and container hosts setPORTdynamically. - Confusing
app.on()withapp.event():app.on()registers activity route handlers (message, card.action, etc.).app.event()registers app lifecycle hooks (start, error, signin). Mixing them up causes handlers that never fire.
references
- Teams SDK v2 GitHub repository
- Teams SDK v2 -- @microsoft/teams.apps
- Teams SDK v2 -- @microsoft/teams.common
- Teams SDK v2 -- @microsoft/teams.dev (DevtoolsPlugin)
- Azure Bot Service documentation
- Teams platform: Build bots
instructions
This expert covers the foundational App class from @microsoft/teams.apps -- the entry point for every Teams SDK v2 bot. Use it when you need to:
- Initialize a new
Appinstance with the correct constructor options - Configure credentials (
clientId,clientSecret,tenantId) for Azure Bot registration - Set up logging with
ConsoleLogger, child loggers, and noise filtering - Register plugins (especially
DevtoolsPlugin) and understand plugin lifecycle - Configure OAuth for Microsoft Graph access
- Set up storage backends for persistent state
- Understand the activity context object (
ctx) and its full set of properties/methods - Handle app lifecycle events (
start,error,activity,activity.sent,signin) - Start the server with
app.start()and understand the default endpoints
Pair with runtime.routing-handlers-ts.md for route registration patterns and project.scaffold-files-ts.md for full project setup including package.json and tsconfig. Pair with project.scaffold-files-ts.md for package.json and project structure, and dev.debug-test-ts.md for local development setup.
research
Deep Research prompt:
"Write a micro expert on Teams SDK v2 App initialization in TypeScript. Cover the App constructor from @microsoft/teams.apps, all AppOptions fields (clientId, clientSecret, tenantId, logger, storage, plugins, oauth, skipAuth, manifest, token, managedIdentityClientId, client, activity.mentions.stripText), ConsoleLogger configuration with levels and pattern filtering, DevtoolsPlugin setup and lifecycle hooks (onInit, onStart, onActivity, onActivitySent, onStop), activity context properties and methods (send, reply, signin, signout, next, stream, isSignedIn, appGraph, userGraph, ref, api, storage, log), app.start() lifecycle, and app.event() hooks (start, error, signin, activity, activity.sent). Include 2-3 initialization patterns from minimal to production-ready."