Skip to content

Known Limitations

Every migration involves trade-offs. Discord and Stoat are different platforms with different capabilities, and some Discord features have no direct equivalent in Stoat. This page documents what changes, what gets lost, and what workarounds are available.

Already migrated with an older version?

Everything on this page is a deliberate limitation. Ferry also had five bugs that lost content silently, fixed between v2.8.2 and v2.10.0 — missing forwarded messages, missing attachments, and voice channels nobody could use. If you migrated before v2.10.0, see Was my earlier migration affected?.


Structural

These limitations relate to how channels, threads, and server organization are represented after migration.

Discord Feature What Stoat Gets Workaround
Threads Flattened to text channels by default, prefixed with parent channel name (e.g. general-my-thread) Use --thread-strategy merge to append thread messages into the parent channel, or --thread-strategy archive to export as a markdown attachment. Both are also available in the GUI's thread strategy setting.
Forum posts Each post becomes a text channel inside a forum-* category, with an auto-generated index channel listing all posts None — this is the closest structural equivalent
Stage Channels Not migrated (no Stoat equivalent) None
Scheduled Events Not migrated None
Channel ordering Categories follow the original Discord order (since v2.3.0, requires a Discord token), but channels inside a category may appear in a different order Manually reorder channels within categories in Stoat after migration

Thread handling and channel limits

With the default flatten strategy, every thread becomes a channel. A busy Discord server with hundreds of threads can easily exceed Stoat's 200-channel limit. Use --thread-strategy merge or archive to avoid creating extra channels, or use --skip-threads to omit threads entirely. Self-hosted admins can raise the limit — see Self-Hosted Tips.


Content

These limitations affect how individual messages and their content appear after migration.

