Skip to main content

Slack Migration Guide

Overview​

Mattermost provides a reliable migration path from Slack, enabling you to bring your organization’s collaboration history into a secure, self-hosted Mattermost environment. The migration process supports full workspaces, including users, channels and message history, direct messages, and threads so your teams can continue working without losing valuable context.

This process involves preparing your environment, exporting data from Slack, converting that data into a compatible format, and then importing it into Mattermost.

  1. Understand Slack to Mattermost feature mapping:
    • Review how workspaces, channels, conversations, posts, users, and integrations migrate.
  2. Prepare your Mattermost environment:
    • Scope history, export size, and attachments.
    • Confirm infrastructure and Mattermost server settings.
    • Back up your Mattermost environment before importing.
  3. Export data from Slack:
    • Generate an export using Slack’s tools and documentation.
    • Identify whether you have a single-workspace or Enterprise Grid export.
  4. Transform the Slack export with mmetl:
    • For Enterprise Grid, split the export with grid-transform first.
    • Validate the export, then run transform slack --dry-run before the full transform.
    • The full transform produces a JSONL file and a data/ attachments directory.
    • For multiple exports, transform each archive separately and import them chronologically.
  5. Package and import the data into Mattermost:
    • Package the transformed data using the bulk loading documentation.
    • Upload and process the archive with mmctl.
  6. Validate the migration:
    • Confirm channels, users, and message history imported correctly.
    • Fix unread channels and threads if needed, then activate imported accounts before the production cutover.

Slack’s data control policies and export capabilities can change at any time, and Slack may charge fees for exporting data. Talk to an expert for support negotiating export of your organization’s data from Slack Enterprise, and to run the migration with your team.

1. Understand Slack to Mattermost feature mapping​

The tables below show what comes with you from Slack and what you set up again in Mattermost.

Workspaces and Channels​

Content typeMigrates?Notes
Enterprise Grid WorkspacesYesMap to Mattermost teams. See Enterprise Grid exports
Public / private channelsYesArchived Slack channels import as archived when timestamps are available
1:1 DMsYesMap directly to Mattermost direct messages
Group DMsYesUp to 8 members become Mattermost Group Messages; larger groups become private channels
Slack ConnectNoNot migrated as Connect, but channels can be mapped to a single Mattermost team. For Mattermost interoperability afterward, see Connected workspaces
Starred conversationsNoMattermost allows users to Save messages

Posts and content​

Content typeMigrates?Notes
Posts and threadsYesMessage history and thread replies come with you
File attachmentsYesFiles attached to messages come with you. Posts with many files may be split across replies
ReactionsYesEmoji reactions come with the post. Custom emoji images must be readded separately. See Custom emoji
Channel mentionsYesBecome Mattermost channel mentions when the channel is included in the export
Links to Slack postsYesOriginal Slack URLs are kept and will work while Slack remains accessible
External linksYesRemain unchanged in post content
Standard emojiYesSkin-tone variants are not kept
Custom emojiNoAdd them in Mattermost with the same names. See Custom emoji
CanvasesNoDownload any you need from the links in canvases.json before you leave Slack

Users and bots​

Content typeMigrates?Notes
UsersYesName, email, and title come with the user. See User emails and Guest users
Deactivated or deleted usersYesImported as deactivated Mattermost users, including their channel memberships
BotsYesMust be owned by a Mattermost user via --bot-owner flag. See Useful transform flags
User groupsNoRecreate with Custom Groups
Profile picturesNoUsers add their own picture. See Manage your profile
PresenceNoUsers set status and availability in Mattermost
Custom profile fieldsNoRecreate them as user attributes

Integrations​

Integrations are not included in the Slack export file, so Mattermost has no way to migrate them. Recreate each one in Mattermost using the mapping below. Incoming webhook payloads and custom slash command requests that already use Slack’s format can be reused; point them at the new Mattermost URL.

