Microsoft Graph SDK for TypeScript / JavaScript

May 14, 2026 · View on GitHub

Use this reference when the target project uses TypeScript or JavaScript (Node.js or browser).

Authoritative sources

Packages

npm install @microsoft/microsoft-graph-client @azure/identity
npm install -D @microsoft/microsoft-graph-types   # TypeScript type definitions

For Node.js environments, also install the fetch polyfill:

npm install node-fetch

Client setup

Managed Identity (Azure-hosted apps — preferred)

import { Client } from "@microsoft/microsoft-graph-client";
import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials/index.js";
import { DefaultAzureCredential } from "@azure/identity";

const credential = new DefaultAzureCredential();
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
  scopes: ["https://graph.microsoft.com/.default"],
});

const graphClient = Client.initWithMiddleware({ authProvider });

Client credentials (app-only / daemon)

import { ClientSecretCredential } from "@azure/identity";

const credential = new ClientSecretCredential(
  process.env.AZURE_TENANT_ID!,
  process.env.AZURE_CLIENT_ID!,
  process.env.AZURE_CLIENT_SECRET!
);

const authProvider = new TokenCredentialAuthenticationProvider(credential, {
  scopes: ["https://graph.microsoft.com/.default"],
});

const graphClient = Client.initWithMiddleware({ authProvider });

On-Behalf-Of (OBO) — agent / API acting as the signed-in user

import { OnBehalfOfCredential } from "@azure/identity";

// incomingToken is the bearer token received from the caller (e.g. from req.headers.authorization)
const credential = new OnBehalfOfCredential({
  tenantId: process.env.AZURE_TENANT_ID!,
  clientId: process.env.AZURE_CLIENT_ID!,
  clientSecret: process.env.AZURE_CLIENT_SECRET!,
  userAssertionToken: incomingToken,
});

const authProvider = new TokenCredentialAuthenticationProvider(credential, {
  scopes: ["https://graph.microsoft.com/.default"],
});

const graphClient = Client.initWithMiddleware({ authProvider });

For OBO, create a new client per request (credential is user-scoped, not singleton-safe).

Interactive (local dev / CLI — Node.js)

Use InteractiveBrowserCredential when a browser is available. Use DeviceCodeCredential for headless environments (SSH, CI-adjacent, WSL):

import { InteractiveBrowserCredential, DeviceCodeCredential } from "@azure/identity";

// Opens a browser tab — requires redirect URI http://localhost in app registration
const credential = new InteractiveBrowserCredential({
  tenantId: process.env.AZURE_TENANT_ID!,
  clientId: process.env.AZURE_CLIENT_ID!,
});

// Prints a device code to the terminal — works in any environment
const credential = new DeviceCodeCredential({
  tenantId: process.env.AZURE_TENANT_ID!,
  clientId: process.env.AZURE_CLIENT_ID!,
  userPromptCallback: (info) => console.log(info.message),
});

Both require the app registration platform to be "Mobile and desktop applications". Neither uses a client secret.

Common call patterns

Get a resource with field selection

import { User } from "@microsoft/microsoft-graph-types";

const user: User = await graphClient
  .api("/me")
  .select("displayName,mail,jobTitle")
  .get();

List with filter, select, and ordering

const result = await graphClient
  .api("/me/messages")
  .filter("isRead eq false")
  .select("subject,from,receivedDateTime")
  .top(25)
  .orderby("receivedDateTime desc")
  .get();

Pagination with PageIterator

import { PageIterator } from "@microsoft/microsoft-graph-client";
import { Message } from "@microsoft/microsoft-graph-types";

const firstPage = await graphClient.api("/me/messages").top(25).get();

const allMessages: Message[] = [];

const pageIterator = new PageIterator(
  graphClient,
  firstPage,
  (message: Message) => {
    allMessages.push(message);
    return true; // return false to stop early
  }
);

await pageIterator.iterate();

Send an email

await graphClient.api("/me/sendMail").post({
  message: {
    subject: "Hello from Graph",
    body: { contentType: "Text", content: "Test message" },
    toRecipients: [{ emailAddress: { address: "user@contoso.com" } }],
  },
});

Post a Teams channel message

await graphClient.api(`/teams/${teamId}/channels/${channelId}/messages`).post({
  body: { contentType: "html", content: "<b>Hello from Graph!</b>" },
});

Upload a file to OneDrive (small files ≤ 4 MB)

const content = Buffer.from("file contents");
await graphClient
  .api(`/me/drive/root:/${fileName}:/content`)
  .putStream(content);

For files > 4 MB, use an upload session (createUploadSession).

Batch requests

const batchRequestBody = {
  requests: [
    { id: "1", method: "GET", url: "/me" },
    { id: "2", method: "GET", url: "/me/messages?$top=5&$select=subject" },
  ],
};

const batchResponse = await graphClient.api("/$batch").post(batchRequestBody);

const meResponse = batchResponse.responses.find((r: any) => r.id === "1");
const messagesResponse = batchResponse.responses.find((r: any) => r.id === "2");

Delta queries

// First sync
let response = await graphClient.api("/users/delta").get();
const users: any[] = [];

while (response["@odata.nextLink"]) {
  users.push(...response.value);
  response = await graphClient.api(response["@odata.nextLink"]).get();
}
users.push(...response.value);

const deltaLink: string = response["@odata.deltaLink"];
// Store deltaLink durably for next sync run

// Next sync — only changes
const changesResponse = await graphClient.api(deltaLink).get();

Throttling / retry middleware

The SDK includes retry middleware by default. For explicit configuration:

import {
  Client,
  RetryHandlerOptions,
  RetryHandler,
  MiddlewareFactory,
} from "@microsoft/microsoft-graph-client";

const retryOptions = new RetryHandlerOptions({ maxRetries: 5 });
const middleware = MiddlewareFactory.getDefaultMiddlewareChain(authProvider);

const graphClient = Client.initWithMiddleware({ middleware });

Always honour the Retry-After header value — do not use fixed backoff when Graph specifies a wait time.

TypeScript-specific guidance

  • Import types from @microsoft/microsoft-graph-types for full IntelliSense on Graph resources.
  • The .api() chain returns any — cast to the appropriate type from @microsoft/microsoft-graph-types.
  • For ESM projects, use the /index.js path suffix on deep imports (e.g., azureTokenCredentials/index.js).
  • Use async/await consistently — all Graph calls return Promises.
  • Singleton the graphClient in application-level code (e.g., Express app init); for OBO flows, construct per-request.
  • In Node.js 18+, fetch is available natively — no polyfill needed.
// Type-safe response example
import { MessageCollectionResponse } from "@microsoft/microsoft-graph-types";

const response: MessageCollectionResponse = await graphClient
  .api("/me/messages")
  .select("subject,from")
  .get();

const messages = response.value ?? [];