Migrating a Discourse forum to atmoBB is not a normal data migration
Someone asked recently whether an existing Atmosphere community could move from Discourse to atmoBB. My first thought was that it's a converter project: export users, categories, topics, and posts from Discourse, turn them into atmoBB records, import them, done. And, if that were it, it would be so simple!!
A regular forum owns the database its users and posts live in. An atmoBB forum doesn't. Public topics and replies live in their authors' AT Protocol repositories. The forum is an AT Protocol account too, but its repository only holds the forum profile, categories, boards, staff grants, and moderation actions, and everybody's actual conversations live somewhere else.
So moving a Discourse community over isn't one admin copying data from one database into another. It's a coordinated migration that involves every author, their identity, their repository, and their consent, and I don't have good answers for most of it yet 😅
Who gets to create an old post?
In atmoBB, a public topic is a record in its author's repository, and a reply is a record in the replier's repository. Writing either one needs authorization from that account.
A Discourse admin has authority over the Discourse database. That doesn't give them any authority over a member's AT Protocol repository. Having a Discourse backup with Alice's old posts in it doesn't let the importer publish them as Alice.
Publishing every imported post from the forum account would be way simpler technically, but it throws away one of the main points of atmoBB. The forum would own the repository while the page says a member wrote the post, and the member couldn't take the post with them when they move PDSes, edit it as their own, or delete it from their repository. I wouldn't really call that a migration.
The clean ownership model has each former Discourse member authorize atmoBB to write their own topics and replies. Which opens up a whole can of worms:
- What happens to posts by people who never make an AT Protocol account?
- What about members who have died, left the community, lost access, or just don't respond?
- Is the migration complete if the most important topic starter never shows up?
- How long does the migration stay open for later claims?
- Can someone authorize their posts without joining the new forum?
- Can they look through their posts and exclude some before publishing?
- What if they authorize an import and later change their mind?
There's no transaction that spans hundreds or thousands of independent repositories, so a migration could just sit there half done forever.
Usernames aren't identities
Discourse knows a user by an internal numeric ID and shows a username. AT Protocol knows an account by a stable DID and currently shows it through a handle that can change, and none of those line up with each other.
A mapping typed in by the forum admin doesn't prove that whoever controls @alice on Discourse also controls alice.example on AT Protocol. Handles change, old usernames get renamed or reused, and admins make honest mistakes.
Members would probably need to claim their old Discourse identity:
- The migration stores the immutable Discourse user ID and its current username.
- The old forum sends a one-time claim link through a channel that Discourse account controls, like a private message.
- The member follows the link and signs into atmoBB with their AT Protocol account.
- The migration shows exactly what will be made public, then binds that Discourse user ID to the account's DID.
Even that leaves questions:
- Does the old Discourse instance have to stay online and able to send private messages for the whole claim period?
- Is email an okay fallback? If so, should email addresses be copied into the migration system at all?
- How do deleted, anonymized, staged, suspended, and system users get represented?
- Can one AT Protocol account claim several old Discourse accounts?
- What if one Discourse account needs to be split between multiple people?
- How do you reverse a fraudulent or mistaken claim after public records are already published?
- Should claims expire, and who can reissue one?
For a while, the migration service would be holding a really sensitive link between private Discourse account data and public decentralized identities. Protecting that data, and eventually deleting it, has to be part of the design from day one.
Order matters
An atmoBB board record lives in the forum's repository. A topic points to that board. Every reply has a strong reference to its topic, meaning both the topic's AT URI and its content identifier (CID). A reply can also strongly reference another reply as its parent.
So the import has to happen in order:
- The forum authorizes creating categories and boards.
- The topic starter claims their identity and publishes the topic.
- The importer gets back the resulting URI and CID.
- Other members can publish replies to it.
- Nested replies may add more ordering constraints on top of that.
If a topic starter never claims their account, every reply in that topic is stuck, even replies from people who did claim. You could import the replies as disconnected posts, but then the conversation is gone, and you can't hand the topic to someone else without lying about who wrote it.
A reliable importer would also need deterministic record keys, resumable jobs, dependency tracking, idempotent retries, and a durable audit trail, because retrying after a failure can't duplicate half a forum. That stuff is all solvable! It just does nothing for the missing-author problem.
Two different times
An imported record can carry the original Discourse createdAt, but the record is being committed to an AT Protocol repository today, and consumers could pretty reasonably read those two times differently.
That matters for more than display order:
- Should importing ten-year-old posts show up as new activity on the network?
- Should it fire notifications, watched-board alerts, or mention alerts?
- Do imported posts count toward first-post stamps and other historical forum state?
- What about old posts that predate the member's atmoBB profile or forum membership record?
- On a gated forum, were these posts written inside a membership window that didn't exist yet?
- How does an appview tell a historical import apart from a new post with a forged createdAt?
Adding an importer-only marker to normal discussion records would need a protocol decision. Trusting a forum to vouch for everybody's historical content would also create a new authority relationship that atmoBB doesn't have right now.
The source keeps moving
A Discourse export is a snapshot, and members can keep creating, editing, moving, and deleting content after it's taken.
So a cutover needs an explicit consistency plan. You could put Discourse in read-only mode before the final export, do an initial copy followed by a delta pass, or try a period of dual writing, and every one of those costs something:
- A long claim period fights with a short maintenance window.
- A topic might get claimed before its latest edit makes it into the export.
- Someone might delete a post on Discourse after approving an earlier copy.
- IDs and references have to stay stable across repeated exports.
- New replies can show up under a topic that's already been imported.
- There's no atomic moment where every independent PDS switches over together.
The practical cutover might need to separate claiming identity from publishing records: collect claims ahead of time, freeze Discourse, then publish a final export for everyone who claimed. That's still a whole community operation and not an import command you run once.
The long tail of Discourse content
Basic paragraphs, headings, links, lists, quotes, and code blocks can be converted. The long tail is harder:
- Discourse Markdown, cooked HTML, BBCode accepted by plugins, and custom theme markup
- mentions of Discourse usernames that may or may not have claimed DIDs
- quotes of posts whose authors or parent topics are still unclaimed
- local emoji, oneboxes, link previews, footnotes, tables, and plugin-defined blocks
- polls and votes
- tags, accepted answers, wiki posts, bookmarks, reactions, and likes
- post revisions and edit history
- deleted, hidden, flagged, or staff-only posts
- topic timers, slow mode, auto-close state, and other Discourse-specific behavior
For each feature, the migration has to pick: keep the semantics, keep only readable text, link back to the old site, or drop the data. I'd want those choices in a preflight report, not discovered after cutover.
Uploads
Images in public atmoBB posts are blobs in the author's PDS, referenced by a record in that same repository. You can't just copy a forum-wide media directory into one central atmoBB upload directory.
To migrate an embedded image as a real atmoBB image, the importer needs the author's authorization to upload the blob to their PDS and put its blob reference in their post record. That drags in storage quotas, file-size and media-type limits, duplicate files, broken source uploads, remote hotlinks, and PDS rate limits.
Leaving every image on the old Discourse domain gets around the authorization problem, but then the migration depends on the old server forever, and the day the old site goes off the archive breaks. Copying all the media to a separate web host keeps things rendering but loses member ownership.
Avatars have their own identity question. A Discourse avatar is forum account data. An atmoBB avatar is part of the member's AT Protocol profile and follows them to every forum, so a forum migration shouldn't overwrite someone's existing cross-forum identity without them explicitly saying yes.
Accounts don't migrate
Discourse passwords, sessions, two-factor credentials, email verification, trust levels, and SSO relationships can't turn into AT Protocol accounts. Members need an existing account or have to create one through an account provider.
Profile fields have a different scope too. A Discourse bio, avatar, title, website, and signature came from one forum. Some atmoBB profile fields are account-wide and others can be overridden per forum. Importing a forum bio as an account-wide identity could change how that person shows up on every atmoBB forum without them expecting it.
The migration has to decide which profile data gets offered as an opt-in forum override, which gets ignored, and which can safely become an account default, and none of that should happen silently.
Private categories
Public Discourse categories map pretty well to atmoBB categories and boards. Private and restricted areas don't.
Discourse can restrict categories by group, trust level, staff status, and plugin-defined policy. atmoBB's members-only boards use Happyview permissioned spaces. Those topics and replies don't live in public AT Protocol repositories at all. They live in the forum's appview database, which has different backup and portability properties.
Open questions:
- How do Discourse groups become space memberships?
- Who authorizes those memberships, and at what point in the cutover?
- Can private content be staged without exposing it to the public firehose?
- Do former members get access back to an old private category?
- What happens to private content written by someone who never claims an account?
- Can the operator prove a supposedly private export never got written to a public repository?
Personal messages and group messages are a separate product, and they aren't forum topics. atmoBB doesn't have a migration destination for them right now, so they should be explicitly excluded before they accidentally get imported as public discussions (which would be a very bad day).
Moderation history
Discourse moderation state includes staff roles, category moderators, suspensions, silences, deleted posts, flags, warnings, closed topics, pinned topics, and a staff action log. atmoBB represents moderation as records signed by the forum account.
Replaying old actions as if the new forum account did them years ago would create a public audit history that never happened on AT Protocol. Dropping all of it loses useful state. Copying only the current state (who's staff, which topics are locked, which members are still banned) might be the least misleading option, but even that needs a review.
The policies don't line up either. A Discourse suspension could cover private messages and login as well as posting, while an atmoBB ban affects forum writes and indexing. Trust levels, badges, likes, and flags have no direct equivalent. Someone has to sort out which state can be translated and which history can only be archived.
Membership
On an open atmoBB forum, a membership declaration lives in the member's repository. On a gated forum, membership also needs an acceptance authored by the forum. A Discourse user row is neither of those.
Declaring membership automatically for every exported user runs into the same authorization problem as importing posts. Auto-accepting everybody might be reasonable for a gated successor forum, but that still doesn't create the members' own declarations. People who only read, people who posted once years ago, suspended users, and people who opted out of the migration each need a deliberate answer.
Arrival dates, sponsors, invitations, applications, first posts, and stamps can pick up false historical meaning if they're rebuilt mechanically. I don't want the migration making up a tidy history for data that never had one.
Links and redirects
Discourse URLs identify topics and posts by Discourse IDs and slugs. atmoBB URLs identify records by DID and record key. Every internal link, external bookmark, search result, email archive, and quoted URL expects the old address to keep working.
A serious migration needs a permanent redirect map for topics and individual posts. That map can't be final until the matching atmoBB records exist, and those might depend on claims that never come in, so links to unclaimed content need somewhere to land.
The old domain might also host uploads, API endpoints, feeds, and well-known URLs. Reusing it for the atmoBB forum while keeping a legacy archive and redirects means planning the routing on purpose. Search indexing, canonical URLs, and link previews shouldn't present duplicate copies as separate conversations.
Federation
A post on a Discourse forum went out under that forum's access rules and social expectations. A public atmoBB post is a record in the author's repository that any compatible appview can index, and boards can also take part in topic federation across forums.
Even if the old Discourse category was public on the web, members might not expect years of posts to turn into portable repository records tied to their DID. The claim screen has to explain that change in audience clearly, because "we moved the website" isn't consent.
No imported board should get opted into topic federation just because its name looks like an existing topic. Federation is a new distribution decision for the forum owner and its members.
Rollback
Before anything is published, staged migration data can be deleted like any private import job. Once records are written to members' repositories and announced on the firehose, restoring the forum database doesn't pull them back.
The importer could delete records from accounts it still has authorization for, but other appviews may have already indexed them, the authorization may have expired, and some users may have edited things or moved their accounts in the meantime. A failed migration can't promise a clean global rollback.
That's why preflight, dry runs, explicit consent, small batches, and audit logs matter way more here than for a centralized database import. "Take a backup first" is still necessary for the forum's own state, but it does nothing for records already out on the network.
Privacy and deletion
A full Discourse backup can include email addresses, IP-derived metadata, deleted content, private messages, staff notes, and stuff only restricted groups could see. Most of that isn't needed to convert public forum content and should never get into the migration system.
The process needs written answers for:
- which source tables and API fields get collected
- where the export and claim mappings are stored
- who can look at them
- how they're encrypted and backed up
- when raw exports, claim tokens, and identity mappings get deleted
- how data subject requests are handled during and after the migration
- what evidence gets kept to show a member approved publishing
Because AT Protocol publishing is decentralized, "delete" can't mean every indexer forgets a record. Members need to understand that before they import years of history under a durable DID.
The options
I see four broad approaches right now, and every one of them gives something up.
1. Claim and publish
Members prove they control their Discourse identities, connect AT Protocol accounts, and authorize their own records. This keeps atmoBB's ownership model intact better than anything else.
It also guarantees an incomplete migration unless nearly everybody participates. Unclaimed topic starters block repliers who'd otherwise be happy to move, media imports need per-user writes, and the whole thing could take months.
2. A forum-owned legacy archive inside atmoBB
The forum imports old content into a new archive record type and shows the original Discourse username as legacy attribution. Claims could link identities later, but the records stay forum-owned unless they get republished.
This keeps history readable and the conversations intact, but it needs new protocol records, appview queries, UI, moderation rules, and very obvious ownership labels. It also creates a permanent second kind of topic in atmoBB and weakens the simple rule that public posts belong to their authors.
3. A static read-only Discourse archive next to atmoBB
The old forum becomes a frozen archive. The community starts new conversations on atmoBB, with redirects or links between the two sites.
This is honest operationally and keeps old URLs, authorship, and uploads. It isn't a data migration, though. Old discussions don't become atmoBB records, don't show up naturally in member activity, and still depend on someone hosting the archive.
4. A clean start
The forum brings over its categories, rules, staff, and current membership policy, but not the old discussions. Authors can link, summarize, or repost important topics by hand.
This loses the most continuity but has the clearest story on ownership and consent. For some communities, keeping the old site up and starting fresh is probably better than a technically impressive migration nobody really agreed to.
What I'd want answered first
Before writing any code, I'd want firm answers to at least these:
- When complete history, correct member ownership, and a practical community cutover conflict, which one wins?
- What's the permanent representation of unclaimed topics and replies?
- How does a member prove a Discourse identity, and how long can they claim it?
- What source content is excluded outright: private messages, deleted posts, restricted categories, revisions, moderation notes?
- Where do uploads live when their authors don't claim them?
- Which current moderation and membership state transfers, and which history stays an archive?
- How do old URLs keep working permanently?
- What's the freeze and cutover procedure?
- What does failure recovery mean once records have hit the network?
- What exact consent text explains public repository ownership and decentralized indexing?
- When is the migration finished if claims are still outstanding?
So
The Discourse parser is the easy part! The hard part is deciding who owns a community's history and what it means to republish it on a decentralized network...... and that feels really complex an so "clean start" or "static archive" feels easiest, but the least fulfilling as a community member :(
Hi, I'm Keith! You can @ me with any bugs or problems or ideas, but pls be nice.