Bulk export Microsoft Teams chats to Markdown files via Playwright browser automation - for when API access is not an option.
Based on microsoft-teams-chat-extractor by Ingo Muschenetz. Rewritten as a Playwright-based CLI for automated bulk export with incremental sync, quick-check optimization, and scroll cutoff.
- Bulk export — exports all chats (1:1, group, meetings) in one run
- Incremental sync — only downloads new messages since last export
- Quick-check — skips unchanged chats without full DOM extraction
- Scroll cutoff — stops scrolling when it reaches already-exported messages
- Hidden chat discovery — finds chats hidden from the Teams sidebar via internal API
- Retention analysis — reports chat age distribution and sidebar visibility window
- Node.js (v18+)
- Google Chrome launched with remote debugging enabled
Chrome must be started with three flags. Pick the command for your OS:
macOS:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--remote-allow-origins='*' \
--user-data-dir="$HOME/Library/Application Support/Google/Chrome-Debug" &Linux:
google-chrome \
--remote-debugging-port=9222 \
--remote-allow-origins='*' \
--user-data-dir="$HOME/.config/google-chrome-debug" &Windows (PowerShell):
& "C:\Program Files\Google\Chrome\Application\chrome.exe" `
--remote-debugging-port=9222 `
--remote-allow-origins=* `
--user-data-dir="$env:LOCALAPPDATA\Google\Chrome-Debug"Then navigate to https://teams.microsoft.com and sign in.
Note:
--user-data-dirmust point to a separate profile directory — Chrome refuses to enable DevTools on the default profile. On macOS/zsh, the--remote-allow-origins='*'flag must be quoted.
git clone <this-repo>
cd teams-webapp-exporter
npm installnpx tsx export-all.ts [options]| Flag | Default | Description |
|---|---|---|
--days <n> |
0 (all) |
How far back to scroll per chat (0 = full history) |
--output <dir> |
./output |
Target directory for Markdown files |
--chat <name> |
— | Export only chats matching this substring |
--dry-run |
false |
List all chats without exporting |
--port <n> |
9222 |
Chrome remote debugging port |
--timeout <s> |
120 |
Per-chat extraction timeout in seconds |
--scroll-down |
false |
Scroll down from current position instead of up |
--region <r> |
emea |
Teams chatsvc API region (emea, amer, apac) |
--user <name> |
auto-detect | Your display name (for resolving 1:1 chat names) |
--resume |
false |
Resume from last checkpoint if interrupted |
List all chats without exporting:
npx tsx export-all.ts --dry-runExport all chats from the last 30 days:
npx tsx export-all.ts --days 30Export a specific chat:
npx tsx export-all.ts --chat "Project Updates"Export to a custom directory with a different region:
npx tsx export-all.ts --output ./exports --region amerEach Markdown file ends with a <!-- last-message: ... --> comment containing the timestamp of the newest exported message. On subsequent runs:
- Quick-check — reads the newest visible DOM timestamp and compares it to the file marker. If unchanged, the chat is skipped entirely (no scrolling).
- Scroll cutoff — if new messages exist, scrolling stops when it reaches the last-exported timestamp instead of loading full history.
- Append — only new messages are appended to the existing file with a separator.
Each chat produces a Markdown file named after the chat (sanitized). Messages are grouped by date with author headers:
# Chat Name
Exported: 2026-02-13T15:00:00.000Z
## Thursday, 13 February 2026
**Alice** [2026-02-13T09:15:00Z]:
Good morning! Here's the update...
**Bob** [2026-02-13T09:20:00Z]:
Thanks, looks good.npm test # Run all tests (Vitest)
npm run lint # Run ESLint53 tests across 4 modules: message extraction, Markdown conversion, retention analysis, and Graph API chat discovery. Tests use temp directories for file I/O and mock fetch for API calls — no network or browser needed.
A CycloneDX Software Bill of Materials is included at sbom.cdx.json covering production dependencies only. Regenerate after dependency changes:
npm run sbomMIT — see LICENSE. Original work copyright (c) 2021-2026 Ingo Muschenetz.