Discord Feature What Stoat Gets Workaround
Embeds Flattened to markdown text; inline fields use \| separators None — Stoat embeds have a different structure and cannot replicate Discord embeds exactly
Polls Rendered as plain text showing the question and options None
Stickers Uploaded as image attachments where the source file is available; Lottie (animated) stickers receive a text fallback None — Lottie format is not supported by Stoat
Reactions Text summary appended to the message by default. Shows emoji and count. Stoat allows at most 20 reactions per message; anything beyond that is dropped and counted in the migration report. Use --reaction-mode native (or the GUI's Reaction mode dropdown) for per-emoji reactions added via the API — slower, still capped at 20 per message
Forwarded messages Content, attachments, embeds and stickers are migrated, marked [forwarded]. They post under the name of whoever forwarded them, not the original author. None for attribution — DiscordChatExporter's forwarded block carries no author field, so the original writer is not in the export at all. Exports made with DCE older than 2.47 have no forwarded content to recover and are still skipped with a warning; re-export to recover them.
Long messages (>2000 chars) Split into sequential parts with [continued K/N] markers (e.g. [continued 2/3]). Original content is fully preserved across parts. None needed — splitting is automatic and lossless
Author names >32 chars Truncated to 29 chars with a #XXXX discriminator suffix derived from the author's Discord ID. Ensures uniqueness while fitting the Stoat masquerade name limit. None — the suffix preserves attributability even after truncation
Unreferenced Autumn uploads --cleanup-orphans detects Autumn file IDs referenced in state but not in the final server. Reported in the migration summary. No automatic deletion — Stoat does not expose a DELETE endpoint for Autumn files. Clean up manually via the Stoat web interface if needed.

Permissions

These limitations affect role and permission migration.

Discord Feature What Stoat Gets Workaround
Per-member channel overrides Not supported by Stoat; only role-based overrides are migrated Create a single-user role for each member who had individual overrides, then apply the override to that role
Managed roles (bot roles) Not migrated — these are auto-created by Discord for each bot integration None needed — bot integrations do not carry over
Role membership Roles are recreated, but members are not assigned to them — there is no reliable way to map Discord accounts to Stoat accounts Members re-join via invite and assign roles manually (or with a Stoat bot)
"Pin messages" permission Not migrated on its own — Stoat has no pin-only permission. Pinning lives under ManageMessages, which also allows deletion, so Ferry will not grant it from a pin-only Discord role Grant ManageMessages manually to roles that should be able to pin
Discord permissions with no Stoat counterpart Dropped — threads, events, polls, soundboard, stickers, application commands, server insights, TTS, priority speaker and voice activity detection have no Stoat equivalent to grant None needed — the underlying features do not exist on Stoat

Metadata

These limitations affect message metadata and server history.

Discord Feature What Stoat Gets Workaround
Original timestamps Preserved as an italic text prefix *[2024-01-15 12:00 UTC]*, not as message metadata. Stoat shows the import time as the "sent" time. Self-hosted admins can use direct database insertion for true timestamps — see Timestamp Preservation
Edit history An *(edited)* indicator is shown on edited messages, but full edit history is lost None
Audit logs Not migrated None
Pin order Pins are restored, but their display order may differ from Discord None

Scale and Compatibility

These limitations affect use cases involving large servers or non-standard export types.

Limitation Detail Workaround
GDPR export incompatibility Ferry is designed for server migrations using DiscordChatExporter guild exports. GDPR personal data packages have a different structure and are not supported. Use DiscordChatExporter.Cli exportguild instead of GDPR downloads
1M+ message RAM usage The in-memory message_map dict requires approximately 200 MB of RAM for servers with 1 million messages. This is held for the duration of the migration. Split very large servers into batches, or use the --incremental flag to migrate in stages
No X-RateLimit-* headers Stoat's API does not expose standard X-RateLimit-Remaining or X-RateLimit-Reset headers. Ferry uses a 429-response rolling window to adaptively tune its request rate. None — this is a platform limitation. The adaptive rate limiter handles it automatically

Incremental Migration

--incremental copies messages that are new since the last completed run. Two things to know:

Limitation Detail Workaround
New messages only Incremental detects messages added after the last run. Messages that were edited or deleted on Discord in the meantime are not updated on Stoat. None — full edit/delete sync is out of scope
Failed messages Messages that failed to send in a previous run are re-sent automatically by the next --incremental run. None needed — this is automatic

Platform Features

These limitations relate to platform-level features that either work differently or have no equivalent in Stoat.

Discord Feature What Stoat Gets Workaround
Voice channels Created, but functionality may be limited due to a known upstream issue (Stoat Bug #194) Verify channel types in the Stoat web interface after migration; voice requires the Vortex or LiveKit service. Run ferry probe to check whether your instance supports voice before migrating.
AutoMod Not supported by Stoat Configure moderation manually after migration
Welcome Screen Not migrated None
Soundboard Not supported by Stoat None
Role icons Image icons are migrated (since v2.5.0, requires a Discord token). Emoji-only role icons cannot be migrated. None for emoji icons — Stoat only supports image icons
Animated emoji Static fallback uploaded where possible; some animated emoji may be skipped None — Stoat does not support animated emoji
Server boosts Not applicable — Stoat uses a different model None

Rollback

Limitation Why Workaround
Autumn objects are not removed by rollback Stoat's Autumn file store has no public DELETE endpoint. Uploaded attachments, avatars, and emoji images uploaded by a migration become orphan files on the Autumn server. None — the orphans are inaccessible from Stoat's UI once their parent channels/messages/emoji are deleted, but they continue to consume Autumn storage on the host. Contact the Stoat instance admin if cleanup is critical. The rollback confirmation summary reports the orphan count.
Editing categories during rollback may overwrite your changes The final category cleanup PATCH is last-write-wins. Rollback fetches the current categories array, filters out Ferry-owned entries, and PATCHes the remainder back. Any category edits you make between rollback's fetch and PATCH are overwritten. The window is typically a few seconds; on large servers it can extend to the duration of the channel + role + emoji deletion phases (~minutes). Don't edit categories while a rollback is running.
Rollback does not delete the Stoat server itself Out of scope per design — Ferry never calls DELETE /servers/{id}. Rollback's job is to remove what Ferry created inside the server, not to delete the user's container. Delete the server manually in Stoat's UI if you want a clean slate.
Rollback cannot run without a state.json The rollback engine reads entity IDs from state.json (channel_map, role_map, emoji_map, category_map). Without it, nothing to delete safely. If you lose state.json but want to clean up, you'll need to identify and delete Ferry-created entities manually.

Verifying a Migration

ferry check confirms a finished migration against the live server. What it proves is narrower than it might appear, and the limits below are deliberate rather than unfinished work.

A gap in the middle of a channel is invisible. Check reads each channel's most recent 100 messages and confirms the last one Ferry recorded is still among them. If messages went missing earlier in the channel while the last one survived, Check reports ok. Read an ok as "the recorded last message is present", never as "this channel is complete".

A channel that gained more than 100 messages since the migration cannot be checked. The window no longer reaches back to the message Ferry recorded, so Check reports unverifiable rather than guessing. This is common on an active server.

A renamed channel or role is only detected for migrations run under 2.17.0 or later. From that release Ferry records the name it gave each channel and role, so ferry check can compare it against the current one and reports a rename as a warning. A migration run by an earlier version recorded no such names, and nothing can recover them after the fact, so a rename there stays invisible. Renamed categories have always been detected, because category names were recorded from the start.

A message accepted as a duplicate cannot be confirmed. When Stoat recognises a retry and refuses it, it does not say which message it already had, so Ferry has no identifier to check later. Channels affected this way report unverifiable.

A channel your token cannot read reports unverifiable. Check can tell this apart from a deleted channel, because the server lists every channel's identifier even when it will not return the channel itself.

A duplicate forum index message is not detected. Ferry records only the most recent index message it posted.

Check refuses to run against a dry run. A dry run records placeholders for channels and messages that were never created, so there is nothing on a server to compare them with.

Repairing a Migration

ferry repair acts on what ferry check reports, so it inherits every limit above. It cannot restore what the check cannot see, and the cases below are deliberate rather than unfinished.

A gap in the middle of a channel cannot be repaired, because it cannot be found. Check confirms the recorded last message is present; it has no way to notice messages missing earlier in the channel, so repair has nothing to act on.

A message accepted as a duplicate cannot be restored. Ferry holds no identifier for it, so neither the check nor the repair can tell whether it is there.

Merged thread content is not restored. Under --thread-strategy=merge a thread's messages were appended to its parent channel, and Ferry finds them by the parent's name rather than its identifier. A repair working from a channel's own export cannot reach them. If repair recreates a parent that absorbed thread content, it restores the parent's own messages and records a warning naming each thread it left behind. flatten, the default, gives every thread its own channel and is restored completely.

A missing forum index channel is declined. Its index message is generated from the forum's posts rather than copied from the export, so there is nothing to re-send. Re-run the migration with --incremental to rebuild it.

A missing custom emoji is declined. An emoji's identifier on Stoat is its uploaded file, so recreating one produces a different emoji that merely looks the same.

A migration run before 2.18.0 may not be repairable. Repair recreates an entity under the name Ferry originally gave it, and that name is recorded only from 2.17.0 onward. Where it is missing, repair declines and says so rather than inventing one: an entity restored under a different name looks repaired and is not.

A recreated role comes back without its colour, rank, hoist setting or icon. Repair restores a role's name and its permissions. The rest is set by separate calls during a migration, and the icon is an uploaded file whose identifier cannot be reused, so repair records a warning naming the role rather than restoring them. Set them by hand, or re-run the migration with --incremental.

Repair never renames anything. A renamed channel, role or category is reported by the check as a warning and left alone, because a rename almost always means you made it.

Repair refuses a rolled-back migration. Rollback deliberately keeps the identifier maps as an audit trail, so a check against that state reports every channel missing. Repair stops rather than rebuilding a server you chose to remove.

See Also