* feat(export): batch export in 100-conv waves and date-range filter Closes #347 (feature request on upstream pionxzh/chatgpt-exporter). Changes: - src/constants.ts: add EXPORT_OPERATION_BATCH = 100 (fixed, non-tunable) - src/utils/download.ts: add PartInfo type, part-suffix support to buildZipFileName, and new buildJsonBatchFileName for official JSON - src/exporter/{html,json,markdown}.ts: accept optional partIndex / totalParts params; each exportAllTo* uses PartInfo to suffix its output filename when multiple batches are produced - src/ui/ExportDialog.tsx: - Remove hardcoded EXPORT_LIMIT = 100 selection cap; users may now select all loaded conversations (up to exportAllLimit from settings) - Add DateFilter component with from/to date inputs that filter the conversation list by create_time (inclusive, end-of-day for dateTo) - Wave-based export pipeline: splits selected conversations into chunks of EXPORT_OPERATION_BATCH (100), fetches each chunk via RequestQueue, downloads the completed batch, pauses 400 ms, then continues with the next chunk — keeping memory bounded and API pressure low - Progress bar now shows global count across all batches plus "Batch X/N" indicator when multiple batches are active - Local-file source also batched in 100-conversation waves with the same 400 ms inter-batch delay - Shift-click range selection and Select All no longer impose a hard cap (handled by the batch pipeline instead) - src/locales/en.json: add Date Filter Label/Hint, Selected count, Export batch info, Exporting batch strings; update Export All Limit Description to mention 100-conversation waves Made-with: Cursor * fix(export): correct date filter for ISO string timestamps + add update_time field The list endpoint returns create_time/update_time as ISO 8601 strings (e.g. "2025-07-10T14:53:58.103234Z"), not Unix numbers. The previous filter compared a string to a number, always producing NaN comparisons and filtering out every conversation. - Add toMs() helper that handles both ISO strings and legacy Unix numbers - Fix filter comparisons to use toMs() and compare in milliseconds - Update ApiConversationItem type: create_time is number|string, update_time is optional number|string - Add filterField state ('create_time' | 'update_time') with a Created/Updated selector in the DateFilter UI so users can filter by either timestamp - Add i18n strings for the new selector labels Made-with: Cursor * feat: redesign date filter layout with presets, auto-load conversations, batch label on export - Date filter redesigned to two-row layout: field selector + quick preset buttons (7d / 30d / 90d / This year / Clear) on row 1; From–To date inputs on row 2, eliminating the wrapping issue - Auto-load all conversations when dialog opens (default project null instead of undefined) so search box, Last 100 button, and counter are immediately visible without needing to manually pick a project - Export button shows batch count when selection > 100, e.g. "Export · 3 files" with a tooltip explaining "3 separate downloads, 100 conversations each" - Progress display shows "Batch X/Y · completed/total" for multi-batch exports - Remove redundant "Export from API" static text (project selector implies it) - Add locale strings: Date Preset 7d/30d/90d/Year, Clear filter, Batch progress Made-with: Cursor * fix: show batch download count inline below export button Made-with: Cursor * feat: filter chips, date column, All conversations, starred indicator - Filter chips via # trigger: type # in search box to open a popover with available filters; click to add as a removable chip: ★ Starred only | 💾 Saved only (skip temp) | ⏱ Long conversation (active ≥ N days, editable inline) | 🤖 GPT/custom AI | 💬 Regular chats - Date column: each conversation row now shows a compact relative date (Today / Yesterday / Xd ago / Mon DD / MMM YYYY) on the right edge - Starred indicator: ★ shown inline for starred conversations - Project dropdown: new "📂 All conversations" option fetches the main list plus every project's conversations, deduplicated; uses new fetchAllConversationsAll() API function - ApiConversationItem: add is_starred, is_temporary_chat, gizmo_id fields from the list endpoint payload - CSS: SelectItem flex layout, new SelectChip / SelectChips / SelectFilterPopover / SelectItemMeta classes Made-with: Cursor * feat: add Load More button to fetch conversations beyond the initial limit - Export fetchConversationsPage from api.ts for single-page incremental loading - Add onHasMore callback to fetchAllConversations so callers know when the fetch was stopped by the user-configured limit (vs the API having no more data) - DialogContent tracks hasMore / totalAvailable / loadingMore state - A "Load N more" button appears below the conversation list when more pages exist; shows "N remaining" count once the API total is known - Resets cleanly on project change Made-with: Cursor * feat: comprehensive filter chip system with chat class, origin, status, and mode logic - Add conversation_origin, pinned_time, is_archived, is_do_not_remember to ApiConversationItem - Replace simple chip flags with typed FilterChipDef union: chat_class (Regular/GPT/Project), origin (dynamic from data), status (starred/temporary/pinned), duration_gte, recency_lte - Each chip has an include/exclude mode toggle (click badge to flip) - AND/OR logic toggle appears in chip bar when 2+ chips are active - Text search supports * and ? wildcards alongside chips - Picker grouped by category with dynamic Origin values from loaded conversations - Project classification cross-references fetched project IDs against gizmo_id - Remove ProjectSelect dropdown: always auto-load all conversations on open - Chat class chip: Regular (no gizmo), GPT (store gizmo), Project (matched gizmo) Made-with: Cursor * refactor(filters): redesign chips as multi-select with two-stage picker - Chat class, Origin, Project, Status are now single chips that open a sub-view where users tick multiple values (Regular + GPT, etc.) - New Project chip lets users pick specific project(s) by name - Date filter moved from top-of-dialog into the chip bar as a full-width date-range chip (Created/Updated selector + date inputs + presets) - Picker uses two-stage UX: root list → sub-view with checkboxes + Apply - Clicking a chip's value label reopens the picker for in-place editing - AND/OR logic toggle only appears for 2+ non-date chips - Remove standalone DateFilter component and related state from DialogContent Made-with: Cursor * fix(filters): guard gizmo_id null check for project chip Made-with: Cursor * fix: prevent export dialog close mid-run and add resume-from-offset control - Block ESC, outside-click, and X button while an export is in progress; module-level exportingRef bridges DialogContent to ExportDialog without lifting state. - Stale-fetch prevention: fetchGenRef generation counter discards callbacks from a previous dialog mount after remount, eliminating the console errors seen when reopening the dialog mid-run. - Cleanup on unmount: clear all three RequestQueues when DialogContent unmounts so orphaned background work stops immediately. - Add "→ 100 from #N" resume control in the toolbar: a number input sets the starting offset and the arrow button selects the next 100 convs from that position, letting users continue a partial export (e.g. set offset to 200 to resume after 2 finished batches). Made-with: Cursor * perf: use Web Worker for sleep() to bypass background-tab timer throttle Chrome throttles setTimeout in non-focused tabs to >=1 s minimum, which causes the export queue (200 ms between requests * 100 convs = 20 s/batch) to stall entirely when the user switches away from the tab. Fix: run all timers inside a single persistent Web Worker. Workers are exempt from background-tab throttling, so exports run at full speed regardless of tab focus. A graceful fallback to regular setTimeout is retained for environments where blob-URL Worker creation fails (CSP etc.) so the change is entirely non-breaking. Made-with: Cursor * fix: proper 429 backoff — wait Retry-After secs, cap retries, skip when exhausted Previously the queue retried forever with a max backoff of 1600 ms, causing the 429 storm seen in the console (the same request hammering the API continuously with no meaningful pause). Changes: - api.ts: export RateLimitError with retryAfterMs read from Retry-After header (defaults to 30 s when header is absent or unparseable) - queue.ts: on RateLimitError, sleep max(retryAfterMs, 30 s) before retrying; give up after 8 consecutive 429s and skip the request; on regular errors give up after 5 retries and skip; add rate_limited status so the UI can show what is happening - ExportDialog.tsx: show "Rate limited — waiting Xs…" with an amber progress bar when status is rate_limited; clear progress bar to blue once the pause ends Made-with: Cursor * fix: global queue pause on 429 instead of per-item backoff The previous design retried each item individually with a 30s wait, so if all 100 conversations in a batch were rate-limited the theoretical worst case was 100 x 8 x 30s = 24,000s. New design: - On the first 429 the entire queue is frozen (pauseUntil timestamp). Every subsequent process() call checks pauseUntil and waits out the remaining time before making any new request — one shared wait for all queued items, not N separate waits. - The pause length is max(Retry-After header, 60s * pauseCount) so it backs off exponentially if the first pause was not long enough: pause 1 = 60s, pause 2 = 120s, pause 3 = 180s, etc. - After MAX_GLOBAL_PAUSES (5) the queue aborts rather than looping forever (total max wait ~15 min before giving up). - The failed item is always returned to the front of the queue to be retried after the pause clears rather than being counted as a skip. - Removed per-item rateRetries counter (no longer needed). Made-with: Cursor * feat: add Test API button and rate-limit header logging - probeApi(): make one minimal request (1 conversation) to check whether the API is currently accepting traffic. Returns ok/rate_limited status plus any X-RateLimit-* or Retry-After headers in the response. - fetchApi(): logs all known rate-limit response headers to console on every call (success or failure) so we can discover which headers ChatGPT actually sends. - ExportDialog: "Test API" button next to the upload icon. Shows "Testing... / API ready / Rate limited · wait Xs / Error" inline. Hovering the button after a successful test shows any rate-limit headers as a tooltip (useful for debugging). Made-with: Cursor * fix: add Cancel button during export; replace useless dimmed X Previously the X button was grayed out and non-functional while an export was running, with no way to stop it short of refreshing the page. Changes: - Cancel button (red, top-right) appears while processing. Clicking it sets cancelledRef, calls queue.stop() on all three queues, and lets the in-flight request finish before cleanly stopping the loop. - The on('done') handler checks cancelledRef: if set, it skips the download and the next-batch kickoff and just resets processing state. - cancelledRef is cleared at the start of each new export so back-to-back exports work correctly. - The dimmed X is kept (slightly more transparent) as a visual reminder that the dialog cannot be closed mid-export; its tooltip now says "Click Cancel to stop the export". Made-with: Cursor * feat: sortable column headers, both date columns, unambiguous date format Problems fixed: - Date column only showed create_time with no label — confusing when the user had filtered or sorted by update_time. - Dates < 1 year omitted the year ("Jun 17" vs "Jun 17, 2025"), making it impossible to tell which year a conversation belonged to. - No way to sort the list — order was whatever the API returned. Changes: - formatConvDate: always includes the year (removes the < 365-day branch). Still shows "Today" / "Yesterday" for very recent items. - ConversationSelect: two date columns (Created, Updated). The active-sort column is visually highlighted (bold, blue) so the user always knows what order the list is in. - Sortable header row (SelectListHeader): click Title / Created / Updated to sort; click again to toggle asc/desc. Arrow indicator (↑/↓/↕) shows current state. Defaults to Created ↓ (newest first) matching the original API order. - Sorting applied in the filtered memo after chip/text filtering. Made-with: Cursor --------- Co-authored-by: Pionxzh <spigbbbbb@gmail.com>
ChatGPT Exporter
A GreasyFork script to export the chat history of ChatGPT
English | Français | Indonesia | 한국어 | Türkçe
Install
Prerequisites
Install Tampermonkey
UserScript
| Greasyfork | GitHub |
|---|---|
📚 Supported Formats | 💡 Example | 📤 Export Multiple Conversations | 🤝 Contribution | ⭐ Star History
📚 Supported Formats
💡 Example
Text
You:
I'm creating a ChatGPT Exporter. What do you think?
ChatGPT:
It sounds like you're planning on creating a tool that uses the ChatGPT model
to export text. ChatGPT is a large language model trained by OpenAI that is
designed to generate human-like text responses based on a given input. It can
be used for a variety of applications, such as chatbots, automated responses
to customer inquiries, and more.
However, please keep in mind that as a large language model, ChatGPT has not
been specifically trained for any specific task, so the quality of the
generated text will depend on how it is used and the context in which it is
applied. It's important to use ChatGPT responsibly and consider the potential
consequences of using it in any given situation.
HTML
Markdown
---
title: ChatGPT Exporter Creation
source: https://chat.openai.com/c/cf3f8850-1d69-43c8-b99b-affd0de4e76f
author: ChatGPT
---
# ChatGPT Exporter Creation
#### You:
I'm creating a ChatGPT Exporter. What do you think?
#### ChatGPT:
It sounds like you're planning on creating a tool that uses the ChatGPT model to export text. ChatGPT is a large language model trained by OpenAI that is designed to generate human-like text responses based on a given input. It can be used for a variety of applications, such as chatbots, automated responses to customer inquiries, and more.
Screenshot
JSON
the raw content from API https://chat.openai.com/backend-api/conversation/[id]
Click to see
{
"id": "35a1fa05-e928-4c39-8ffa-ca74f75b509f",
"title": "AI Turing Test.",
"create_time": 1678015311.655875,
"mapping": {
"5c48fa3e-e4ee-4d00-aa66-8fbcb671a358": {
"id": "5c48fa3e-e4ee-4d00-aa66-8fbcb671a358",
"message": {
"id": "5c48fa3e-e4ee-4d00-aa66-8fbcb671a358",
"author": {
"role": "system",
"metadata": {}
},
"create_time": 1678015311.655875,
"content": {
"content_type": "text",
"parts": [
""
]
},
"end_turn": true,
"weight": 1,
"metadata": {},
"recipient": "all"
},
"parent": "9310b90f-d8f0-4be6-bac2-daacddac784f",
"children": [
"4afb9720-3a88-49b1-9309-e2b53d607f34"
]
},
"9310b90f-d8f0-4be6-bac2-daacddac784f": {
"id": "9310b90f-d8f0-4be6-bac2-daacddac784f",
"children": [
"5c48fa3e-e4ee-4d00-aa66-8fbcb671a358"
]
},
"4afb9720-3a88-49b1-9309-e2b53d607f34": {
"id": "4afb9720-3a88-49b1-9309-e2b53d607f34",
"message": {
"id": "4afb9720-3a88-49b1-9309-e2b53d607f34",
"author": {
"role": "user",
"metadata": {}
},
"create_time": 1678015311.656259,
"content": {
"content_type": "text",
"parts": [
"write an poem about the Turing test"
]
},
"weight": 1,
"metadata": {
"timestamp_": "absolute"
},
"recipient": "all"
},
"parent": "5c48fa3e-e4ee-4d00-aa66-8fbcb671a358",
"children": [
"7027f1a5-8bd5-4463-9869-8a1d1db2f9a1"
]
},
"7027f1a5-8bd5-4463-9869-8a1d1db2f9a1": {
"id": "7027f1a5-8bd5-4463-9869-8a1d1db2f9a1",
"message": {
"id": "7027f1a5-8bd5-4463-9869-8a1d1db2f9a1",
"author": {
"role": "assistant",
"metadata": {}
},
"create_time": 1678185638.890551,
"content": {
"content_type": "text",
"parts": [
"In the realm of machines and code,\nThere lies a test, with rules bestowed,\nA challenge to determine true mind,\nAnd if a machine's intelligence can find.\n\nThe Turing test, named for its creator,\nSeeks to find if a machine's behavior\nCan mimic that of a human so well,\nThat it's indistinguishable, can you tell?\n\nThrough conversations, the test is done,\nWith a judge and a machine, the game is run,\nIf the judge can't tell who is who,\nThen the machine's intelligence shines through.\n\nBut can a machine truly think?\nOr is it just a clever bit of link,\nBetween algorithms and programming code,\nCan it truly understand and decode?\n\nThe Turing test, a thought experiment,\nBrings up questions, doubts, and sentiment,\nFor as we strive to create machines smart,\nWe wonder, will they ever truly have heart?\n\nSo let us ponder and explore,\nAs we create machines more and more,\nThe Turing test a reminder to be,\nMindful of what our machines can truly see."
]
},
"end_turn": false,
"weight": 1,
"metadata": {
"model_slug": "text-davinci-002-render-sha",
"finish_details": {
"type": "stop"
},
"timestamp_": "absolute"
},
"recipient": "all"
},
"parent": "4afb9720-3a88-49b1-9309-e2b53d607f34",
"children": []
}
},
"moderation_results": [],
"current_node": "7027f1a5-8bd5-4463-9869-8a1d1db2f9a1"
}
📤 Export Multiple Conversations
When you click the "Export All" button, the Export Conversations dialog pops up. Here are the functions you can access.
Export from official export file (conversations.json)
Click the upload icon button to upload a JSON file of conversations, such as one downloaded from OpenAI.
Export from API
In the list of all your conversations, select which conversations you want to export. Check the "Select All" checkbox to export all your conversations.
Select your export format from the dropdown on the bottom left. You can choose from the following formats.
- Markdown
- HTML
- JSON
- JSON (ZIP)
Click the button to perform the action you want.
- Archive - Archived chat sessions will disappear from the sidebar and can be managed in ChatGPT settings. See #199 for more details.
- Delete - Deletes the selected conversations.
- Export - Exports the selected conversations in the format chosen using the format selector.
🤝 Contribution
See CONTRIBUTING.md


