Skip to content

Kilowhisky/slack-to-teams-dm-migrator

Repository files navigation

Slack to Teams DM Migrator

Migrate direct messages (1:1 and group DMs) from Slack to Microsoft Teams with full timestamp and user attribution fidelity.

This project was built and is maintained with the assistance of Claude, an AI assistant by Anthropic.

Features

  • Three destination modes: native Teams chats, archive channels, or automatic hybrid routing
  • 1:1 and group DMs: migrates both im (1:1) and mpim (group) Slack conversations
  • Timestamp preservation: messages appear with their original Slack timestamps
  • User attribution: messages are attributed to the correct user via Azure AD mapping
  • Bot attribution fallback: unmapped users' messages are posted by the app with [Display Name] prepended
  • File migration: downloads files from Slack and uploads to Teams/SharePoint
  • Resumable: state file tracks progress per-conversation for safe restart
  • Formatting conversion: Slack mrkdwn converted to Teams-compatible HTML (bold, italic, code, links, mentions, blockquotes, emoji)
  • Rate limiting: built-in Bottleneck-based rate limiting for both Slack and Teams APIs
  • Dry-run mode: preview what would be migrated without posting anything

Prerequisites

Permissions by Mode

Not every mode needs every permission. Use these tables to grant only what you need:

Slack Bot Token Scopes

Scope Chat mode Channel mode Auto mode Purpose
im:read Required Required Required List 1:1 DM conversations
im:history Required Required Required Read 1:1 DM message history
mpim:read Required Required Required List group DM conversations
mpim:history Required Required Required Read group DM message history
users:read Required Required Required Resolve user display names
users:read.email Required Required Required Look up user emails for user mapping
files:read Required Required Required Download file attachments

Azure AD Application Permissions (Microsoft Graph)

Permission Chat mode Channel mode Auto mode Purpose
Teamwork.Migrate.All Required Required Required Enable migration mode for timestamp backdating and user attribution
User.Read.All Required Required Required Look up Azure AD users by email for user mapping
Chat.ReadWrite.All Required Required Create Teams chats and post messages
Team.ReadBasic.All Required Required Read team information
Channel.Create Required Required Create private archive channels
ChannelMessage.ReadWrite.All Required Required Post messages to channels
Files.ReadWrite.All Required Required Upload file attachments to SharePoint

Auto mode needs all permissions from both chat and channel modes because it routes per-conversation at runtime.

Slack Setup

Step 1: Create a Slack App

  1. Go to api.slack.com/apps
  2. Click Create New AppFrom scratch
  3. Name it something like DM Migrator and select your workspace
  4. Click Create App

Step 2: Add Bot Token Scopes

  1. In the left sidebar, click OAuth & Permissions
  2. Scroll down to ScopesBot Token Scopes
  3. Click Add an OAuth Scope and add each of the following:
Scope What it does
im:read Allows the bot to list 1:1 DM conversations in the workspace
im:history Allows the bot to read messages in 1:1 DMs
mpim:read Allows the bot to list group DM conversations in the workspace
mpim:history Allows the bot to read messages in group DMs
users:read Allows the bot to look up user display names and profile info
users:read.email Allows the bot to read user email addresses (needed for matching to Azure AD accounts)
files:read Allows the bot to download shared file attachments

Step 3: Install the App

  1. Scroll to the top of the OAuth & Permissions page
  2. Click Install to Workspace (or Reinstall to Workspace if updating scopes)
  3. Review the permissions and click Allow
  4. Copy the Bot User OAuth Token — it starts with xoxb-

This token is your SLACK_TOKEN / --slack-token value.

Note on DM access: A bot token with the scopes above can read DM history for all users in the workspace. Unlike channels, the bot does not need to be a member of each DM conversation. These scopes grant workspace-wide read access to direct message data.

Azure AD / Microsoft Teams Setup

Step 1: Register an Application

  1. Go to the Azure Portal → App registrations
  2. Click New registration
  3. Fill in:
    • Name: Slack DM Migrator (or any name you prefer)
    • Supported account types: Accounts in this organizational directory only (Single tenant)
    • Redirect URI: Leave blank (not needed for client credentials flow)
  4. Click Register
  5. On the Overview page, note down:
    • Application (client) ID — this is your TEAMS_CLIENT_ID
    • Directory (tenant) ID — this is your TEAMS_TENANT_ID

