Migrated from XenForo to Discourse: Overview of the Migration

We’ve successfully migrated our community (techenclave.com) from XenForo to Discourse.

Total Posts: 2.5 million
Total Users: 79K (active and banned)
Total Private Messages: 0.7 million

It is a 20 year old community so it has seen its fair share of migrations from Proboards, Vbulletin, IPB, Xenforo and now Discourse.

Took us 4 days to complete the migration. But it had 4 weeks of migration prework and 4 weeks of custom plugin development prior to that.

Thank you everyone for helping out and guiding wherever we got stuck. :folded_hands:
Overall it was a wildly successful migration with almost no useful data left behind.


The core of our migration was built upon an enhanced XenForo import script, significantly improving upon a standard base. We also leveraged several specialized supporting scripts to handle specific data transformations and ensure data integrity post-import.

Key Enhancements in the Main XenForo Import Script

Performance Optimization (Keyset Pagination): The most critical enhancement is the adoption of keyset pagination (WHERE id > last_id). This method drastically improves batch processing speed compared to traditional OFFSET queries, especially on large datasets, by using indexed primary keys for fetching the next set of records.

Robust Checkpointing and Resumption: Advanced checkpointing logic (using .json files) was implemented for users, topics, replies, and private messages. This allows the import process to safely resume from the last successfully imported record after interruptions (e.g., server restarts, script errors), saving significant time.

Two-Pass Post Import Strategy: Posts are now imported in two distinct passes:

Topics First: All original XenForo threads (first posts) are imported, ensuring that parent topics exist before replies.

Replies Second: Subsequent posts within threads are then imported, linking them correctly to their newly created Discourse topics. This structured approach minimizes orphaned replies and enhances data consistency.

Comprehensive Reaction/Like Import:

The script now intelligently differentiates between XenForo reactions that map directly to Discourse’s core “Likes” (hearts) and those that are custom reactions.

It supports bulk insertion of both core likes (into post_actions table) and custom reactions (leveraging the discourse-reactions plugin), significantly speeding up the process.

Includes a mapping for XenForo emoji shortnames to Discourse equivalents (e.g., thumbsup to +1, heart_eyes to heart).

Enriched Marketplace Data Import (Custom Plugin):

A dedicated section was added to import detailed marketplace listing data (e.g., price, location, condition, warranty, payment options) from XenForo’s thread_field_value table.

This data is stored in a custom TecencMarket::Listing model and as topic custom fields, enabling richer display and functionality on Discourse.

Marketplace Feedback Import (Custom Plugin):

A new function was introduced to migrate user feedback/ratings (likes/dislikes) associated with marketplace transactions.

Thread Prefix to Tag Conversion: The script now automatically converts XenForo’s thread prefixes into Discourse tags. This is crucial for maintaining content organization and discoverability, using the discourse-tagging plugin. It also correctly maps prefix IDs to human-readable titles using XenForo’s phrase table.

Enhanced Post Content Processing: This method received significant updates for better Markdown conversion and handling of XenForo’s complex BBCode:

Improved Quote Handling: Conversion of XenForo’s [QUOTE] tags, including handling of quoted users and linking to specific Discourse posts/topics when possible.

Corrected Media Embeds: Ensures that all imported media (YouTube, Twitter/X) are on their separate lines to enable Discourse’s oneboxing/embedding feature, and converts old BBCode [MEDIA] tags to standard URLs.

Table Conversion: Convert XenForo’s [TABLE] BBCode into Markdown tables.

Attachment Handling (Inline & Appended): The attachment processing logic was refactored. It now attempts to replace [ATTACH] tags with the correct Markdown for the uploaded file. Crucially, any attachments that were not explicitly tagged [ATTACH] in the post content are now appended at the end of the post under a clear “Attachments:” header, ensuring no files are lost.

Emoji/Smiley Conversion: Expanded mapping for XenForo smileys to native Discourse emojis, improving visual consistency.

General BBCode Cleanup: More comprehensive removal and conversion of various BBCode tags (e.g., [B], [I], [URL], [IMG], [LIST], [CODE], [COLOR], [FONT], [SIZE], [INDENT], [USER]).

Character Encoding & Scrubbing: Includes .scrub! for invalid UTF-8 sequences and CGI.unescapeHTML for proper HTML entity decoding.

Suspended User Handling: Banned XenForo users are now imported as suspended Discourse users, retaining their accounts but limiting their activity, with ban reasons preserved in custom fields.

Bookmarks Import: Import all the bookmarks from XenForo to Discourse. There weren’t many to begin with.


A large section of users were not happy after the migration. Which is expected. Its not something you all have not heard before after a migration. Nevertheless, will share a summary of the feedback later in this topic, there might be some food for thought to feed into the future of discourse.

11 Likes

Thanks for sharing! A lot of stuff on the list that I identified as action items for an ongoing SMF to Discourse migration project :slightly_smiling_face:

Is your customized importer code available to the public? I would especially be interested in how you handled the two-pass import to ensure correct new links.