Slack integrationMigrates?Mattermost mappingWhat to recreate
Slack Marketplace appsNoPre-built plugins, such as Jira, GitHub, GitLab, and ZoomInstall the matching plugin and set up channels, credentials, and notifications again. Use a webhook, slash command, or custom plugin when Mattermost has no equivalent
Custom appsNoPlugins and the REST APIRebuild the app, including its tokens and where it is installed
Incoming webhooksNoIncoming webhooks. Payloads are Slack-compatibleEach webhook URL and the channel it posts to
Outgoing webhooksNoOutgoing webhooksTrigger words, channels, and callback URLs
Slash commandsNoBuilt-in and custom slash commands. Custom command requests are Slack-compatibleEach custom command name, request URL, and channel scope. Use the built-in command when Mattermost already provides one
Bot usersNoBot accounts migrate. See Users and bots. Behavior is a bot account with the REST API or a pluginWhat the bot does. Assign an owner with --bot-owner
Workflow BuilderNoPlaybooks for in-channel processes, and no-code automation (n8n, Zapier, Make) for multi-app workflowsTriggers, steps, forms, and destinations
Block KitNoMattermost Blocks and interactive dialogsButtons, menus, and forms. Posted message text still imports with the post
Events APINoOutgoing webhooks for keyword triggers; plugins or the REST API for other eventsThe subscriptions and the service that receives them

2. Prepare your Mattermost environment​

This guide assumes you already have a Mattermost Server ready to accept data. If not, use the deployment documentation together with the guidance below to make informed decisions about your database and file storage infrastructure.

Scope the migration​

  • Slack edition: Enterprise Grid exports need extra mapping steps. See Enterprise Grid.
  • Data history: Prefer a smaller time window for the first import (for example, the last six months). Expand after a successful trial.
  • Export size: Files under ~25 GB often complete within a day; exports over ~100 GB can take several days per iteration.
  • Attachments: Exclude very large or non-critical files (installers, videos, outdated assets) when you can, to speed iteration.
  • Phased imports: Multiple smaller exports (for example, six dumps of six months each) are supported and recommended. See Handle multiple exports.

Infrastructure​

  • Test environment: Always migrate to development or staging first. Most migrations need several iterations.
  • Operating system: mmetl is supported on Linux and macOS. Windows is not supported. Avoid WSL; filesystem performance is usually too slow for large transforms.
  • Storage: Plan for at least three times the Slack export size for the archive, unpacked data, and Mattermost import package.
  • File store: Imports into S3-compatible storage typically complete faster than local disk or NFS. Prefer S3 for large imports.

Mattermost server settings​

3. Export your data from Slack​

Mattermost does not export data from Slack for you. Use Slack’s own export tools and documentation:

Slack offers two common export types:

  1. Public channels export: Public channel messages and file links. Available on all Slack plans.
  2. All channels and conversations export: Public channels, private channels, DMs, and group messages. Available on Business+ (application required) and Enterprise Grid.

Slack Free plan exports include messages and file links from public channels, but only file links from the last 90 days are included. Slack can change its export format without notice, so use the latest mmetl release before a large migration. If a transform fails with unexpected parse errors, check the changelog or file an issue.

Single-workspace exports​

A typical single-workspace zip includes:

  • channels.json, users.json
  • dms.json, groups.json, mpims.json (all-channels export)
  • integration_logs.json
  • Per-channel folders of daily post JSON files

Typical layout (all-channels export):

Slack Export/
├── general/
│ ├── 2024-01-15.json
│ └── 2024-01-16.json
├── project-alpha/
│ ├── 2024-02-01.json
│ └── 2024-02-02.json
├── private-leadership/
│ └── 2024-03-10.json
├── channels.json
├── users.json
├── groups.json
├── dms.json
├── mpims.json
└── integration_logs.json

Public and private channel names appear as top-level folders of daily post files. A public-channels-only export omits groups.json, dms.json, mpims.json, and private-channel folders.

Use mmetl transform slack on this archive (see Transform).

Enterprise Grid exports​

Slack Enterprise Grid combines multiple workspaces in one management plane. The export is a single archive with org-level and per-workspace data.

How data maps to Mattermost

  • Slack workspaces roughly correspond to Mattermost teams.
  • Channels in Grid belong to individual workspaces. Shared channels across workspaces must be mapped to a single Mattermost team. Mattermost does not support shared channels between teams the way Slack does.
  • For most customers, importing into one Mattermost team is simplest. Use multiple teams only when you intentionally want workspace separation.
  • Org-wide DMs that span workspaces are assigned to a single team, and cross-workspace participants import as placeholder accounts; see What happens to org-wide DMs in an Enterprise Grid export?.

Typical Grid layout

