Migrate from Slack

Overview

Mattermost provides a migration path from Slack so you can bring collaboration history into a self-hosted Mattermost environment. The recommended flow is:

  1. Export data from Slack (using Slack’s tools and documentation).

  2. Understand which export format you have (single workspace vs Enterprise Grid).

  3. Review what migrates and what does not.

  4. Transform the Slack export into a Mattermost bulk import package with mmetl.

  5. Import that package into Mattermost using the bulk loading documentation.

This is a best effort migration. Mattermost tools preserve the majority of messages, files, and workspace structure, but Slack and Mattermost differ architecturally, and manual adjustments are often required. Plan for days to weeks depending on export size and complexity. Always run a trial migration in a development or staging environment first.

Slack’s data control policies and export capabilities can change at any time, and Slack may charge fees for exporting data. Support for negotiating export of customer IP from Slack Enterprise is available by contacting a Mattermost Expert.

Note

Consider talking to a Mattermost expert if your organization needs support migrating from Slack to Mattermost.

Migration at a glance

Step

What you do

Outcome

1. Prepare

Scope the migration and tune Mattermost

Server ready for import

2. Export from Slack

Generate a Slack export zip

Raw Slack archive

3. Understand the format

Identify single-workspace vs Enterprise Grid

Correct transform path

4. Review coverage

Know gaps (emoji, guests, emails, MPIMs, integrations)

Realistic expectations

5. Transform with mmetl

mmetl check to mmetl transform (or grid-transform)

.jsonl + data/ attachments

6. Multiple dumps

Process phased or split exports safely

Combined history without duplicates

7. Import into Mattermost

Zip the package; follow bulk loading docs

Live Mattermost data

1. 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.

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

2. 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.

Workspaces on the Slack Free plan can only export file links from the last 90 days.

Important

Avoid unzipping and rezipping the Slack export. That can change the archive layout and break the transformation process.

Note

Slack periodically changes its export format without notice. Before a large migration, download the latest mmetl release. If mmetl check or mmetl transform fails with unexpected parse errors after a Slack-side change, check the mmetl changelog or file an issue; it may be a format change rather than a mistake in your process.

3. Understand your Slack export format

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.

  • Multi-party DMs need special attention; see Direct messages and multi-party DMs. 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?.

  • Slack Connect channels are not migrated as Connect. mmetl has no Connect-specific handling; if a Connect channel appears in the export it may transform as an ordinary channel with only your workspace’s members, without external org participants. For cross-organization collaboration in Mattermost after migration, see Connected workspaces. See also What migrates.

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. What migrates / What doesn’t

The Slack path focuses on core collaboration data: posts, threads, reactions, file attachments, users, and channels. Bot users and bot messages also migrate when you configure --bot-owner. Slash commands, workflows, webhooks, and other integrations do not migrate and must be rebuilt in Mattermost.

For basic content (posts, reactions, threads, attachments, and users), expect better than about 70% fidelity (often higher), excluding integrations.

Content type

Migrates?

Notes

Posts and threads

Yes

Most message types and thread replies migrate; some unsupported Slack subtypes are skipped, and some thread relationships may not preserve

File attachments

Yes

Included unless you pass --skip-attachments. Oversized messages split into replies; more than five attachments on a post overflow into additional replies

Reactions

Yes

Stored by emoji name. Custom emoji images are not imported automatically (see Custom emoji)

Users

Yes

Created by the import. Missing emails: see User emails. Guests: see Guest users

Deactivated or deleted Slack users

Yes

Imported as deactivated Mattermost users (DeleteAt set), including public/private channel memberships

Public / private channels

Yes

Archived Slack channels import as archived when timestamps are available

Multi-party DMs

Yes (converted)

Become Mattermost Group Messages (see Direct messages and multi-party DMs)

1:1 DMs

Yes

Map directly to Mattermost direct messages

Bots

Yes

Requires --bot-owner <MATTERMOST_USERNAME> on mmetl transform slack

Channel mentions (embedded)

Best effort

Slack <#CHANNEL_ID> mentions become Mattermost ~channel mentions when the channel is in the export

Links to specific posts

Preserved as-is

Original Slack URLs kept; they work while Slack remains accessible

External links

Preserved as-is

Slash commands / workflows / webhooks

No

Rebuild with Mattermost integrations

Slack Connect

No

Not migrated as Connect. mmetl does not detect or skip these channels; if present in the export they may import as ordinary channels without external org members. For Mattermost interoperability afterward, see Connected workspaces

Custom emoji images

No (separate step)

mmetl does not import emoji images; recreate or bulk-import with matching names (see Custom emoji)

Emoji skin tone modifiers

No

Stripped (for example +1::skin-tone-3 to +1)

Starred conversations

No

User groups

No

Recreate with Custom Groups

Canvases

No

Presence, profile pictures, custom profile fields

No

Name, email, and title/position migrate; images and presence do not

Direct messages and multi-party DMs

Mattermost does not use Slack’s “multi-party DM” terminology.

  • 1:1 DMs map to Mattermost direct messages and behave the same way.

  • Multi-party DMs (MPIMs) become Group Messages: a conversation scoped to those members. Functionally equivalent for most teams; the product label differs.

  • MPIMs larger than Mattermost’s group-channel limit (8 members) are converted to private channels so the history can still import.

Custom emoji

mmetl does not import Slack custom emoji images or definitions. Reaction names are still attached to posts in the bulk import file, so those reactions are present on the imported posts but stay invisible until matching custom emoji are created on the Mattermost server.

  • Standard/system emoji reactions (for example thumbsup, heart) work without extra setup.

  • Custom emoji reactions are stored by name only (for example shipit, without surrounding colons). Bulk import saves those reactions even if the custom emoji does not exist yet.

  • Until a custom emoji with that name exists in Mattermost, the reaction does not render in the UI (no image is shown). After you add a matching custom emoji, historical reactions appear automatically because the client resolves emoji by name at display time.

  • Custom emoji names in Mattermost are lowercase (alphanumeric, _, -, +). Recreate Slack custom emoji using the same base name.

  • Skin-tone and similar modifiers are not preserved. mmetl strips Slack names at :: (for example +1::skin-tone-3 becomes +1).

  • Add custom emoji after (or before) message import via the custom emoji UI, or include emoji objects in a bulk import package (emoji lines must appear after the version line and before team lines). Enable custom emoji in System Console if needed: Enable custom emoji.

Guest users

Slack marks guests with is_restricted (multi-channel guest) or is_ultra_restricted (single-channel guest; Slack also sets is_restricted on those accounts). Control how mmetl migrates them with mmetl transform slack --guest-handling:

Mode

Behavior

guest (default; or omit the flag)

Export them as Mattermost guests (system_guest / team_guest / channel_guest). For correct guest behavior, the destination server needs Guest Accounts enabled and licensed (see guest accounts). Without that, the import can still succeed, but the accounts will not behave as guests; use user mode instead.

user

Export them as regular Mattermost users (full member permissions).

skip

Drop guest users entirely, along with their memberships, authored posts, and reactions.

Additional behavior in guest mode:

  • Guests are added only to the public and private channels they belonged to in the Slack export. Mattermost scopes guest access through those memberships; guests cannot freely discover or join other channels, so Slack’s channel-scoped access is effectively preserved.

  • A guest with no public or private channel membership (for example, present only in a DM or MPIM) cannot be imported as a Mattermost guest. mmetl skips that user and drops their memberships and authored posts/reactions, and logs a warning. Use --guest-handling=user to import those accounts as regular members instead.

  • On later bulk imports, Mattermost matches existing users by username, so the same guest is updated rather than duplicated; additional channel memberships from a later batch can be applied on re-import.

User emails

If a Slack user has no email in the export, mmetl transform slack does not invent an address by default. Without one of the flags below, transform exits with an error naming that user.

Transform choice

JSONL edit needed?

Neither flag (transform fails)

No: fix the Slack export or re-run with a flag

--default-email-domain <DOMAIN>

Optional: only if the synthesized username@DOMAIN addresses are wrong or temporary

--skip-empty-emails

Required: blank emails fail Mattermost import validation; set a real email on each affected user line

Placeholder users with @local (user ID missing from users.json)

Recommended: replace <userid>@local with real addresses before import

Flags

  • --default-email-domain <DOMAIN>: sets missing emails to username@<DOMAIN> (for example --default-email-domain example.com yields jane@example.com). Prefer your organization domain when possible.

  • --skip-empty-emails: leaves the email blank and keeps the user in the JSONL.

Search the JSONL for your --default-email-domain value or @local, correct addresses, then re-validate with mmctl import validate. If you leave temporary placeholders in place, update those users in Mattermost after import.

5. 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 release for your OS and architecture.

  2. Run mmetl help (or ./mmetl help) to explore commands.

Validate the Slack export

./mmetl check slack --file slack_export.zip

Fix any reported structural issues before transforming.

Transform 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.

  3. Run mmetl check slack on each resulting archive, then continue with Run the transform using --team for the 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.

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.

  3. 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)

  4. 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.

  5. 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.

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.

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"}
    

6. 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 (oldest 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).

7. Package and import into Mattermost

When mmetl finishes, you should have:

  • A .jsonl bulk import file

  • A data/ directory of attachments

Before you import

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.

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.

Choose an upload method

Situation

Recommended 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.

Monitor jobs with mmctl import job list / mmctl import job show <JOB_ID> --json as described in the bulk loading and mmctl docs.

After you import

These steps are Slack-migration specific after a successful bulk import.

Fix unread channels and threads

After importing, messages may appear unread for users. Run against the Mattermost database:

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;

Email verification and account activation

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.

How are multi-party DMs represented in Mattermost?

They become Group Messages (or private channels if over the group size limit). See Direct messages and multi-party DMs.

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 check slack, and file an issue if a new format break is confirmed.

What additional considerations are there for Slack Enterprise Grid?

Use mmetl grid-transform with a teams.json map, then transform each workspace archive separately. Shared channels must land in a single Mattermost team. See Enterprise Grid exports.

Are there features of Slack that are not supported?

See What migrates / What doesn’t. Core collaboration data can be migrated subject to that table; apps, workflows, and similar integrations must be rebuilt using Mattermost integrations.