Step 2: Add API Permissions

  1. In the left sidebar, click API permissions
  2. Click Add a permissionMicrosoft GraphApplication permissions
  3. Search for and add the permissions you need based on your mode:
Permission When you need it What it enables
Teamwork.Migrate.All Always Posts messages with original timestamps and user attribution via migration mode
User.Read.All Always Looks up Azure AD users by email address for the generate-user-map command
Chat.ReadWrite.All Chat mode or Auto mode Creates 1:1 and group chats in Teams and posts messages to them
Team.ReadBasic.All Channel mode or Auto mode Reads team metadata to verify the app has access
Channel.Create Channel mode or Auto mode Creates private archive channels in the target team
ChannelMessage.ReadWrite.All Channel mode or Auto mode Posts messages into Teams channels during migration
Files.ReadWrite.All Channel mode or Auto mode Uploads file attachments to the channel's SharePoint document library

Important: Make sure you select Application permissions, not Delegated permissions. Application permissions allow the tool to run unattended using client credentials (no signed-in user required).

Step 3: Grant Admin Consent

  1. After adding permissions, you'll see them listed with a status of "Not granted"
  2. Click the Grant admin consent for [Your Organization] button
  3. Confirm by clicking Yes
  4. All permissions should now show a green checkmark with status "Granted for [Your Organization]"

This step requires Global Administrator or Privileged Role Administrator privileges in your Azure AD tenant. If you don't have this role, ask your IT admin to grant consent.

Step 4: Create a Client Secret

  1. In the left sidebar, click Certificates & secrets
  2. Click New client secret
  3. Add a description (e.g., DM Migrator) and choose an expiration period
  4. Click Add
  5. Immediately copy the secret Value — it is only shown once

This is your TEAMS_CLIENT_SECRET. If you lose the value, you'll need to create a new secret.

Note: The Secret ID is not the same as the Secret Value. You need the Value (the long string), not the ID (the GUID).

Step 5: Get Your Teams Team ID (Channel Mode Only)

If using --mode channel or --mode auto, you need the Team GUID where archive channels will be created:

  1. In Microsoft Teams, navigate to the team you want to use
  2. Click the three dots () next to the team name → Get link to team
  3. The link contains the Team ID as the groupId parameter:
    https://teams.microsoft.com/l/team/...?groupId=<YOUR-TEAM-ID>&...
    
  4. Copy the groupId value — this is your TEAMS_TEAM_ID

Installation

npm install -g slack-to-teams-dm-migrator

Or clone and build from source:

git clone https://github.com/Kilowhisky/slack-to-teams-dm-migrator.git
cd slack-to-teams-dm-migrator
npm install
npm run build

Quick Start

1. Configure credentials

Copy the example environment file and fill in your credentials:

cp .env.example .env
# Edit .env with your Slack token and Azure AD credentials

Or pass credentials as CLI flags or set them as environment variables directly.

2. Generate a user map

Match Slack users to Azure AD accounts by email:

slack-to-teams-dm generate-user-map \
  --slack-token $SLACK_TOKEN \
  --teams-tenant-id $TEAMS_TENANT_ID \
  --teams-client-id $TEAMS_CLIENT_ID \
  --teams-client-secret $TEAMS_CLIENT_SECRET \
  -o user-map.json

This fetches all Slack users, looks up each email in Azure AD, and writes the matched pairs to user-map.json. Review the output — it will list any unmatched users.

3. Validate credentials

slack-to-teams-dm validate \
  --slack-token $SLACK_TOKEN \
  --teams-tenant-id $TEAMS_TENANT_ID \
  --teams-client-id $TEAMS_CLIENT_ID \
  --teams-client-secret $TEAMS_CLIENT_SECRET

4. Preview conversations

slack-to-teams-dm list-conversations --slack-token $SLACK_TOKEN --types both

5. Run the migration