Enterprise Grid Export/
├── channel1/
│ ├── 2023-01-01.json
│ └── 2023-01-02.json
├── channel2/
│ ├── 2023-05-01.json
│ └── 2023-05-02.json
├── teams/
│ ├── team1/
│ │ ├── channel3/
│ │ ├── channels.json
│ │ ├── mpims.json
│ │ ├── dms.json
│ │ ├── users.json
│ │ └── groups.json
│ └── team2/
│ ├── channel4/
│ ├── channels.json
│ ├── mpims.json
│ ├── dms.json
│ ├── users.json
│ └── groups.json
├── channels.json
├── org_users.json
├── mpims.json
├── dms.json
└── groups.json

Shared channels often appear at the root; per-workspace data lives under /teams/. Grid migrations usually take more time because of manual team mapping. See Transform an Enterprise Grid export.

4. Transform the export with mmetl​

mmetl converts a Slack export into Mattermost’s bulk import format: a JSONL file plus a data/ directory of attachments.

  1. Download the latest mmetl release for your OS and architecture.
  2. Run mmetl help (or ./mmetl help) to explore commands.

4.1 Split an Enterprise Grid export (optional)​

Skip this subsection unless you have a Slack Enterprise Grid export. Grid archives must be split into per-workspace exports with mmetl grid-transform before you run mmetl transform slack on each team.

  1. Download the full Grid export from Slack.
  2. Split it into per-team exports:
./mmetl grid-transform -f slackexport.zip

