Skip to content

About

Migrate users, channels, DMs, messages (with original timestamps), threads, reactions, avatars and file attachments from Mattermost (PostgreSQL) to Rocket.Chat

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

mattermost2rocketchat

Migrate a Mattermost workspace (PostgreSQL backend) to Rocket.Chat — users, channels, direct messages, messages with original timestamps, threads, reactions, edits and file attachments.

Built for a real-world migration (Mattermost Enterprise 11.x → Rocket.Chat 8.x Community, ~50 users, ~200k messages incl. delta/cutover mode) and released so others can reuse it. Tested against Rocket.Chat 8.5.x and 8.8.x; the direct-MongoDB parts are inherently version-sensitive, so always run verify after a load and test against your exact Rocket.Chat version first.

How it works

Two decoupled commands connected by a portable archive format:

mattermost2rocketchat extract --out ./archive        # runs on the Mattermost host (read-only)
mattermost2rocketchat load   --archive ./archive     # runs on the Rocket.Chat host
mattermost2rocketchat verify --archive ./archive     # count + content comparison
  • extract reads the Mattermost PostgreSQL database read-only (users, teams, channels, channelmembers, posts, reactions, fileinfo) and copies the file blobs from the Mattermost data directory into an archive: JSONL files + blob tree + manifest.json with counts and SHA-256 checksums.
  • load imports the archive:
    • users, public/private channels and memberships via the REST API (Rocket.Chat keeps its own consistency),
    • direct/group messages as rooms directly in MongoDB (the API cannot create DMs between arbitrary users),
    • all messages directly in MongoDB with deterministic 17-char IDs derived from the Mattermost post ID — this is the only way to preserve historical timestamps, and it makes every run idempotent,
    • file attachments via REST upload (staged through a private mm2rc-staging room so the admin always has write access, then re-linked to the target room in MongoDB); files the API rejects (corrupt images, blocked types) fall back to a direct store write when rocketchat.uploads_dir is configured,
    • room counters, lastMessage, thread aggregates and read markers,
    • cleanup of system-message noise Rocket.Chat itself generates during the import (subscription-role-added/removed, room_changed_topic, ...).
  • verify compares per-channel message counts, user/channel/file mappings, thread counts and file sizes between archive and Rocket.Chat, and writes a machine-readable report.

Delta / cutover mode

load is incremental by design: re-running it only inserts what is missing. The typical cutover is: full extract+load → validate → maintenance window → Mattermost read-only → final extract+load (delta) → switch DNS/proxy.

The SQLite state file (<archive>/mm2rc-state.sqlite3) holds the Mattermost↔Rocket.Chat ID mappings. Keep it next to the archive — without it, user/channel/file linkage cannot be resumed (messages are still idempotent via deterministic IDs).

Install

Requires Python 3.11+.

pip install .
# or for development
pip install -e '.[dev]'

A Dockerfile is included for running on servers without a Python setup:

docker build -t mattermost2rocketchat .

Configuration

YAML file (-c config.yaml) and/or environment variables:

mattermost:
  pg_dsn: postgresql://user:pass@mattermost-host:5432/mattermost
  data_dir: /opt/mattermost/data
  team_strategy: flatten        # flatten | prefix (channel name collision handling)
rocketchat:
  url: http://rocketchat:3000
  admin_user: admin
  admin_password: secret
  mongo_url: mongodb://mongodb:27017/rocketchat?replicaSet=rs0&directConnection=true
  uploads_dir: /opt/rocketchat/uploads   # host path of RC's upload store (direct-store fallback)
options:
  placeholder_email_domain: localhost.invalid   # for MM users without an email
  include_system_messages: true                  # join/leave/add/remove/...
  batch_size: 1000

Environment overrides: MM_PG_DSN, MM_DATA_DIR, RC_URL, RC_ADMIN_USER, RC_ADMIN_PASSWORD, RC_MONGO_URL, RC_UPLOADS_DIR.

Example

# on the Mattermost host
mattermost2rocketchat -c config.yaml extract --out /tmp/mm-archive

# copy the archive to the Rocket.Chat host, then:
mattermost2rocketchat -c config.yaml load --archive /tmp/mm-archive --dry-run
mattermost2rocketchat -c config.yaml load --archive /tmp/mm-archive
mattermost2rocketchat -c config.yaml verify --archive /tmp/mm-archive --out-json report.json

What is migrated

Mattermost Rocket.Chat Notes
Users (incl. deactivated) Users via REST API random unusable password; use LDAP/OAuth merge or password reset
User avatars users.setAvatar profile pictures (users/<id>/profile.png) uploaded per user
Public/private channels Channels/groups via REST API display names (umlauts/spaces/case) preserved as fname; name collisions across teams get suffixed
Channel memberships + admins Subscriptions + owner role
DMs and group DMs d rooms (direct MongoDB) incl. self-DMs
Messages Messages (direct MongoDB) original timestamps, deterministic IDs
Threads Threads (tmid, tcount, tlm)
Reactions Reactions (:emoji:) custom emoji render as text unless the emoji exists in RC
Edited messages editedAt/editedBy
File attachments Uploads + linked messages all files per post; API upload with direct-store fallback for rejected files
@mentions, #channel refs, URLs mentions/channels/urls subdocs ~channel rewritten to #channel
System messages (join/leave/add/remove/topic/header/purpose/rename) RC system message types best-effort; unmapped types are skipped (counted in stats)

Limitations

  • Passwords are not migrated (Mattermost stores bcrypt hashes; Rocket.Chat uses a different scheme). Users authenticate via LDAP/OAuth merge or password reset. For LDAP: enable merge existing users and matching usernames.
  • No bots/webhooks/integrations, no user preferences (channel mutes, notification settings), no pinned/saved messages, no channel moderation settings.
  • Mattermost teams are flattened (Rocket.Chat teams are an optional, different concept); use team_strategy: prefix if you have name collisions.
  • Rocket.Chat re-encodes uploaded images (strips metadata / recompresses), so image file sizes in Rocket.Chat can differ from the archive; verify tolerates this for image types and compares sizes exactly for all other files.
  • Direct MongoDB inserts target the Rocket.Chat 8.x schema (tested on 8.5.x and 8.8.x). Pin your version, test, and always run verify.
  • Messages imported via MongoDB lack the pre-rendered md markdown AST; the web client renders them from msg. If you rely on API consumers that require md, let us know (issue welcome).

Development

pip install -e '.[dev]'
pytest              # unit tests (no external services)
ruff check .

Integration fixtures (seeded Postgres + Rocket.Chat + MongoDB) live in docker-compose.test.yml — see tests/integration/.

License

Apache-2.0 — see LICENSE.

About

Migrate users, channels, DMs, messages (with original timestamps), threads, reactions, avatars and file attachments from Mattermost (PostgreSQL) to Rocket.Chat

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages