A messages array is easy to produce and unpleasant to read; a transcript is the opposite. Converting between them happens constantly — pasting a log into a bug report, lifting a good exchange back out as a test fixture — and doing it by hand is where the interesting parts get lost. Content is not always a string: tool calls, images, documents and thinking blocks are content blocks, and flattening them to text drops them without a trace. This shows them in brackets and counts them, so you can see what a flattened log left out, and it reads a transcript back into JSON when you need the other direction.
Features
Both directions: messages to transcript, transcript to messages
Tool calls, images and documents shown rather than silently dropped
Counts the non-text blocks a flat transcript would lose
Accepts a bare array or an object with a messages, conversation or history key
Three transcript styles, from Markdown to plain
System messages optional
Reads back both “User:” and “**User**” heading styles
Per-role message counts
How to use the Chat Transcript Converter
Paste the messages JSON
Pick a transcript style
Choose whether to keep the system message and the tool calls
Copy the transcript — or switch direction and go back to JSON
Example
Input
[{"type": "tool_use", "name": "lookup_order"}]
Output
[tool_use: lookup_order]
A plain transcript would show nothing here at all.
Common errors & troubleshooting
The transcript is missing part of a message. — That part was not text. Turn on tool calls to see the non-text blocks, which are counted in the summary either way.
No messages array is found. — A bare array works, as does an object with a messages, conversation, history, turns or chat key. Anything else needs the array pulled out first.
Reading a transcript back loses the roles. — Turns are recognised from lines like “User:” or “**Assistant**”. A transcript with no role markers has nothing to split on.
Round-tripping does not give me back the original. — It gives back the text. Tool calls and images cannot be reconstructed from their bracketed summaries — the transcript is lossy by nature, which is why the count is shown.
Frequently asked questions
Why does content come as blocks rather than a string?
Because a turn can contain more than text — a tool call, an image, a document, a block of reasoning. A string is the simple case, and code that assumes it is always a string is why tool calls vanish from logs.
Which transcript style should I use?
Markdown for anything going into a document or an issue. Labelled for something you might read back in — it round-trips cleanly. Plain when you only want the words.
Can I paste a log from anywhere?
If it is a messages array in JSON, yes. The common wrapper keys are recognised, and unusual roles are kept as they are rather than being coerced.
Does it round-trip?
Text does, exactly. Non-text blocks do not — they are summarised for reading, not preserved for reconstruction, which is the honest limit of any transcript.
What is the count of blocks for?
It tells you how much the transcript is not showing. A conversation with twenty tool calls reads very differently once you know they are there.