BYOKchat Blog

How to Export AI Conversations Portably

Design portable AI conversation exports with Markdown, JSON, attachments, branches, tool events, metadata, usage, and versioning without leaking credentials or locking history to one provider.

· 8 min read

On this page
  1. Markdown is the best simple human-readable baseline
  2. JSON is better for machine-readable fidelity
  3. Export semantic state, not database implementation details
  4. Stable IDs are useful inside the export
  5. Export the selected path clearly
  6. Full-history export needs the graph
  7. Do not flatten branches into fake chronology
  8. Tool calls need semantic export
  9. Tool results can contain sensitive external data
  10. Historical approval is not future authority
  11. MCP events may need normalized representation
  12. Provider-native state is often non-portable
  13. Separate portable core from extensions
  14. Credentials must never be part of a normal conversation export
  15. Connection metadata can be useful without secrets
  16. Model identity belongs to generation history
  17. Generation settings can be valuable
  18. Usage and cost should be optional metadata
  19. Timing data can be useful but should stay compact
  20. Reasoning content needs special care
  21. Attachments need a manifest
  22. Sanitize exported filenames
  23. Avoid absolute filesystem paths
  24. Shared attachments should not be duplicated unnecessarily
  25. Export extracted text only when it belongs to the product contract
  26. Search indexes should not be exported as trusted state
  27. Projects can be referenced or embedded
  28. Context snapshots can be enormous
  29. Do not export secret request headers as diagnostics
  30. A versioned manifest is essential
  31. Prefer forward-compatible parsing
  32. Define required semantic invariants
  33. Export files are untrusted when imported
  34. Export is not the same as backup
  35. Plain Markdown should not pretend to preserve everything
  36. HTML/PDF can be presentation formats, not canonical formats
  37. Export should work offline for local data
  38. Missing remote attachments should be explicit
  39. Deletion after export is outside app control
  40. Encryption is a separate export option
  41. Export integrity can use checksums
  42. Test round-trip semantics
  43. A useful two-layer export architecture
  44. Useful invariants
  45. Where BYOKchat fits
  46. 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:

  1. human-readable export — easy to open, share, print, or archive;
  2. 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

Diagram illustrating the surrounding section

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.

Further reading

Keep reading