conversations.json is the file that holds your chat history when you request your data from ChatGPT or Claude. It is not meant for reading: it is one long line of JSON, and in ChatGPT’s version the messages are stored as a tree, not a list. This guide covers how to request the export, what is inside the zip, how the structure works, and four ways to turn it into files you can read.
The short route: drop the zip or the JSON into our free converter. It runs in your browser, uploads nothing, and gives you Markdown, Word or HTML files, one per conversation. The rest of this page is for when you want to understand the file or do it yourself.
OpenAI’s help article gives two routes. The one most people want is in settings:
The other route is OpenAI’s Privacy Portal: Make a Privacy Request, I have a consumer ChatGPT account, Download my data.
Self-service export is available for ChatGPT Free, Go, Plus, Pro and eligible Edu workspaces. It is not available in Business, Enterprise or ChatGPT for Healthcare workspaces; ask your workspace owner. If you plan to move a personal workspace into a managed one, export first, because after the personal workspace is removed you may no longer be able to request another export.
Anthropic’s help article says:
The iOS and Android apps cannot start an export. Free, Pro and Max users export their own data; on Team and Enterprise plans only the organization’s Primary Owner can. Exports cannot be imported into another personal Claude account.
OpenAI and Anthropic do not publish a specification for these files, so what follows comes from real exports and community write-ups, and either company can change it. Check your own zip.
ChatGPT zips typically contain:
conversations.json, every conversation. Large accounts are often split into numbered files such as conversations-000.json, conversations-001.json.chat.html, the same history as one long web page you can open in a browser. It has no per-chat files or search.user.json and message_feedback.json, and the images and audio you uploaded or generated. The exact set varies by account and date.Claude zips typically contain conversations.json, projects.json and users.json.
The file is a JSON array. Each element is one conversation with fields such as:
{
"title": "Sorting dicts in Python",
"create_time": 1760000000.0,
"update_time": 1760000300.0,
"current_node": "a_new",
"mapping": { "...": "..." }
}
(create_time is a Unix timestamp in seconds.)
mapping is an object keyed by node ID. Each node looks like this (trimmed):
"u_new": {
"id": "u_new",
"parent": "sys",
"children": ["a_new"],
"message": {
"author": { "role": "user" },
"create_time": 1760000100.0,
"content": { "content_type": "text", "parts": ["How do I sort a list of dicts by key?"] },
"metadata": {}
}
}
parent and children link nodes into a tree. The first node of a conversation has no parent, and its message is often null.message.author.role is user, assistant, system or tool.message.content.parts holds the text. Plain chat has strings; other content types (images, code execution, reasoning summaries, search) have other shapes.metadata can mark hidden messages, such as custom-instruction text injected for the model.ChatGPT never deletes a message when you edit a prompt or press regenerate. The new version becomes a second child of the same parent node, and the old version stays in the tree. So a node can have two or more children, and mapping holds every draft you ever made, not just the chat you remember.
current_node is the ID of the last message on the branch that was on screen when the export was made. To rebuild the visible thread, start at current_node, follow parent up to the root, and reverse the list. Reading mapping in dictionary order, or taking every message, mixes old drafts into the conversation.
Claude’s conversations.json is also an array. Each conversation has uuid, name, created_at, updated_at and a chat_messages list. Each message has a sender (human or assistant), a created_at, and its text either in a text field or as typed blocks in a content list. Attachments appear under attachments (with extracted text) and files, and artifacts appear as tool-use blocks. There is no tree to walk. Edited-message branches are not represented the way ChatGPT’s are, so you generally see one linear thread.
Open the converter, choose the zip or the JSON, search your conversations, tick the ones you want and download .md, .docx or .html files, a zip of them, or one combined Markdown file. It follows current_node, so you get the branch you saw, and it keeps code blocks with their language, tables and lists. Hidden system messages and tool output are skipped, and images and attachments become placeholders such as [Image attachment]. It streams the file, so a 100 MB or larger export does not freeze the page. Disclosure: Tidyleaf is us. The tool is free and independent of OpenAI and Anthropic.
This script needs only Python 3. Put it next to your unzipped conversations*.json files and run it. It writes one Markdown file per conversation into a chats folder, following the active branch.
import glob, json, pathlib, re
out = pathlib.Path("chats")
out.mkdir(exist_ok=True)
for path in sorted(glob.glob("conversations*.json")):
for conv in json.load(open(path, encoding="utf-8")):
# Walk from the last node of the visible branch up to the root.
chain, node_id = [], conv.get("current_node")
while node_id:
node = conv["mapping"][node_id]
chain.append(node)
node_id = node.get("parent")
blocks = []
for node in reversed(chain):
msg = node.get("message")
if not msg or msg["author"]["role"] not in ("user", "assistant"):
continue
if (msg.get("metadata") or {}).get("is_visually_hidden_from_conversation"):
continue
parts = (msg.get("content") or {}).get("parts") or []
text = "\n".join(p for p in parts if isinstance(p, str)).strip()
if text:
blocks.append(f"## {msg['author']['role'].title()}\n\n{text}\n")
title = conv.get("title") or "Untitled"
name = re.sub(r'[\\/:*?"<>|]+', "-", title).strip()[:80] or "Untitled"
(out / f"{name}.md").write_text(f"# {title}\n\n" + "\n".join(blocks), encoding="utf-8")
Known limits: it keeps only plain text parts, so images, code-interpreter output and reasoning summaries are dropped; ChatGPT citation markers can survive as odd private-use characters in the text; and two chats with the same title overwrite each other, so add the conversation’s ID or date to the file name if that matters. For very large files, json.load needs memory of several times the file size; the third-party ijson package can stream the array instead.
To list titles and dates without writing a script, with jq: jq -r '.[] | [.create_time, .title] | @tsv' conversations.json. For Claude, use .created_at and .name. jq reads the whole file into memory, so use --stream or a script for multi-gigabyte exports.
chat.html in the ChatGPT zip opens in any browser and is fine for scrolling a recent chat, but it is one page for everything and cannot be searched per chat or saved as separate files. If you only need a chat you have open right now, you do not need the data export at all: our free AI Chat Exporter extension saves the open ChatGPT, Claude or Gemini conversation as Markdown or PDF in one click (bulk export of every chat is a paid Pro feature; the converter above is free).
current_node as described above.Where is conversations.json after I export my ChatGPT data? In the zip that ChatGPT emails you after you choose Settings, Data controls, Export data. Unzip it and you find conversations.json (large accounts may have several numbered files such as conversations-000.json) next to other files, and often a chat.html page. Claude’s export zip also contains a conversations.json.
How long does the ChatGPT export take, and when does the link expire? OpenAI’s help page says an export can take up to 7 days to arrive and that the download link expires 24 hours after you receive it. Claude’s help page says its link also expires 24 hours after delivery.
Why is conversations.json one huge line and hard to open? The export is compact JSON with no line breaks, and for heavy users it can run to hundreds of megabytes, which text editors struggle with. Use a streaming reader, a script, or a converter that parses it for you.
Why does a ChatGPT conversation contain a mapping instead of a list of messages? Because ChatGPT conversations are trees. Editing a message or regenerating a reply creates a new branch, and the old branch is kept. Each node points to its parent and children, and current_node marks the end of the branch that was on screen.
How do I know which branch I was actually looking at? Start at the conversation’s current_node and follow each node’s parent up to the root, then reverse the list. That path is the thread as it appeared in the app. Other branches in the mapping are older edits or regenerated replies.
Is my data safe if I use an online converter? Only if the converter works locally. Tidyleaf’s converter reads the file in your browser, makes no request carrying your data and has no server, and you can confirm that in your browser’s Network tab. Treat any converter that uploads the file as a transfer of your whole chat history.
Can I import conversations.json into another chat service? Claude’s help page says exported data cannot be imported into another personal Claude account. For reading or searching, convert the file to Markdown or Word. Converting does not recreate chats inside ChatGPT or Claude.
Does the export contain my images, voice chats and canvas documents? Not reliably. Text conversations are in conversations.json, with images and files referenced rather than embedded as readable text. Reasoning summaries, search results, canvas and similar content types have different shapes, and simple scripts often skip them. Tidyleaf’s converter turns images and attachments into placeholders.
Tidyleaf is an independent maker of browser extensions. Not affiliated with, endorsed by or sponsored by OpenAI or Anthropic. ChatGPT and Claude are trademarks of their owners, named only to describe the files this guide explains.