How to Extract Only Your Own Messages From a ChatGPT Export
Summary
- A ChatGPT export is a ZIP file that usually contains your conversations in HTML and/or JSON.
- The most reliable way to extract only your messages is to parse the JSON and filter by the “user” role.
- You can do this without coding (using jq) or with a short Python script that outputs clean text or CSV.
- Always run a quick result check: confirm the output contains no “assistant” role content and spot-check a few conversations.
- If you need formatting preserved, export to a structured format (CSV/JSONL) first, then convert to DOCX/Google Docs.
If you have a ChatGPT data export and you only want your messages (not the assistant replies), the key is to work from the export’s structured data (JSON) rather than copying from the HTML pages. The HTML is designed for reading, not clean extraction, so it tends to mix both sides of the conversation and can be painful to clean up.
This guide shows two reproducible methods:
- Fast, no-code-ish: use jq to filter messages where the role is user.
- Most flexible: use a short Python script to output a single text file or a CSV you can open in Excel/Google Sheets.
Note on UI details: the exact export screens and file names can change over time. The steps below focus on what stays consistent: you download a ZIP, extract it, then locate the conversations JSON and filter by message role.
What’s inside a ChatGPT export (and where your messages live)
After you download your ChatGPT export, you’ll have a .zip file. When you unzip it, you’ll usually see a folder with files that may include:
- HTML files (human-readable conversation pages)
- JSON files (structured data that’s best for filtering)
Your goal is to find the file that contains the conversation message objects (commonly named something like conversations.json). In that file, each message is represented with metadata, including a field that indicates who authored it (commonly a role such as user or assistant).
Desktop vs browser: what’s different?
- Browser: you download the ZIP via your browser’s downloads and unzip it using your OS tools.
- ChatGPT desktop app: the export flow still ends with a ZIP you download/open on your computer; the extraction steps are the same once you have the ZIP.
Method 1 (recommended if you want speed): Extract only “user” messages with jq
This method is quick and reproducible if you’re comfortable running a command in Terminal (macOS/Linux) or PowerShell (Windows). It also avoids the formatting mess you get from HTML copy/paste.
Step A: Unzip and locate the conversations JSON
- Unzip the export file.
- Search inside the unzipped folder for a file named something like conversations.json.
If you can’t find it by name, use your OS search for “conversations” or “.json”.
Step B: Install jq (one-time)
- macOS (Homebrew):
brew install jq - Windows: install jq via a package manager (for example, winget/choco) or download jq and add it to PATH.
- Linux: install via your distro package manager (for example,
apt,dnf,pacman).
Installation steps vary by system; the important part is that running jq --version prints a version number.
Step C: Run a filter that outputs only your message text
Because export schemas can vary, you may need to adjust the jq path to the message content. Start by inspecting the JSON structure:
jq 'keys' conversations.json
Then search for “role” fields to confirm how authorship is represented:
jq '.. | objects | select(has("role")) | .role' conversations.json | head
Once you confirm the role values include "user", you can extract user messages. A common pattern is to select objects where .role == "user" and then print a text field. Try this exploratory command first (it may need adjustment):
jq -r '.. | objects
| select(.role? == "user")
| .content? // empty' conversations.json | head -n 40
If you see output that looks like your messages, you can write it to a file:
jq -r '.. | objects
| select(.role? == "user")
| .content? // empty' conversations.json > my_messages.txt
If the output is blank or messy: your export may store message text deeper (for example, arrays of parts). In that case, use jq to print the whole object for one user message and inspect it:
jq '.. | objects | select(.role? == "user")' conversations.json | head -n 80
Look for fields like text, parts, content, or nested structures, then update the extraction path accordingly.
Result check (do this every time)
- Check for assistant leakage: search your output file for common assistant-only phrases you know appear in replies, or search the original JSON for
"assistant"and confirm none of those blocks appear inmy_messages.txt. - Spot-check: pick 2-3 conversations you remember and confirm your messages appear in the right order.
Method 2 (most reliable for clean output): Use Python to export TXT, JSONL, or CSV
If you want a clean file you can reuse in other tools (Docs, Sheets, a writing app), Python gives you more control: you can include timestamps, conversation titles, and keep messages grouped.
Step A: Find the conversations JSON file
As above, unzip your export and locate the JSON file that contains the conversations (often conversations.json).
Step B: Save this script as extract_my_messages.py
This script is designed to be schema-tolerant: it searches for objects with a role field equal to user, then tries several common places where text may live. It outputs:
- my_messages.txt (readable text)
- my_messages.csv (structured rows)
import json
import csv
from pathlib import Path
INPUT = Path("conversations.json")
OUT_TXT = Path("my_messages.txt")
OUT_CSV = Path("my_messages.csv")
def iter_objects(node):
if isinstance(node, dict):
yield node
for v in node.values():
yield from iter_objects(v)
elif isinstance(node, list):
for item in node:
yield from iter_objects(item)
def extract_text(obj):
# Try common patterns without assuming a single schema.
# Return a string or None.
if not isinstance(obj, dict):
return None
# 1) Direct content as string
c = obj.get("content")
if isinstance(c, str) and c.strip():
return c.strip()
# 2) content as dict with text
if isinstance(c, dict):
if isinstance(c.get("text"), str) and c["text"].strip():
return c["text"].strip()
# 3) content parts array
parts = c.get("parts")
if isinstance(parts, list):
texts = [p for p in parts if isinstance(p, str) and p.strip()]
if texts:
return "\n".join(texts).strip()
# 4) Some exports store message text under "text"
t = obj.get("text")
if isinstance(t, str) and t.strip():
return t.strip()
return None
def extract_meta(obj):
# Best-effort metadata; may be missing depending on schema.
# Keep fields optional.
conv_title = obj.get("title")
create_time = obj.get("create_time") or obj.get("created_at") or obj.get("timestamp")
return conv_title, create_time
def main():
data = json.loads(INPUT.read_text(encoding="utf-8"))
rows = []
for obj in iter_objects(data):
role = obj.get("role")
if role != "user":
continue
text = extract_text(obj)
if not text:
continue
title, ts = extract_meta(obj)
rows.append({
"conversation_title": title or "",
"timestamp": ts or "",
"message": text
})
# Write TXT
with OUT_TXT.open("w", encoding="utf-8") as f:
for r in rows:
if r["conversation_title"] or r["timestamp"]:
f.write(f"[{r['conversation_title']} | {r['timestamp']}]\n")
f.write(r["message"].rstrip() + "\n")
f.write("\n---\n\n")
# Write CSV
with OUT_CSV.open("w", encoding="utf-8", newline="") as f:
w = csv.DictWriter(f, fieldnames=["conversation_title", "timestamp", "message"])
w.writeheader()
w.writerows(rows)
print(f"Wrote {len(rows)} messages to {OUT_TXT} and {OUT_CSV}")
if __name__ == "__main__":
main()
Step C: Run it
In the same folder as conversations.json and the script:
python extract_my_messages.py
If your system uses python3:
python3 extract_my_messages.py
Result check (quick and concrete)
- Open my_messages.txt and confirm you only see your writing.
- Open my_messages.csv and filter the “message” column for a phrase you know you wrote.
- If you still see assistant text, your export schema may store role differently (for example, nested under an “author” object). In that case, search the JSON for “assistant” and inspect nearby fields to find the correct role path, then update the script’s role check.
Keeping formatting: what to do if you need paragraphs, lists, and code blocks
Exports can store message content as plain text, multi-part arrays, or structured blocks. If you need formatting preserved:
- Prefer structured extraction first (CSV/JSONL), then convert to your target format.
- Keep separators (like
---) between messages so you can re-split later. - For code blocks: if your messages include triple backticks, keep them in TXT/CSV and only convert to rich text at the end.
Common problems and fixes
You can’t find “conversations.json”
- Search the unzipped folder for .json files and open the largest one in a text editor.
- Use your editor’s search for
"role","assistant", or"user"to confirm you’re in the right file.
Your output is empty
- Your export may not use a top-level
rolefield where you expect it. - Inspect one message object in the JSON and identify where the author role and text actually live, then adjust the jq filter or Python script.
You’re getting duplicates
- Some exports include multiple representations of the same message (for example, a message object plus a rendered block).
- Fix by adding a de-duplication step (for example, track message IDs if present, or de-duplicate identical text lines).
You only want messages from one conversation
Use the CSV output and filter by conversation title (if available), or modify the script to include a conversation ID and then filter on that value.
Optional: Saving your extracted messages for reuse (without mixing in assistant replies)
If your goal is to reuse your own writing (snippets, drafts, prompts you wrote, outlines), it can help to store the extracted output somewhere searchable. One approach is to keep a dedicated folder with dated exports (for example, chatgpt-my-messages-2026-10-17.txt) so you can search across them later.
If you use CopyCharm on Windows, you can also copy sections from my_messages.txt into your normal clipboard workflow and favorite the parts you want to keep handy. If you later want ChatGPT to retrieve specific saved items, that requires eligible account authorization and AI Access sync; ChatGPT can search and retrieve only supported synced data, not unsynced local items.
Frequently Asked Questions
FAQ 1: What file in the ChatGPT export should I use to extract only my messages?
Answer: Use the structured conversations JSON file (commonly named something like conversations.json) rather than the HTML pages. The JSON is where message objects and author roles are represented in a way you can filter reliably.
Takeaway: Start from JSON so you can filter by author role.
FAQ 2: Why is copying from the exported HTML a bad way to isolate my messages?
Answer: The HTML is formatted for reading and usually interleaves both sides of the conversation. When you copy/paste, you tend to bring along assistant text, UI labels, and inconsistent spacing. JSON extraction avoids that by selecting only the message objects authored by you.
Takeaway: HTML is convenient to view, but JSON is cleaner to extract.
FAQ 3: How do I confirm my output contains only “user” messages and no assistant replies?
Answer: Do a two-part check: (1) search your output for phrases you know appear only in assistant replies, and (2) spot-check a few conversations you remember to confirm the extracted text matches what you wrote. If you used jq or Python, also verify your filter condition is strictly selecting the user role.
Takeaway: Always validate with a quick search plus a spot-check.
FAQ 4: Can I extract only my messages from a single conversation instead of the whole export?
Answer: Yes, but you need a way to identify that conversation in the JSON (by title, ID, or a unique snippet of text). A practical approach is to export everything to CSV first, then filter rows by conversation title (if present) or by searching for a distinctive phrase from that conversation.
Takeaway: Extract broadly, then filter down by conversation identifier.
FAQ 5: How do I keep line breaks, lists, and code blocks when extracting my messages?
Answer: Output to TXT, CSV, or JSONL first and avoid rich-text conversion until the end. Preserve separators between messages (like ---) and keep triple backticks intact if your messages include code blocks. After you have clean text, you can convert to DOCX/Google Docs if needed.
Takeaway: Keep it plain-text/structured during extraction; format later.
FAQ 6: What if my export JSON doesn’t use a simple “role”: “user” field?
Answer: Open the JSON and search for assistant to find how the export labels authorship. In some schemas, the role is nested (for example, under an “author” object) or uses different keys. Once you find the correct field path, update your jq filter or Python script to match that structure.
Takeaway: Inspect the schema first, then tailor the filter to the actual role field.
FAQ 7: Can I turn the extracted messages into a spreadsheet I can search and filter?
Answer: Yes. Export to CSV with columns like conversation title, timestamp (if available), and message text. Then open the CSV in Excel or Google Sheets and use filters, search, or pivot tables to review your writing across conversations.
Takeaway: CSV is the easiest bridge to spreadsheet filtering.
FAQ 8: Can CopyCharm help me store and find the extracted messages later?
Answer: If you are on Windows, you can copy excerpts from your extracted file into CopyCharm and then search past clips or favorite important ones. If you want ChatGPT to retrieve specific saved items, that requires eligible account authorization and AI Access sync; ChatGPT can search and retrieve only supported synced data, not unsynced local items.
Takeaway: You can keep your extracted text searchable, and optionally make selected items retrievable in ChatGPT via synced data.
