It is a tree, not a chat log.

Unzip a ChatGPT export and the file that matters is conversations.json. Open it and you get a wall of text: UUIDs, nested objects, numbers where you expected dates. It looks broken. It is not — it is simply not written for you.

Understanding one thing about its shape explains almost every strange result people get from tools that read it.

The one structural fact


A conversation in this file is not an ordered list of messages. It is a mapping — a bag of nodes, each holding a message, a pointer to its parent, and a list of its children. The conversation you remember having is one path through that structure, and a field called current_node marks where that path ends.

The reason is the edit button. Every time you edited a prompt and regenerated an answer, ChatGPT branched: the old messages stayed exactly where they were and a new branch grew alongside them. The interface showed you one path and quietly kept the rest.

So a conversation you remember as twelve messages long can arrive as thirty nodes, eighteen of which you never chose. Sorted by key, they are indistinguishable from the ones you did.

Why that breaks naive converters


A converter that iterates the mapping and prints what it finds produces a transcript that reads plausibly and is wrong: three abandoned drafts of the same question in a row, answers that respond to prompts you never sent, an argument that does not follow. Nothing errors. Nothing warns you. It just is not the conversation you had.

Reading it correctly means walking backwards from current_node through the parent pointers to reconstruct the path, and then deciding — deliberately — what to do with everything left over.

The fields you will actually meet


  • conversation_id — the id of the conversation, and the only stable way to trace a note back to its source.
  • mapping — the node bag described above. The root node has message: null, which is normal and not corruption.
  • current_node — the leaf of the path that was on screen when the export was taken.
  • create_time and update_time — Unix seconds when present, and frequently `null`. A conversation with no times of its own can usually still be dated from the messages inside it.
  • weight: 0 — marks a message the interface was hiding. It is still a real message and still counts.
  • content_type — text most of the time, but also code, execution_output, multimodal_text, tether_quote, tether_browsing_display, and user_editable_context. New ones appear without notice.
  • asset_pointer — a reference to an uploaded image or file. The ids only partly match the filenames in the ZIP, and the referenced file is routinely not in the export at all.

Numbered files in large exports


Very large accounts do not always get one conversations.json. The export can split across numbered JSON files, which trips up anything looking for a single fixed filename — and it is exactly the accounts with the most to lose that hit it.

The safe approach is to probe every JSON file in the archive for the structure rather than trusting the name on it.

What a trustworthy reader does with all this


There is a short list, and it is mostly about refusing to guess:

  • Resolve the current path from current_node, rather than printing the mapping in whatever order it came in.
  • Count the off-path messages instead of dropping them, and say how many there were.
  • Treat an unfamiliar content_type as a diagnostic to report, not as text to invent. An unknown object rendered as plausible prose is worse than an unknown object reported as unknown.
  • Derive missing conversation dates from the messages inside, and leave a conversation genuinely undated when nothing anywhere carries a time.
  • Never fetch a remote URL to fill in a missing asset, and never emit a link to a file that is not there.

Reading it without reading JSON


You do not have to do any of this by hand. Drop the ZIP into the converter below and it will show you the counts, the dates, the models, and the known issues before you convert anything — free, and without the file leaving your browser.

Next

Related


Questions

4 answered


Why is conversations.json so hard to read?
It stores conversations as a tree of nodes keyed by UUID rather than as ordered message lists, because ChatGPT has to preserve every branch created by editing a prompt.
Why are my create_time values null?
Missing conversation timestamps are common in real exports. The individual messages usually still carry times, so the conversation can be dated from its earliest and latest message rather than left undated.
What is current_node?
A pointer to the last message of the path that was on screen when the export was taken. Following parent pointers back from it reconstructs the conversation you actually had.
Can I just open it in a text editor?
You can open it, but a large export is tens or hundreds of megabytes on one line and most editors struggle. Inspecting it with a tool built for the format is faster and does not require you to read JSON.