grid-transform infers each Slack workspace ID from the teams/<name>/ folders already in the archive and prints the mapping. Shared channels at the archive root are moved into the workspace named on the first post that has a team field.

  1. Validate each resulting archive with transform slack --dry-run, then transform each one using --team for its Mattermost team (that flag is independent of Slack's folder names).

If inference fails, pass --team-map-path teams.json:

{
"T0001": "acme",
"T0002": "widgets-inc"
}
  • Key: Slack workspace ID as it appears in a message's team field (typically T...).
  • Value: The existing teams/<name>/ folder in the export. Must match Slack's folder name exactly. This is the output zip name, not the Mattermost --team value.

4.2 Validate the Slack export​

For a single-workspace export, use the original zip. For Enterprise Grid, use each per-workspace zip produced by grid-transform. Set --team to the destination Mattermost team and use the same transform flags you plan to use for the full run (including --bot-owner if the export contains bots):

./mmetl transform slack \
--team my-team \
--file slack_export.zip \
--output mattermost_import.jsonl \
--bot-owner admin \
--dry-run

Dry-run parses and transforms the export without writing JSONL or copying attachments. It logs warnings and errors to the terminal and exits non-zero if it finds problems, including missing attachments that a full transform would skip. Fix reported issues before the full run. Use --skip-attachments only if you intend to skip attachment checks and copying in the full run.

4.3 Run the transform​

Use a small export (or --skip-attachments) for the first run so you can learn the flow quickly.

  1. Confirm the destination team already exists in Mattermost. The team name must be lowercase and hyphenated (for example, team display name My Team to my-team). Allow any user with an account on this server to join this team must be enabled.

  2. Transform:

./mmetl transform slack \
--team my-team \
--file slack_export.zip \
--output mattermost_import.jsonl \
--bot-owner admin

If any Slack user is missing an email, transform fails unless you pass --default-email-domain or --skip-empty-emails. Prefer --default-email-domain with your organization domain. See User emails.

  1. Expected outputs in the working directory:
  • mattermost_import.jsonl: users, channels, posts, and related records
  • data/ (or your --attachments-dir): attachment files under bulk-export-attachments/
  • transform-slack.log: INFO-level progress (use --debug for more detail). See Appendix B for what each phase means.
  1. Spot-check success:
  • The command exits 0.
  • The JSONL starts with a version line and contains user, channel / direct_channel, and post / direct_post lines.
  • Attachment paths referenced in the JSONL exist under data/.
  • The log shows the parse phases completing without fatal errors.
  • If you used an email flag, search the JSONL for the synthesized domain (or blank emails if you used --skip-empty-emails) and follow User emails before packaging.
  1. Signs of a failed or partial run:
  • Non-zero exit or panic during parse/transform.
  • Warnings about unsupported message subtypes (some posts skipped; review the log).
  • Missing emails with neither email flag set (transform exits naming the user).
  • Guest-related skip warnings if channelless guests were dropped under default --guest-handling=guest.

Recommended incremental approach

  1. Transform and import one channel’s worth of history (or a short time window) with --skip-attachments.
  2. Validate in Mattermost.
  3. Expand to a full workspace export, still without attachments if needed.
  4. Re-run with attachments for the final package.

Bulk import into Mattermost is idempotent: re-importing the same posts does not create duplicates. That makes iterative testing safe. Weekly export/transform/import cycles during a phased cutover are a supported pattern.

4.4 Handle multiple exports​

Customers often receive several Slack dumps (for example, six archives covering six months each) or run weekly exports during cutover.

Recommendations

  • Transform each archive separately into its own .jsonl + data/ tree (or merge carefully only if you understand the bulk format).
  • Import chronologically when practical (newest first).
  • Rely on Mattermost bulk import idempotency: overlapping posts with the same author and create_at are not duplicated.
  • Keep team names and --guest-handling / --bot-owner choices consistent across batches.
  • For very large histories, prefer several medium imports over one multi-hundred-GB upload (see Package and import).

Useful transform flags​

  • --skip-attachments / -a: Skip copying attachments (faster trials).
  • --allow-download / -l: Allow downloading attachments from URLs when needed.
  • --attachments-dir / -d: Attachments output directory (default data).
  • --default-email-domain <DOMAIN>: Synthesize missing emails as username@DOMAIN. See User emails.
  • --skip-empty-emails: Keep users with empty emails (produces invalid import data you must fix manually). See User emails.
  • --discard-invalid-props / -p: Drop posts whose props exceed Mattermost limits.
  • --guest-handling=guest|user|skip: See Guest users.
  • --bot-owner <MATTERMOST_USERNAME>: Required when the export contains bot users.
  • --debug: Verbose logging in transform-slack.log. See Appendix B for how to read the phases in that log.

5. Package and import into Mattermost​

When mmetl finishes, you should have:

  • A .jsonl bulk import file
  • A data/ directory of attachments

5.1 Review the import data​

In some cases, depending on the transform configuration, you may need to review or edit the JSONL before you zip and upload. For missing or synthesized emails (--default-email-domain, --skip-empty-emails, or @local placeholders), see User emails.

5.2 Package the import​

Zip them together:

zip -r mattermost-bulk-import.zip data mattermost_import.jsonl

Validate the export locally:

mmctl import validate ./mattermost-bulk-import.zip

Then follow the Mattermost Bulk loading data guide to import the zip with mmctl. That guide covers authentication, job status, and the full import data format.

5.3 Choose an upload method​

SituationRecommended method
Smaller packages (rough guide: under ~25 GB)mmctl import upload, then mmctl import process (see bulk loading)
Large packages (multi-GB / multi-year history, tens of thousands of channels)Place the zip on the Mattermost server and process in place; avoid uploading through the API

Large imports (file store / local process)

For large datasets, copying the file onto the server is usually more reliable than mmctl import upload:

  1. Copy mattermost-bulk-import.zip to the server (for example into the Mattermost data/import directory, or another path readable by the server process).
  2. On the server, in mmctl local mode, process the file directly:
mmctl import process --bypass-upload ./mattermost-bulk-import.zip --local

Notes:

  • --bypass-upload works only with --local.
  • It is not supported when the server is in High Availability; disable HA for the import window or use the standard upload path.
  • Imports into S3-backed file storage typically outperform local/NFS disk for large jobs.

6. Validate the migration​

After each import, confirm the job completed and compare representative data in Mattermost with the Slack export. Account for the gaps described in feature mapping before the production cutover.

6.1 Check the import job​

Use mmctl import job list and mmctl import job show <JOB_ID> --json to confirm the job completed and review any errors. See the bulk loading and mmctl docs for job details.

6.2 Verify migrated data​

Spot-check the imported data against the Slack export:

  • Confirm the expected users, bots, guests, public and private channels, and archived channels are present.
  • Open sample 1:1 DMs, Group Messages, and oversized multi-party DMs converted to private channels. For Enterprise Grid, also check org-wide DMs and placeholder users.
  • Compare sample posts, thread replies, reactions, and file attachments. Check the transform logs for skipped content, and confirm custom emoji reactions display after matching emoji are added.
  • Test account access with an imported user after activation.

6.3 Fix unread channels and threads if needed​

If imported messages appear unread for users, back up the database and run the following SQL on PostgreSQL:

begin;
UPDATE channelmembers
SET
msgcount = channels.totalmsgcount,
lastupdateat = channels.lastpostat,
lastviewedat = channels.lastpostat,
msgcountroot = channels.totalmsgcountroot
FROM channels
WHERE channelmembers.channelid = channels.id;

INSERT INTO preferences (UserId, Category, Name, Value)
SELECT
cm.userid,
'channel_approximate_view_time',
cm.channelid,
cm.lastupdateat
FROM
channelmembers cm
ON CONFLICT (userid, category, name)
DO UPDATE SET
Value = EXCLUDED.Value;

update preferences
set value = false
where category = 'direct_channel_show';

update preferences
set value = false
where category = 'group_channel_show';

commit;

6.4 Activate imported accounts​

Bulk import (the mmetl + mmctl import path in this guide) always marks imported user emails as verified. Users do not need a separate email confirmation step.

Imported users receive a generated password they do not know. They activate their accounts by using Password Reset on the Mattermost login page with the email address from the import (normally their Slack email).

To move authentication to LDAP or SAML after import, see mmctl user migrate-auth.

FAQ​

Can I import in batches, or must I import everything at once?​

Batches are fine and recommended. Transform and import incrementally; Mattermost bulk import is idempotent. See Handle multiple exports.

Do I need to create users before running the import?​

No. Users (and bots, with --bot-owner) are created from the import file. Create the destination team beforehand.

Do I need to import custom emoji before messages?​

No. Reactions are stored by emoji name and can import before the custom emoji exists. Add matching custom emoji whenever convenient; once the name exists in Mattermost, those reactions render. See Custom emoji.

What happens if I run the same import twice?​

Duplicate posts are not created. Matching records are skipped or updated per bulk import rules. Safe for retries and phased cutovers.

What should I do with Slack Connect channels?​

There is no Slack Connect migration path: mmetl does not map external org membership or Connect semantics. If a Connect channel is present in the export, it may still transform as a normal public or private channel with only members from your workspace. For ongoing cross-organization collaboration in Mattermost, use Connected workspaces.

How do I handle a very large import (multiple GB)?​

Package with mmetl, copy the zip to the Mattermost server, and use mmctl import process --bypass-upload --local. See Choose an upload method and Bulk loading data.

Will Slack URLs embedded in messages still work?​

Channel mentions are translated to Mattermost ~channel form when possible. Links to specific Slack posts remain Slack URLs and work while Slack is accessible.

What happens to org-wide DMs in an Enterprise Grid export?​

mmetl grid-transform has no special handling for org-wide conversations. Root-level dms.json and mpims.json entries follow the same path as every other conversation: grid-transform reads the conversation's post files and assigns the whole conversation to the workspace named on the first post that carries a team field. A DM that spans two workspaces therefore lands in exactly one Mattermost team — it is never split across teams or duplicated into both. Two consequences to plan for:

  • Participants from the other workspace become placeholder accounts. grid-transform does not merge user lists between workspaces, so the participant who is not a member of the assigned workspace is missing from that team's users.json. mmetl transform slack then creates a deactivated placeholder user named after the Slack user ID, with a <USERID>@local email address. Correct those addresses before you import; see User emails.
  • Unassignable conversations are dropped. If a conversation's posts carry no team field, or the workspace ID is not in the team mapping, grid-transform logs an error to grid-transform-slack.log and skips it. Skipped conversations are not written to any per-workspace zip, so review that log before you continue rather than relying on the exit code.

If preserving cross-workspace DM history matters, decide in advance which team should own those conversations, and verify a sample of them in your trial import.

How are Slack guests imported?​

By default (--guest-handling=guest), Slack guests are exported as Mattermost guests when Guest Accounts are enabled and licensed. Guests with no public/private channel membership are skipped; use --guest-handling=user to import those as regular members. See Guest users.

What if mmetl suddenly fails to parse an export that used to work?​

Slack may have changed the export format. Update mmetl from GitHub Releases, re-run mmetl transform slack --dry-run with the same flags you plan to use for the full transform, and file an issue if a new format break is confirmed.

What additional considerations are there for Slack Enterprise Grid?​

Use mmetl grid-transform to split the archive, then transform each workspace archive separately. grid-transform infers workspace IDs from the teams/<name>/ folders; pass --team-map-path teams.json only if inference fails. Shared channels must land in a single Mattermost team. See Enterprise Grid exports.

Are there features of Slack that are not supported?​

See Slack to Mattermost feature mapping. Core collaboration data can be migrated subject to those tables; apps, workflows, and similar integrations must be rebuilt using Mattermost integrations.

Appendix A: Custom emoji, guests, and user emails​

Custom emoji, guest accounts, and user emails each need a decision during the migration. The Slack export does not bring them across in a form Mattermost can use as-is. Message history can still import while you settle these.

  • Custom emoji. Reactions on imported posts keep the emoji name. Add the image in Mattermost for the reaction to display.
  • Guest users. Decide whether Slack guests become Mattermost guests, regular members, or are left out of the import.
  • User emails. Every imported user needs an email address. Users missing one in the Slack export need an address before import.

Custom emoji​

Reactions import with the post, stored by emoji name. Custom emoji images have to be added in Mattermost.

  • Standard emoji such as thumbsup and heart display with no extra setup.
  • Add each custom emoji with the same name, in lowercase. Historical reactions display as soon as that name exists. Add them before or after the message import in the custom emoji UI, or as emoji objects in the bulk import. Turn on custom emoji in the System Console if needed.
  • Skin-tone variants are dropped. +1::skin-tone-3 becomes +1.

Guest users​

Set --guest-handling on mmetl transform slack to choose what happens to Slack guests:

ModeWhat happens
guest (default)They become Mattermost guests, limited to the public and private channels they were in. Guest Accounts must be enabled and licensed.
userThey become regular members.
skipThey are left out, along with their channel memberships, posts, and reactions.

A guest who was only in direct or group messages cannot be a Mattermost guest. That account is skipped unless you use --guest-handling=user.

User emails​

Every imported user needs an email address. If the Slack export is missing one, the transform stops and names that user unless you choose a flag:

ChoiceWhat to do
--default-email-domain example.comMissing emails become username@example.com. Use your organization domain. Correct any address that should be different before import.
--skip-empty-emailsEmails stay blank. Set a real address on each affected user before import, or import validation fails.
No flagThe transform stops. Fix the export or re-run with a flag.

Enterprise Grid imports can also create placeholder users with a <userid>@local address. Replace those with real addresses before import.

Search the import file for your domain or @local, correct the addresses, and run mmctl import validate.

Appendix B: mmetl parsing phases​

Large transforms move through four phases. Watch transform-slack.log (and console output) to gauge progress. Per-channel progress for phases 2-4 is logged at DEBUG level; run with --debug to see those lines. Phase completion messages and file-reading progress use INFO.

  1. Reading the import file: Often 5-10 minutes depending on archive size.

    Example:

    {"file":"parse.go:226","level":"info","msg":"Processing file 1 of 10335: aluminum-white-lightbulb/","time":"2024-03-11T20:41:09-04:00"}
  2. Converting user mentions: Can take hours on large exports.

    Example (requires --debug):

    {"file":"parse.go:109","level":"debug","msg":"Slack Import: converting user mentions for channel touchscreen-headphones-sleek. 1 of 400","time":"2024-03-11T20:41:10-04:00"}

    When the phase finishes (INFO):

    {"level":"info","msg":"Slack Import: Converted user mentions"}
  3. Converting channel mentions: Typically about half as long as user mentions.

    Example (requires --debug):

    {"file":"parse.go:132","level":"debug","msg":"Slack Import: converting channel mentions for channel robust-smart-home-device-matrix. 95 of 400","time":"2024-03-11T20:41:48-04:00"}

    When the phase finishes (INFO):

    {"level":"info","msg":"Slack Import: Converted channel mentions"}
  4. Converting post markup: Slack formatting to Mattermost Markdown; usually the fastest phase.

    Example (requires --debug):

    {"file":"parse.go:196","level":"debug","msg":"Slack Import: converting markdown for channel vertex-robust-vacuum. 120 of 400","time":"2024-03-11T20:41:58-04:00"}

    When the phase finishes (INFO):

    {"level":"info","msg":"Slack Import: Converted markdown"}