On this page
- Markdown is the best simple human-readable baseline
- JSON is better for machine-readable fidelity
- Export semantic state, not database implementation details
- Stable IDs are useful inside the export
- Export the selected path clearly
- Full-history export needs the graph
- Do not flatten branches into fake chronology
- Tool calls need semantic export
- Tool results can contain sensitive external data
- Historical approval is not future authority
- MCP events may need normalized representation
- Provider-native state is often non-portable
- Separate portable core from extensions
- Credentials must never be part of a normal conversation export
- Connection metadata can be useful without secrets
- Model identity belongs to generation history
- Generation settings can be valuable
- Usage and cost should be optional metadata
- Timing data can be useful but should stay compact
- Reasoning content needs special care
- Attachments need a manifest
- Sanitize exported filenames
- Avoid absolute filesystem paths
- Shared attachments should not be duplicated unnecessarily
- Export extracted text only when it belongs to the product contract
- Search indexes should not be exported as trusted state
- Projects can be referenced or embedded
- Context snapshots can be enormous
- Do not export secret request headers as diagnostics
- A versioned manifest is essential
- Prefer forward-compatible parsing
- Define required semantic invariants
- Export files are untrusted when imported
- Export is not the same as backup
- Plain Markdown should not pretend to preserve everything
- HTML/PDF can be presentation formats, not canonical formats
- Export should work offline for local data
- Missing remote attachments should be explicit
- Deletion after export is outside app control
- Encryption is a separate export option
- Export integrity can use checksums
- Test round-trip semantics
- A useful two-layer export architecture
- Useful invariants
- Where BYOKchat fits
- Further reading
An AI conversation export should answer a simple user expectation:
If I leave this app, can I take my work with me?
That becomes more complicated once chats contain:
multiple providers
reasoning metadata
tool calls
tool results
attachments
branches
projects
usage/cost records
provider-side response IDs
MCP interactions
A useful export system separates two goals:
- human-readable export — easy to open, share, print, or archive;
- structured export — preserves enough semantics for another application or future version to reconstruct the conversation faithfully.
Do not force one format to do both jobs badly.
Markdown is the best simple human-readable baseline
For the currently selected conversation path, Markdown can represent:
# Conversation title
## User
How does SSE work?
## Assistant
Server-Sent Events use a text event stream...
Benefits:
- plain text;
- durable;
- easy to inspect;
- easy to version-control;
- readable without the original app;
- compatible with many editors.
Markdown should be useful even if every provider-specific field is lost.
JSON is better for machine-readable fidelity
A structured export can preserve:
message IDs
parent IDs
roles
content parts
attachment references
generation metadata
provider/model labels
tool calls/results
usage
timing
branch selection
project references
Example shape:
{
"format": "ai-conversation-export",
"version": 1,
"conversation": {
"id": "conv_123",
"title": "SSE debugging",
"current_leaf_id": "msg_9"
},
"messages": []
}
Version the format from day one.
Export semantic state, not database implementation details
Avoid dumping raw persistence rows such as:
SQLite rowid
Core Data object URI
internal table names
migration flags
cache keys
Those are implementation details.
Instead export durable concepts:
conversation
message
attachment
generation
tool execution
project metadata
That makes the archive more portable across future rewrites.
Stable IDs are useful inside the export
A message graph can preserve:
message.id
message.parent_id
These IDs do not need to be globally meaningful outside the export, but they allow branches and tool references to remain intact.
Do not replace them with array positions if relationships matter.
Export the selected path clearly
A branching chat has one active visible path and possibly many hidden sibling branches.
For human-readable Markdown, defaulting to the selected path is intuitive.
The export should say what it contains:
Selected conversation path
rather than silently omitting alternate branches without explanation.
See How AI Chat Branching and Regeneration Should Work.
Full-history export needs the graph
A structured archive can include all branches:
{
"id": "a2b",
"parent_id": "u2",
"role": "assistant"
}
The consumer can reconstruct sibling relationships.
Store the current selected leaf separately.
Do not flatten branches into fake chronology
If two answers are siblings, exporting them as:
Assistant A
Assistant B
User next message
implies both answers were part of the same context.
That is false.
Use explicit branch labels in Markdown if exporting all branches, or reserve full branch fidelity for structured JSON.
Tool calls need semantic export
A tool-enabled turn may contain:
tool name
arguments
approval decision
execution status
result
error
A structured representation can look like:
{
"type": "tool_execution",
"tool_name": "get_weather",
"call_id": "call_17",
"status": "completed",
"arguments": {"city": "Tokyo"},
"result": {"temperature_c": 31}
}
Whether raw arguments/results should be included depends on the export’s purpose and privacy settings.
Tool results can contain sensitive external data
A calendar tool result may contain:
meeting titles
email addresses
private notes
A full conversation export that includes visible tool results may legitimately include that data.
But do not include hidden internal tool payloads merely because they exist in the database.
Export what belongs to the user’s conversation/history contract.
Historical approval is not future authority
If an export records:
Always Allow
or a one-time approval event, importing it later should not automatically grant new permissions.
Treat approval records as history.
Permission state should be restored only through a deliberately designed trusted configuration path.
See How to Build Human Approval Into AI Tool Calls.
MCP events may need normalized representation
Do not require the export consumer to understand every raw protocol frame.
A portable archive can represent semantic events such as:
tool discovery snapshot
tool call
input request
user response
task state transition
Raw frames can be optional diagnostics, not the primary portable format.
Provider-native state is often non-portable
Fields such as:
provider response ID
opaque continuation token
encrypted reasoning state
provider file ID
background job ID
may be useful for same-app recovery but meaningless to another client.
You can include them under a clearly namespaced extension:
{
"provider_extensions": {
"openai": {...}
}
}
but do not make them required to understand the conversation.
Separate portable core from extensions
A clean format can define:
core fields -> provider-neutral and documented
extensions -> optional, namespaced, ignorable
This lets another client import the conversation even if it ignores provider-specific state.
Credentials must never be part of a normal conversation export
Exclude:
API keys
OAuth tokens
refresh tokens
protected custom headers
cookies
authorization headers
client secrets
These are account credentials, not conversation content.
The export should remain safe to move between devices without becoming an authentication bundle.
Connection metadata can be useful without secrets
You may include:
provider type
display name
model ID
base URL when appropriate
But custom URLs can be sensitive too.
A privacy-conscious export mode may omit private hostnames or redact connection details.
See How to Redact Secrets From AI App Logs for the broader principle of minimizing sensitive metadata.
Model identity belongs to generation history
If a chat used:
Turn 1 -> Model A
Turn 2 -> Model B
Turn 3 -> local model
store those actual historical model IDs per generation.
Do not export only the conversation’s current selected model.
Generation settings can be valuable
A technical export may include:
reasoning effort
sampling parameters when used
max output setting
provider/model
context strategy version
These help reproduce experiments.
But do not claim full reproducibility: providers and models can change behavior over time even with identical settings.
Usage and cost should be optional metadata
A structured export can preserve:
input tokens
cached input tokens
output tokens
reasoning tokens when reported
estimated cost at the time
currency/pricing metadata version
Mark estimated cost as an estimate.
Do not recompute historical cost silently from today’s pricing during export.
See How to Estimate Per-Conversation AI Cost.
Timing data can be useful but should stay compact
Possible fields:
started_at
first_event_at
first_text_at
completed_at
finish_status
Raw low-level traces can stay in diagnostics unless the user requests a developer export.
Reasoning content needs special care
Providers differ in what reasoning data is exposed, summarized, encrypted, or unavailable.
A portable export should distinguish:
user-visible reasoning summary
provider-native opaque reasoning state
no reasoning content available
Do not invent hidden chain-of-thought content that the application never received.
Attachments need a manifest
A portable archive can look like:
conversation.json
conversation.md
attachments/
att_1-report.pdf
att_2-image.png
Then the manifest maps:
{
"id": "att_1",
"filename": "report.pdf",
"media_type": "application/pdf",
"path": "attachments/att_1-report.pdf"
}
Use relative paths only.
Sanitize exported filenames
Original filenames may contain:
slashes
reserved characters
control characters
very long names
path traversal sequences
Generate safe archive paths while preserving the original display name as metadata.
Never use an attachment name directly as an extraction destination.
Avoid absolute filesystem paths
Do not export:
/Users/hoang/Documents/report.pdf
as the only attachment reference.
That path is not portable and can leak local directory structure.
Use archive-relative paths and logical IDs.
Shared attachments should not be duplicated unnecessarily
If several messages reference the same local attachment, the archive can include one file object and multiple references.
This preserves identity and reduces export size.
A content hash can optionally help detect accidental duplication, but it should not replace logical identity.
Export extracted text only when it belongs to the product contract
A PDF may have derived:
OCR text
retrieval chunks
embeddings
search index
These are usually rebuildable derived data.
A normal export should include the original file and perhaps a documented extracted-text representation if useful, but not internal vector indexes or embeddings by default.
Search indexes should not be exported as trusted state
Derived indexes are:
implementation-specific
version-specific
rebuildable
potentially stale
Export primary data, then let an importer rebuild search.
See How to Design AI Conversation Search.
Projects can be referenced or embedded
A conversation export may include:
project name
project ID
instruction revision used
A full workspace export can include the project itself.
Keep scopes distinct:
export this conversation
export this project
export all app data
Users should know what they are exporting.
Context snapshots can be enormous
Storing the exact serialized provider request for every generation may duplicate the full conversation repeatedly.
Instead preserve semantic history plus the configuration needed to rebuild context.
Developer/debug exports can optionally include normalized request summaries.
Do not export secret request headers as diagnostics
Even a developer export should redact:
Authorization
X-API-Key
cookies
protected custom headers
If the export contains HTTP traces, make redaction deterministic and testable.
A versioned manifest is essential
Example:
{
"format": "byok-conversation-export",
"version": 2,
"created_at": "2026-09-04T10:30:00+07:00",
"scope": "conversation",
"features": ["branches", "attachments", "tools"]
}
The importer can reject unsupported future versions safely.
Prefer forward-compatible parsing
For optional fields:
unknown field -> ignore/preserve if safe
missing optional field -> default
unknown required feature -> stop with useful error
Do not crash because a future exporter added metadata your current app does not use.
Define required semantic invariants
For example:
message IDs unique within export
parent references resolve
attachment references resolve
current leaf belongs to conversation
tool call references resolve
no absolute paths
no secret credential fields
Validate all of these before import.
Export files are untrusted when imported
A file created by your app can be edited later.
Import should defend against:
malformed JSON
zip bombs
path traversal
huge counts
invalid references
duplicate IDs
unexpected nested data
malicious Markdown/HTML
Use the same mindset as backup restore.
See Backup and Restore Security for Local AI Chats.
Export is not the same as backup
A backup optimizes for restoring the application.
A portable export optimizes for interoperability and user access.
A backup may preserve:
app-specific settings
internal IDs
migration metadata
while a portable export should minimize app-specific coupling.
You can support both.
Plain Markdown should not pretend to preserve everything
A Markdown footer can say:
Exported from BYOKchat.
Provider/model metadata and tool details may be omitted from this human-readable view.
The structured JSON can carry richer details.
Honest lossy export is better than hidden data loss.
HTML/PDF can be presentation formats, not canonical formats
Users may want a printable PDF or styled HTML.
Generate those from semantic conversation data, but do not make them the only export path.
They are harder to re-import and diff.
Export should work offline for local data
If the conversation and attachments are stored locally, export should not require provider connectivity.
Provider-side resources that were never downloaded may be unavailable; label them clearly rather than blocking the whole export.
Missing remote attachments should be explicit
If a historical message references a provider file that no longer exists locally, the export can record:
attachment metadata present
binary unavailable
Do not silently omit the reference, because that changes conversation meaning.
Deletion after export is outside app control
If the user exports a conversation to Files or cloud storage and later deletes the chat in the app, the exported copy remains.
The deletion UI should not imply external exports are removed.
See How to Delete AI App Data Correctly.
Encryption is a separate export option
A user may want encrypted archives for sensitive data.
If supported, use established cryptographic containers/primitives and clear password/key recovery UX.
Do not invent custom encryption casually.
Also keep an interoperable unencrypted format where appropriate.
Export integrity can use checksums
A manifest can include hashes for attachment files:
{
"path": "attachments/att_1-report.pdf",
"sha256": "..."
}
This detects corruption.
It does not prove who created the archive unless the manifest is authenticated separately.
Do not call a checksum a signature.
Test round-trip semantics
Important tests:
simple linear chat
multi-provider chat
branched chat
edited historical message
tool calls and results
partial generation
attachment references
project-linked conversation
non-English content
large code blocks
math/Markdown
unknown optional JSON field
archive with missing attachment
malicious path
export contains sentinel API key -> must fail scan
A round trip should preserve semantic relationships, not byte-for-byte database representation.
A useful two-layer export architecture
The semantic model is the shared boundary.
Useful invariants
credentials never enter normal exports
selected branch is explicit
all message/attachment references resolve
provider-specific extensions are optional
absolute local paths are not exported
human-readable output is generated from semantic state
structured format is versioned
imports treat archives as untrusted
Where BYOKchat fits
A multi-provider local-first client can offer unusually strong portability because it owns the semantic conversation history instead of relying only on one provider’s server-side thread object. Messages, branches, project context, attachments, tool history, and usage can be exported from local state while provider credentials remain in secure storage.
That makes switching apps or archiving work possible without turning a provider-specific response ID into the user’s only copy of the conversation.