What you actually said - messages and DMs in your Instagram export

Your Instagram export splits conversations into thread folders, and large threads are sharded across message_1.json, message_2.json and so on. This is how DMs, group chats and owned broadcast channels are reconstructed, and why unsent messages, media-in-thread and reactions can look odd if you read only one file.

You cannot read a thread by reading a single message_*.json file. Instagram shards one conversation across message_1.json, message_2.json, ... inside a thread folder. The parser merges shards by conversation_id when present, and derives the thread key from the folder if it is missing so statistics do not fragment.

Where messages live

Under messages/inbox/ there are per-thread folders. Inside, each shard is usually message_1.json (and sometimes message_2.json, message_3.json etc). HTML variants may also be present.

Sharding: why message_1 and message_2 must be merged

If you read only message_1.json you will undercount messages for that conversation. The shared engine accepts any message_\d+\.json in the inbox and merges them into the same conversation: if conversation.conversation_id exists, it uses that; otherwise it derives a thread id from the path up to the shard so shards do not create duplicate conversation entries.

DMs vs groups: a strict participant rule

Classification is strictly by distinct participants. After excluding the self account (case-insensitively, collapsing placeholders), a thread with zero others becomes a group. If there is at least one other, a 1:1 (DM) requires the number of others to be <= 1 after accounting for selfFound. Anything with more distinct others is a group. This deliberately does not rely on a fragile owner name.

Owned broadcast channels

LMKFR only flags a thread as isChannel if: there are >= 2 others, the thread is one-way from you (you post, nobody else posts) and you actually sent at least one message. Subscribed channels you do not own, and one-sided DMs, are not flagged as channels - they merge into ordinary group chats.

Unsent messages

When msg.is_unsent is present, the message type becomes unsent and isUnsent is true. The rendered content becomes "Message unsent" so the presence is visible without inventing its text.

Media, shares, reactions

Message types are detected from the JSON shape (stickers, GIFs, audio_files/voice, photos, videos, clips, shares for reels/stories/posts, text). Reactions are normalized (NFC, U+FE0F variation selector stripped) so visually identical emojis group together; counts and actor lists aggregate. Share URLs are extracted when present.

HTML fallback

The HTML parser (message_\d+\.html) reconstructs a minimal conversation structure. It derives thread id from the folder and extracts sender/timestamp and text/type; some fields (precise share URLs/reactions) may be simpler than the JSON path.

How to read it honestly

  • Merge, do not cherry-pick. If a thread has message_2.json, include it.
  • DM/group classification comes from participant distinctness, not folder title.
  • Channel flag requires you to be the sole sender to a >=2-audience.
  • An "unsent" row means it was removed before download - there is no reconstructed body.
Why does a message show the sender as 'Instagram user'?

It is the label left behind by a deleted account. When the account that sent a DM was
removed, Instagram replaced that account's name in the record with a placeholder. The
message body, timestamp and thread are all still there — only the sender identity is
gone. We render the exact label the record carries rather than inventing the name,
because the record no longer holds it.

Why do I only see part of a long DM when I open one file?

Because the conversation is sharded across message_1.json, message_2.json, etc. All shards for that thread must be merged to get the full count.

How does the app tell DM from group chat?

It counts distinct participants excluding yourself. If there is more than 1 other, it is a group (DM only when others <= 1 after accounting for self). Casing drift is tolerated.

What are unsent messages?

Messages recorded as unsent. The export keeps is_unsent; the text is not reliably recoverable, so it is rendered as "Message unsent".

Are owned broadcast channels always flagged?

Only when you are the sole sender to >= 2 others and sent at least once. One-sided DMs and channels you subscribe to are not flagged as channels.

Sources of truth

  • packages/shared/src/types/messages.ts - MessageItem (isUnsent, shareUrl, reactions), Conversation (isChannel, channelOwner, type, participants, messages), MessagesData.
  • packages/shared/src/parsers/messages.ts - shard detection (message_\d+\.json/.html), thread id derivation (uses conversation_id if present, else path up to shard), DM/group classification, owned channel detection, type detection and normalization including is_unsent -> type unsent, groupReactions (NFC + U+FE0F stripping).
What you actually said - messages and DMs in your Instagram export — LMKFR Blog