An export is a tree and not a transcript, so here is our own proposal: count only the chain from current_node back to the root, because everything else in the file is a branch you abandoned. In a real export of 1,463 conversations (916 MB, 15 numbered shards), 74 conversations branch and the longest branch is 561 nodes deep.
A ChatGPT export arrives as a download link by email, and the link expires after 24 hours. Ours was 916 MB and held 1,463 conversations. Conversations are not in one conversations.json. They are sharded into fifteen numbered files, conversations-000.json to conversations-014.json, each a JSON array. Beside them sit chat.html, export_manifest.json (every file with its size), user.json, user_settings.json, library_files.json, and about 834 image files.
Each conversation carries a mapping of nodes. A node has only an id, a message and a parent; there is no list of children. Every conversation has exactly one node whose message is null, and current_node names the last node of the branch you were reading when you exported. Of our 1,463 conversations, 74 branch, and the longest branch was 561 nodes deep. Every time you edit a prompt or regenerate an answer, the old turn stays in the file as a sibling branch.
Walk from current_node up through parent to the root, reverse it, and that is the conversation you remember. Anything else in mapping is a discarded draft. A tool that reads every node will import turns you replaced and retell them as things you said. The same walk is the right way to answer "how many messages do I have", and the count will be lower than the raw node count whenever a conversation branches.
| Field | What we found |
|---|---|
| Roles | Only user and assistant. No system or tool roles, no author names. |
| Content types | text (17,753), thoughts (1,669), reasoning_recap (1,323), multimodal_text (346) |
| Parts | Strings, or dicts such as an image asset pointer (373 of them) |
| Flags per conversation | is_archived, is_starred, is_study_mode, is_read_only, is_do_not_remember, memory_scope, default_model_slug |
Only the text records are what you typed or were told. The thoughts and recaps are the model's reasoning scaffolding, and you probably do not want them in a memory store.
One of our conversations had is_do_not_remember set to true. If you move an export into any memory tool, skip those conversations. They are marked that way because you asked ChatGPT not to remember them.
Once you can read the chain, you can put it somewhere your other assistants can reach. The three formats compared shows how this layout differs from Claude and Grok.