slack-to-teams-dm migrate \
  --slack-token $SLACK_TOKEN \
  --teams-tenant-id $TEAMS_TENANT_ID \
  --teams-client-id $TEAMS_CLIENT_ID \
  --teams-client-secret $TEAMS_CLIENT_SECRET \
  --user-map-file user-map.json \
  --mode auto

Destination Modes

Chat Mode (--mode chat)

Creates native Teams 1:1 or group chats. Messages appear in the Teams "Chat" sidebar with original timestamps and user attribution. Uses the Graph beta API for migration mode.

Best for: organizations where most Slack users have Azure AD accounts and you want DMs to feel native in Teams.

Channel Mode (--mode channel)

Creates private archive channels in a designated Team (requires --teams-team-id). One channel per conversation, named like dm-alice-bob. Uses the stable v1.0 channel migration API.

Best for: archival purposes, or when many participants don't have Azure AD accounts.

Auto Mode (--mode auto, default)

Routes per-conversation: if both sides of a 1:1 DM (or 2+ participants of a group DM) have Azure AD mappings, uses chat mode. Otherwise falls back to channel mode (if --teams-team-id is provided).

Best for: most migrations — gets the best result for each conversation automatically.

CLI Reference

migrate (default command)

Option Description Default
--slack-token <token> Slack Bot token (xoxb-...) $SLACK_TOKEN
--teams-tenant-id <id> Azure AD tenant ID $TEAMS_TENANT_ID
--teams-client-id <id> Azure AD app client ID $TEAMS_CLIENT_ID
--teams-client-secret <secret> Azure AD app client secret $TEAMS_CLIENT_SECRET
--mode <mode> chat, channel, or auto auto
--teams-team-id <id> Teams team GUID (channel mode) $TEAMS_TEAM_ID
--conversation-ids <ids> Comma-separated Slack conversation IDs all
--conversation-types <types> im, mpim, or both both
--skip-bot-dms Skip DMs where the other participant is a bot false
--oldest <date> Earliest message date (ISO 8601 or Unix timestamp)
--latest <date> Latest message date
--user-map-file <path> Path to user mapping JSON file
--state-file <path> Path to state file ./dm-migration-state.json
--dry-run Fetch and transform without posting to Teams false
--concurrency <n> Concurrent Teams API requests (1–5) 1
--verbose Enable debug logging false

list-conversations

List Slack DM conversations with participant names.

slack-to-teams-dm list-conversations --slack-token $SLACK_TOKEN --types both
slack-to-teams-dm list-conversations --slack-token $SLACK_TOKEN --types im --json

generate-user-map

Generate a JSON mapping of Slack user IDs to Azure AD user IDs by matching email addresses.

slack-to-teams-dm generate-user-map \
  --slack-token $SLACK_TOKEN \
  --teams-tenant-id $TEAMS_TENANT_ID \
  --teams-client-id $TEAMS_CLIENT_ID \
  --teams-client-secret $TEAMS_CLIENT_SECRET \
  -o user-map.json

You can also create or edit the mapping file manually:

{
  "U012AB3CD": "aad-user-guid-for-alice",
  "U098ZYX": "aad-user-guid-for-bob"
}

validate

Preflight check: test Slack and Teams credentials and verify API access for your chosen mode.

slack-to-teams-dm validate \
  --slack-token $SLACK_TOKEN \
  --teams-tenant-id $TEAMS_TENANT_ID \
  --teams-client-id $TEAMS_CLIENT_ID \
  --teams-client-secret $TEAMS_CLIENT_SECRET \
  --mode auto

status

Show migration progress from the state file. No API credentials needed.

slack-to-teams-dm status
slack-to-teams-dm status --state-file ./my-state.json --json

Environment Variables

All required options can be set via environment variables. Copy .env.example to .env:

cp .env.example .env
Variable CLI Flag Description
SLACK_TOKEN --slack-token Slack Bot token (xoxb-...)
TEAMS_TENANT_ID --teams-tenant-id Azure AD directory (tenant) ID
TEAMS_CLIENT_ID --teams-client-id Azure AD application (client) ID
TEAMS_CLIENT_SECRET --teams-client-secret Azure AD client secret value
TEAMS_TEAM_ID --teams-team-id Teams team GUID (channel mode only)

How It Works

