Question necromancy: migrating from mattermost

Question

I would like to know about the current best advice on data migration from a Mattermost team edition instance to a discourse instance (I’m administering both).

I have already read the advice on how to coordinate the move, and therefore I am now looking for technical resources and advice on data migration.

Context

It is October 2025, and I find myself in a situation similar to this question from 2018[1]:

The pressure in my case, however, is more definite—Mattermost is effectively shutting down their team edition[2] (technically it does not, but I view the changes as a violation of their open source commitment).

The original discussion here started with a recommendation to combine both platforms, and then was closed with a statement that Discourse now has a pretty OK chat (which it does, especially with the upcoming chat search). It did not, however, contain any advice for migration.


  1. the discussion concluded with a Discourse team member saying that Discourse uses Mattermost ↩︎

  2. ironically the announcement is posted using Discourse ↩︎

5 Likes

Hi Anton!

Migrating from Mattermost to Discourse is certainly doable, but will require building a custom import script, as there isn’t a pre-made script available yet (see available import scripts here). You can study the existing scripts for reference, but please double check their last update dates and adapt accordingly, as some could have outdated references to Discourse’s tables.

A pull request with a Mattermost import script would be very welcome by the community!

Channel and Data Mapping

You can import everything as chat channels but you can also map them as other types:

  • Channels as Categories: Import each Mattermost channel as a Discourse category. Threads within channels can become topics, with each message as a post. Alternatively, individual posts can be the OP of topics.
  • Channels as Topics: Another approach is to make each Mattermost channel a single topic, with messages as replies, threads will be displayed in sequence in this case.
  • DMs: They can be imported as private messages too. It’s a good idea for discussions that needs archiving.

Think carefully about which approach best fits your community and content volume.

Topic titles

When mapping a chat to topics and posts you have to create the titles for each topic. One amazing way of doing it is using Discourse AI to generate the titles with actual context for the topic.

Generating topic titles using AI

TL;DR use this method:

def gen_title(llm, system_prompt, topic)
  begin
  content = topic.posts.map(&:cooked).join("\n").slice(0..10_000)
  message = [{type: :user, content: content}]
  prompt = DiscourseAi::Completions::Prompt.new(system_prompt, messages: message)

  title = llm.generate(
      prompt,
      user: Discourse.system_user,
      temperature: 0.3,
      feature_name: "ai_helper"
  )

  topic.title = title
  topic.save!
  puts "Topic: #{topic.id}, changed sucessfully."
  rescue ActiveRecord::RecordInvalid
    puts "validation error"
  end
end

It needs an LLM and a master prompt

llm = DiscourseAi::Completions::Llm.proxy(SiteSetting.ai_helper_model)

system_prompt = <<-PROMPT
  I want you to act as a title generator for written pieces. I will provide you with a text,\nand you will generate a title. Please keep the title concise and under 20 words,\nand ensure that the meaning is maintained. The title will utilize the language type of the topic.\nI want you to only reply the proposed title and nothing else, do not write explanations.\nNever ever use colons in the title. Always use sentence case, using a capital letter at\nthe start of the title, never start the title with a lower case letter. Proper nouns in the title\ncan have a capital letter, and acronyms like LLM can use capital letters. Format some titles\nas questions, some as statements. Make sure to use question marks if the title is a question."
PROMPT

Then you can loop to a list of topics as you prefer:

# gen everything
Topic
  .joins(:_custom_fields)
  .where('topic_custom_fields.name = ?', 'import_id')
  .find_each { |topic| gen_title(llm, system_prompt, topic) }

# filter PMs
Topic
  .joins(:_custom_fields)
  .where('topic_custom_fields.name = ?', 'import_id')
  .where.not(archetype: "private_message")
  .find_each { |topic| gen_title(llm, system_prompt, topic) }

Important Considerations

  • Reactions: Mattermost supports multiple reactions per post. If importing as Discourse posts, you’ll need to limit to one, unless you’re mapping into Discourse Chat (which supports multiple reactions natively).
  • Custom Emoji: You can bring over custom emoji, see the Discourse Reactions plugin documentation.
  • Teams & Permissions: Mattermost “teams” don’t map directly to Discourse, but you can set up categories/channels with appropriate group based access controls.
  • Attachments: Mattermost attachments (images and documents) are not embedded in the content like in Discourse. When importing, you’ll need to append attachment links (Markdown) or embed them in the post bodies.

Reference Materials


We have experience with chat platform migrations, if you’d like help from our team, see our Discourse migration services page.

If you have specific questions during the script development or mapping decisions, feel free to ask on Meta for guidance!

5 Likes

Thanks for this very nice summary! I will look into it once the time for migration comes. If I end up with a script, I’ll certainly share it with the community.

Right now I lean towards importing chat into chat channels. Aside from the lack of search, which will be implemented soon, are there other considerations that are relevant?

2 Likes

In this case most of the work will be spent on parsing the messages. A few relevant things are:

  • Mentions will need a record in the database, check: mention.rb and group_mention.rb.
  • Attachments should also be imported as upload and referenced in the message markdown. Alternatively you can upload images to a temp server of your choice, add a link to them in the post and enable the setting: download_remote_images_to_local.
  • You still have to create categories for the channels to be able to set permissions.
  • If you use SSO, you can import users from your Identity provider directly. Check: Sync DiscourseConnect user data with the sync_sso route.
4 Likes