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:
Export data from Slack (using Slack’s tools and documentation).
Understand which export format you have (single workspace vs Enterprise Grid).
Review what migrates and what does not.
Transform the Slack export into a Mattermost bulk import package with mmetl.
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 |
|---|---|---|
Scope the migration and tune Mattermost |
Server ready for import |
|
Generate a Slack export zip |
Raw Slack archive |
|
Identify single-workspace vs Enterprise Grid |
Correct transform path |
|
Know gaps (emoji, guests, emails, MPIMs, integrations) |
Realistic expectations |
|
|
|
|
Process phased or split exports safely |
Combined history without duplicates |
|
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:
mmetlis 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¶
Fresh server: The most reliable imports use a fresh Mattermost installation. If importing into an existing server, never import over an existing team.
Server version: Use the latest supported Mattermost release.
Backups: Back up the database and file store before importing into an existing environment. If an import fails, roll back or reset.
If merging multiple Slack workspaces into a single team is the desired end-result, we recommend completing the import to separate teams, validating the results, then using mmctl to move channels between teams.
Configuration before import:
TeamSettings.MaxChannelsPerTeam: higher than the number of channels you will import.
TeamSettings.MaxUsersPerTeam: higher than the number of users you will import.
Team Settings > Allow any user with an account on this server to join this team: enabled for the destination team.
EmailSettings.EnableSignUpWithEmail and EmailSettings.EnableSignInWithEmail: both
true.FileSettings.MaxFileSize: higher than the largest attachment in the Slack export.
ElasticsearchSettings.EnableIndexing, EnableSearching, and EnableAutocomplete: set to
falseduring import. Purge and reindex after import if you use Elasticsearch.
Guest Accounts: If you will keep Slack guests as Mattermost guests (default
mmetlbehavior), ensure Guest Accounts are licensed and enabled (GuestAccountsSettings.Enable).
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:
Public channels export: Public channel messages and file links. Available on all Slack plans.
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.jsondms.json,groups.json,mpims.json(all-channels export)integration_logs.jsonPer-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.
mmetlhas 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 |
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 ( |
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 |
Channel mentions (embedded) |
Best effort |
Slack |
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. |
Custom emoji images |
No (separate step) |
|
Emoji skin tone modifiers |
No |
Stripped (for example |
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.
mmetlstrips Slack names at::(for example+1::skin-tone-3becomes+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 |
|---|---|
|
Export them as Mattermost guests ( |
|
Export them as regular Mattermost users (full member permissions). |
|
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.
mmetlskips that user and drops their memberships and authored posts/reactions, and logs a warning. Use--guest-handling=userto 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 |
|
Optional: only if the synthesized |
|
Required: blank emails fail Mattermost import validation; set a real email on each affected |
Placeholder users with |
Recommended: replace |
Flags
--default-email-domain <DOMAIN>: sets missing emails tousername@<DOMAIN>(for example--default-email-domain example.comyieldsjane@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.
Download the latest release for your OS and architecture.
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.
Download the full Grid export from Slack.
Split it into per-team exports:
./mmetl grid-transform -f slackexport.zip
grid-transforminfers each Slack workspace ID from theteams/<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 ateamfield.Run
mmetl check slackon each resulting archive, then continue with Run the transform using--teamfor 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
teamfield (typicallyT...).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--teamvalue.
Run the transform¶
Use a small export (or --skip-attachments) for the first run so you can learn the flow quickly.
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.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-domainor--skip-empty-emails. Prefer--default-email-domainwith your organization domain. See User emails.Expected outputs in the working directory:
mattermost_import.jsonl: users, channels, posts, and related recordsdata/(or your--attachments-dir): attachment files underbulk-export-attachments/transform-slack.log: INFO-level progress (use--debugfor more detail)
Spot-check success:
The command exits
0.The JSONL starts with a
versionline and containsuser,channel/direct_channel, andpost/direct_postlines.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.
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
Transform and import one channel’s worth of history (or a short time window) with
--skip-attachments.Validate in Mattermost.
Expand to a full workspace export, still without attachments if needed.
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 (defaultdata).--default-email-domain <DOMAIN>: Synthesize missing emails asusername@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 intransform-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.
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"}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"}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"}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_atare not duplicated.Keep team names and
--guest-handling/--bot-ownerchoices 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
.jsonlbulk import fileA
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) |
|
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:
Copy
mattermost-bulk-import.zipto the server (for example into the Mattermostdata/importdirectory, or another path readable by the server process).On the server, in mmctl local mode, process the file directly:
mmctl import process --bypass-upload ./mattermost-bulk-import.zip --local
Notes:
--bypass-uploadworks 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-transformdoes not merge user lists between workspaces, so the participant who is not a member of the assigned workspace is missing from that team’susers.json.mmetl transform slackthen creates a deactivated placeholder user named after the Slack user ID, with a<userid>@localemail address. Correct those addresses before you import; see User emails.Unassignable conversations are dropped. If a conversation’s posts carry no
teamfield, or the workspace ID is not in the team mapping,grid-transformlogs an error togrid-transform-slack.logand 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.