The Mattermost ETL is a tool to transform an export from another platform into
a Mattermost-compatible bulk import file (JSONL) plus an attachments directory,
ready to be imported with mmctl import.
| Provider | Input | Command |
|---|---|---|
| Slack | export .zip |
mmetl transform slack |
| Slack Enterprise Grid | export .zip |
mmetl grid-transform |
| RocketChat | mongodump directory |
mmetl transform rocketchat |
To install the project in your $GOPATH, just run:
go install github.com/mattermost/mmetl@latestThe typical workflow is two steps — dry-run the transform to catch issues,
then write the import file. --dry-run takes the same flags as a real run
(--team is required, and --bot-owner if the export contains bots). It
writes nothing and exits non-zero if problems are found, including missing
attachments that a real transform would skip.
# 1. Dry-run the transform to catch issues before writing output
mmetl transform slack --team myteam --file export.zip --dry-run
mmetl transform rocketchat --team myteam --dump-dir /tmp/rc-dump/meteor --dry-run
# 2. Transform it into a Mattermost import file
mmetl transform slack --team myteam --file export.zip --output mm_export.jsonlSlack Enterprise Grid exports must be split first. mmetl grid-transform infers each workspace ID from the teams/<name>/ folders already in the archive and writes one zip per workspace:
mmetl grid-transform --file slackexport.zip
# then, for each generated zip:
mmetl transform slack --team acme --file acme.zip --dry-run
mmetl transform slack --team acme --file acme.zip --output acme.jsonlPass --team-map-path teams.json only if you need to override the inferred mapping. The file is a JSON object: keys are Slack workspace IDs (the team field on posts, typically T...); values must match an existing folder under teams/ in the export — they are not Mattermost team names.
The tool is self-documented — run any command with --help to see its
subcommands and options:
mmetl --helpRocketChat marks guests with a guest role (not a distinct user type). Control
how they are migrated with transform rocketchat --guest-handling:
guest(default) — migrate them as Mattermost guests (system_guest/team_guest/channel_guest). Highest fidelity. This only behaves correctly if the destination server has Guest Accounts licensed (Professional/Enterprise) and enabled (GuestAccountsSettings.Enable). The import will not fail without it, but the accounts won't behave as guests — useusermode for targets without guest licensing.user— migrate them as regular Mattermost users. Works everywhere, but grants guests full user permissions.skip— drop guest users entirely, along with their memberships and authored posts.
Users whose RocketChat type is neither user nor bot (for example app
accounts like rocket.cat) are always skipped, and any memberships, posts, and
reactions referencing them are dropped so the import stays referentially
consistent.
Full CLI reference is generated under docs/cli. For the end-to-end Slack migration guide, see the Mattermost docs.
Slack marks guests with the is_restricted (multi-channel guest) or
is_ultra_restricted (single-channel guest) flags on the user object. Control
how they are migrated with transform slack --guest-handling:
guest(default) — migrate them as Mattermost guests (system_guest/team_guest/channel_guest). Highest fidelity. This only behaves correctly if the destination server has Guest Accounts licensed (Professional/Enterprise) and enabled (GuestAccountsSettings.Enable). The import will not fail without it, but the accounts won't behave as guests — useusermode for targets without guest licensing.user— migrate them as regular Mattermost users. Works everywhere, but grants guests full user permissions.skip— drop guest users entirely, along with their memberships and authored posts/reactions.
A guest's team and channel memberships mirror their Slack access scope: they
are only added to the channels they belonged to in the Slack export. Mattermost
can only scope a guest's access through public/private channel membership, so
in guest mode a guest with no public/private channel in the Slack export
(for example, one present only in a DM or MPIM) cannot be validly imported as
a guest. Rather than silently promoting them to a full member, they — along
with their memberships and authored posts — are skipped, and a warning is
logged. Use --guest-handling=user if you'd rather those guests be imported
as regular members instead of skipped.
See AGENTS.md for architecture, conventions, and the checks to run after making changes.
The CLI docs in docs/cli/ are generated from the Cobra command definitions.
After changing any command or flag, regenerate and commit them:
make docs # regenerate docs/cli/
make docs-check # verify they're up-to-date (CI enforces this on PRs)