ChatGPT JSON Explained: What Is Actually Inside conversations.json

You asked OpenAI for your data, waited for the email, unzipped the archive, and found a file called \conversations.json\. You opened it and got a wall of braces. This is what every part of that file means, and why it is shaped so strangely. What is in the ZIP A ChatGPT export contains a handful of files. The two that m…

You asked OpenAI for your data, waited for the email, unzipped the archive, and found a file called \conversations.json\. You opened it and got a wall of braces. This is what every part of that file means, and why it is shaped so strangely.

What is in the ZIP

A ChatGPT export contains a handful of files. The two that matter are:

Everything else is account metadata, user settings, and any images or files you uploaded. If you want your conversations, \conversations.json\ is the file.

The top level: an array of conversations

The file is a JSON array. Each element is one conversation, and looks roughly like this:

\\\`

{

"title": "Debugging a React render loop",

"create_time": 1738291200.123,

"update_time": 1738294800.456,

"mapping": { ... },

"current_node": "aa11-bb22-cc33",

"conversation_id": "6a2f..."

}

\\\`

title is what you saw in the sidebar. create_time and update_time are Unix timestamps in seconds, which is why they look like meaningless nine-digit numbers rather than dates. Multiply by 1000 to get a JavaScript timestamp.

current_node is the important one, and almost everyone misses it. Hold that thought.

The mapping object is a tree, not a list

Here is the part that trips up every homemade script. \mapping\ is not an ordered list of messages. It is a dictionary keyed by node ID, where each node points at its parent and its children:

\\\`

"aa11-bb22-cc33": {

"id": "aa11-bb22-cc33",

"parent": "99ff-ee88-dd77",

"children": ["44kk-55ll-66mm"],

"message": {

"author": { "role": "assistant" },

"create_time": 1738291260.5,

"content": { "content_type": "text", "parts": ["Here is what is happening..."] }

}

}

\\\`

The conversation is stored as a linked structure. To read it, you start at \current_node\, follow \parent\ pointers all the way back to the root, then reverse the list. That is the thread you actually saw in the app.

Why the tree exists: branches

Every time you regenerate an answer, or edit a prompt and resend it, ChatGPT does not overwrite the old message. It adds a sibling branch to the tree. The old version stays in the file forever.

This means a single conversation node can have three or four children, each the start of a different version of the rest of the conversation. Your export therefore contains messages you never chose, answers you rejected, and prompts you rewrote.

That is the single biggest reason a quick script produces garbage. If you iterate over \mapping\ and print every message you find, you get:

The fix is to walk the active branch from \current_node\ backwards, not to loop over the dictionary.

Nodes without messages

Some nodes have \"message": null\. These are structural roots that exist to anchor the tree. Skip them.

Others have a message whose \author.role\ is \system\ or \tool\. System nodes carry hidden instructions and custom instruction context. Tool nodes carry web search results, code interpreter output, and image generation calls. Whether you want these depends on what you are doing: for a readable transcript, drop them; for a full forensic record, keep them.

Content parts and content types

\content.parts\ is an array, not a string. For ordinary text messages it holds a single string, and you can join the array. But \content_type\ varies:

A converter that assumes \parts[0]\ is always a string will crash or silently drop content on any conversation that used images, files, or code interpreter.

Metadata worth knowing about

Inside \message.metadata\ you will sometimes find:

Why chat.html is not the easy way out

It is tempting to skip the JSON and just open \chat.html\. The problem is that it is one file containing every conversation you have ever had, with no per-chat separation, no working search, and no way to extract a single thread. For anything past a quick glance, it is worse than the JSON.

Turning it into something readable

You have three realistic options.

Write a script. Entirely doable if you are comfortable with Python or Node. Budget an afternoon, because the branch resolution and content types are where the time goes, and plan to revisit it when OpenAI changes the format, which happens.

Use a browser extension. Fine for saving one conversation as you read it. Not useful for an export you have already downloaded, and extensions with access to your chat pages are worth thinking twice about.

Use a converter. ChatExports parses the tree correctly, follows the active branch, handles every content type, and outputs Word, PDF, Markdown, or CSV. It runs entirely in your browser, so the file never leaves your device. If you just want to read and search the file first, the ChatGPT JSON viewer does that without exporting anything.

The short version

\conversations.json\ is an array of conversations, each containing a tree of message nodes. Read it by starting at \current_node\ and walking parents backwards. Expect branches from regenerated answers, null-message root nodes, hidden system and tool messages, and content parts that are not always plain strings. Handle those five things and the file makes complete sense.

Related reading