The migration runs in 4 phases:

  1. Initialize — Authenticate with Slack and Teams APIs, load or create the state file, load the user mapping
  2. Discover — List DM conversations from Slack, resolve participant names and Azure AD mappings, filter by CLI options
  3. Migrate — For each conversation: fetch messages, determine destination mode, create the Teams chat or channel, enter migration mode, post messages with original timestamps, upload files, complete migration mode
  4. Report — Print per-conversation and overall summary of migrated, skipped, and failed messages

Unmapped Users

When a Slack user cannot be matched to an Azure AD account, their messages are still migrated. The message is posted by the application identity with the user's display name prepended to the body:

[Alice Smith] Hey, here's that document you asked about

This ensures no messages are ever skipped due to missing user mappings.

Resumability

The state file tracks progress per-conversation and per-message. If the process is interrupted, simply re-run the same command. Completed conversations and individual messages are skipped automatically — no duplicates are created.

Chat Migration Mode (Beta API)

Chat mode uses the Microsoft Graph beta API endpoints startMigration and completeMigration on chats. While in migration mode:

  • Messages can be posted with custom createdDateTime (original Slack timestamps)
  • Messages can be attributed to specific users via the from field
  • The chat is locked — participants cannot send new messages until migration completes

Limitations

  • Beta API: Chat mode relies on Graph beta API endpoints (/beta/chats/{id}/startMigration). These may change without notice. The beta calls are isolated to a single module (src/teams/chat-migration.ts) for easy updating.
  • Reactions: Rendered as text in the message body (e.g., "Reactions: 👍 5, ❤️ 2"). The Teams migration API does not support programmatic reaction import.
  • Thread flattening: Thread replies are flattened into the main timeline chronologically. The Teams chat migration API does not support threaded replies in chats.
  • @mentions: Show as bold display names (e.g., @Alice) but are not clickable Teams mentions.
  • Block Kit messages: Complex Slack Block Kit layouts use the plain text fallback.
  • Slack-specific features: Workflows, Canvas documents, Huddle recordings, and Slack Connect messages are not migrated.
  • Message edits: Only the latest version of each message is migrated.
  • Self-DMs: Skipped with a warning (Teams has no self-chat equivalent).

Troubleshooting

"Forbidden" or 403 errors from Microsoft Graph

  • Verify you added Application permissions (not Delegated) in the Azure AD app registration
  • Ensure you clicked Grant admin consent — all permissions should show a green checkmark in the portal
  • Double-check that Teamwork.Migrate.All is granted — this is the most commonly missed permission
  • If using channel mode, verify Files.ReadWrite.All is also granted

"Insufficient privileges" when granting admin consent

You need Global Administrator or Privileged Role Administrator role in Azure AD. Ask your IT admin to grant consent on the app registration, or have them run the validate command to confirm everything is set up correctly.

Chat or channel stuck in migration mode

If the process crashes while a chat/channel is in migration mode, simply re-run the migration command. The state file tracks which conversations have active migration and will resume from where it left off, eventually calling completeMigration to unlock the conversation.

"Channel not found" or team access errors

  • Verify the --teams-team-id value is the correct GUID (not the team name or display name)
  • Ensure the Azure AD app has Team.ReadBasic.All permission with admin consent granted

Rate limiting

The tool handles rate limits automatically with Bottleneck:

  • Slack: ~50 requests per 60 seconds (tier 3)
  • Teams: ~5 requests per second

Large DM histories will take time. The --concurrency flag (1–5) controls parallel Teams API requests, but stay conservative to avoid throttling.

Missing user mappings

If generate-user-map can't match a Slack user to Azure AD:

  • The Slack user may not have an email address set in their profile
  • The email may not match any Azure AD account (e.g., different email domains between Slack and Microsoft 365)
  • You can manually add entries to the user map JSON file

Messages from unmapped users are always migrated with bot attribution — they are never skipped.

Contributing

Contributions are welcome! Please open an issue or pull request.

# Development
npm install
npm run dev -- --help

# Run tests
npm test

# Build
npm run build

License

MIT

About

Migrate direct messages (1:1 and group DMs) from Slack to Microsoft Teams with original timestamps, user attribution, and file attachments.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors