The Problem
Microsoft Teams does not provide a simple way for regular users to export or save their chat conversations locally. The built-in export options are either locked behind admin portals, require IT intervention, or produce formats that are difficult to read.
On top of that, Teams renders its chat using a virtualized list — meaning only the messages currently visible on screen exist in the DOM at any given time. Any naive scraping approach would only capture a small window of the conversation, not the full history.
"For individuals who want a personal record of their conversations — for compliance, reference, or archiving — there was no easy solution. So I built one."
What It Does
- Adds a one-click "Save Chat" button via a browser popup
- Automatically scrolls back through the entire chat history, triggering Teams to load older messages batch by batch
- Extracts every message — sender name, timestamp, and content — from the DOM
- Cleans the output by removing UI noise (buttons, reaction labels, system chrome)
- Deduplicates messages that appear multiple times due to virtualized rendering
- Saves the result as a plain
.txtfile named after the conversation
Output Format
[2024-03-15 09:32] Alice Johnson: Good morning everyone [2024-03-15 09:33] Bob Smith: Morning! Ready for the standup? [2024-03-15 09:33] Alice Johnson: Yes, give me 2 minutes
Architecture
The extension follows the standard Manifest V3 three-layer architecture:
Popup (popup.js) └── sends message to → Background Service Worker (background.js) └── injects and calls → Content Script (scraper.js) └── reads from → Teams DOM
- popup.js — Handles the UI, renders the Save Chat button, shows live progress, and displays success or error feedback.
- background.js — The orchestrator. Validates the active tab is a Teams page, injects the scraper, relays progress updates, and triggers the file download via
chrome.downloads. - scraper.js — The core engine. Contains all DOM extraction logic: scroll loading, message extraction, deduplication, and noise removal.
Key Engineering Challenges
Virtualized DOM
Teams only renders visible messages. The scraper uses scrollLoadHistory() — incrementally scrolling to the top, polling for new messages after each scroll until the count stabilises or a 2-minute timeout is hit.
Obfuscated Selectors
Teams' CSS class names are obfuscated and change with every deployment. The scraper uses a resilience-first approach: data-tid attributes first, then ARIA roles, never class names.
Noise Removal
Teams injects visually-hidden UI elements (reaction bars, action buttons) inside each message bubble. The scraper collects and strips these strings line-by-line before reading content.
Deduplication
Virtualized rendering causes the same message to appear multiple times as the user scrolls. A composite key (sender + timestamp + content) is used to deduplicate the final record set.
Testing
The project has a comprehensive test suite of 87 tests using Jest for unit tests and fast-check for property-based tests.
- Filename sanitization never produces invalid characters
- Message extraction always produces exactly N records for N bubbles
- Deduplication is idempotent
- Scroll loop progress values are strictly increasing
- Timeout with zero messages throws instead of saving an empty file
Built with Agentic AI
This entire project — requirements, design, implementation, 87 tests, debugging, and documentation — was produced through conversation with Kiro, an agentic AI IDE. The human role was to describe the problem, review outputs, test against the real Teams app, and report what was wrong. The agent handled the rest.
"This demonstrates how agentic AI can compress the full software development lifecycle: from idea to a tested, working, deployable artifact — with the developer acting as a product owner rather than a line-by-line author."