<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ThatToolSite</title>
    <description>The latest articles on DEV Community by ThatToolSite (@thattoolsite).</description>
    <link>https://dev.to/thattoolsite</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4176101%2F940957a8-0a97-42b6-a4c0-f19c8e50428b.png</url>
      <title>DEV Community: ThatToolSite</title>
      <link>https://dev.to/thattoolsite</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thattoolsite"/>
    <language>en</language>
    <item>
      <title>How to Save a Whole ChatGPT or Claude Chat as a PDF</title>
      <dc:creator>ThatToolSite</dc:creator>
      <pubDate>Wed, 21 Oct 2026 16:08:44 +0000</pubDate>
      <link>https://dev.to/thattoolsite/how-to-save-a-whole-chatgpt-or-claude-chat-as-a-pdf-27bj</link>
      <guid>https://dev.to/thattoolsite/how-to-save-a-whole-chatgpt-or-claude-chat-as-a-pdf-27bj</guid>
      <description>&lt;p&gt;Neither app has a download-as-PDF button for a conversation. Printing the page is quick but messy; your data export gives clean PDFs of whole chats, even many at once.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://thattoolsite.com/chatgpt-export-viewer" rel="noopener noreferrer"&gt;ChatGPT and Claude export viewer&lt;/a&gt; turns any chat from your data export into a clean PDF with both sides of the conversation, and can put many chats into one file. Nothing is uploaded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/chatgpt-export-viewer" rel="noopener noreferrer"&gt;Open your export&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Four ways to get a conversation into a PDF
&lt;/h2&gt;

&lt;p&gt;Neither ChatGPT nor Claude has a “Download as PDF” button for a conversation. What works best depends on whether you want one answer, one full chat or many chats:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Good for&lt;/th&gt;
&lt;th&gt;Watch out for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Print the chat page (Ctrl+P or Cmd+P)&lt;/td&gt;
&lt;td&gt;A quick copy of a short chat&lt;/td&gt;
&lt;td&gt;Sidebars, cut-off tables and code, dark backgrounds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data export plus a viewer&lt;/td&gt;
&lt;td&gt;Whole chats, old chats, many chats at once&lt;/td&gt;
&lt;td&gt;Waiting for the export email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy one answer into Markdown to PDF&lt;/td&gt;
&lt;td&gt;A single answer laid out as a document&lt;/td&gt;
&lt;td&gt;Only one answer at a time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser extensions&lt;/td&gt;
&lt;td&gt;Saving from the chat page in one click&lt;/td&gt;
&lt;td&gt;Access to every page you open on the chat site&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sections below go through each, starting with the one most people try first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Printing the chat page: quick, but messy
&lt;/h2&gt;

&lt;p&gt;Pressing Ctrl+P (Cmd+P on a Mac) in the chat prints the web app as it is on screen. Depending on the browser and the length of the chat, you may get the sidebar and the message box on every page, only the part of the conversation that had loaded, code blocks and wide tables cut off at the page edge, and grey text on black if you use dark mode.&lt;/p&gt;

&lt;p&gt;For a short chat with plain text it can be good enough. Switch the app to light mode first, scroll to the top so the whole chat has loaded, and choose Save as PDF in the print window. For anything with tables, code or more than a few screens of text, use one of the methods below.&lt;/p&gt;

&lt;h2&gt;
  
  
  A whole chat, cleanly, from your data export
&lt;/h2&gt;

&lt;p&gt;Your data export contains every message of every chat as text, so it can be laid out properly as a document rather than photographed from the screen. If you don't have an export yet, the &lt;a href="https://thattoolsite.com/guides/export-chatgpt-history" rel="noopener noreferrer"&gt;guide to exporting your ChatGPT history&lt;/a&gt; shows how to request one; Claude's is under Settings, Privacy, Export data.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Drop the export ZIP.&lt;/strong&gt; Open the &lt;a href="https://thattoolsite.com/chatgpt-export-viewer" rel="noopener noreferrer"&gt;export viewer&lt;/a&gt; and drop the ZIP from ChatGPT or Claude.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Find the chat.&lt;/strong&gt; Search for a word you remember from it, or sort by date.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Choose the paper.&lt;/strong&gt; A4 or Letter, next to the Save as PDF button.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Press Save as PDF.&lt;/strong&gt; The print window opens with only the chat in it. Choose Save as PDF as the destination.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The PDF has a title, the date and the model, then each message under a You, ChatGPT or Claude heading. Headings, lists, tables, code blocks and formulas are drawn as they were in the app, and the text stays real text, so you can search and copy it later. The file is named after the chat.&lt;/p&gt;

&lt;p&gt;Tool steps, such as code that ChatGPT ran, web searches and reasoning summaries, are left out by default so the PDF reads like the conversation you saw. Tick Include tool steps, code runs and thinking if you need a complete record, for example to show how an analysis was done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Several chats in one PDF
&lt;/h2&gt;

&lt;p&gt;Tick the chats you want in the list, then choose Save as PDF in the bar that appears. Each chat starts on a new page, in the order they're listed. Up to 100 chats go into one PDF, which keeps the print window responsive; for more, make several PDFs or download them as Markdown instead.&lt;/p&gt;

&lt;p&gt;This is handy for a project that spread across many chats: search for the project name, select all the results and save them as one document to share with a colleague or keep with the project files.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you only need one answer
&lt;/h2&gt;

&lt;p&gt;If what you want is a single answer, not the whole chat, you don't need the export. Use the copy button under the answer and paste it into &lt;a href="https://thattoolsite.com/markdown-to-pdf" rel="noopener noreferrer"&gt;Markdown to PDF&lt;/a&gt;, which gives you margins, text size and styles for a polished document. The &lt;a href="https://thattoolsite.com/guides/save-ai-answer-as-pdf" rel="noopener noreferrer"&gt;guide to saving an AI answer as a PDF&lt;/a&gt; walks through it, including how to keep tables and maths intact.&lt;/p&gt;

&lt;p&gt;Copying works for chats you have open now. For a chat you can no longer find in the sidebar, or one that was very long, the export is quicker than scrolling and copying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Settings in the print window
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;What to choose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Destination or printer&lt;/td&gt;
&lt;td&gt;Save as PDF (Chrome, Edge, Firefox), PDF menu, Save as PDF (Safari on a Mac), or Microsoft Print to PDF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Headers and footers&lt;/td&gt;
&lt;td&gt;Off, so the date and web address aren't printed in the margins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paper size&lt;/td&gt;
&lt;td&gt;The same as you picked on the page; Safari sometimes needs it set here as well&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale&lt;/td&gt;
&lt;td&gt;Default. Shrinking to fit makes text small without fixing wide tables&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On iPhone and iPad the print window has a share button; choose Save to Files. On Android, pick Save as PDF as the printer and tap the PDF button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems and fixes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The PDF shows the whole web page.&lt;/strong&gt; The browser's own print menu was used before the chat was ready. Use the Save as PDF button on the page, which prepares the document first.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Images show as [Image].&lt;/strong&gt; Exports keep pictures as separate files next to the chats. The PDF marks where each was; the image files are in the ZIP.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;An old version of an answer appears.&lt;/strong&gt; It can't, if you use the export viewer: it follows the branch you last saw. Printing the page shows whichever version is on screen, so check the arrows under an edited message first.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;A long code block runs across pages.&lt;/strong&gt; Blocks that fit on a page are kept together; longer ones have to continue on the next page. Narrow margins in Markdown to PDF help for single answers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://help.openai.com/en/articles/7260999-how-do-i-export-my-chatgpt-history-and-data" rel="noopener noreferrer"&gt;OpenAI Help Center: Exporting your ChatGPT history and data&lt;/a&gt;. how the export is requested&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://support.claude.com/en/articles/9450526" rel="noopener noreferrer"&gt;Claude Help Center: How can I export my Claude data?&lt;/a&gt;. Settings, Privacy, Export data&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://thattoolsite.com/guides/save-chatgpt-conversation-as-pdf" rel="noopener noreferrer"&gt;ThatToolSite&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>chatgpt</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Export Your ChatGPT History and Open It Privately</title>
      <dc:creator>ThatToolSite</dc:creator>
      <pubDate>Tue, 20 Oct 2026 16:00:02 +0000</pubDate>
      <link>https://dev.to/thattoolsite/how-to-export-your-chatgpt-history-and-open-it-privately-4emb</link>
      <guid>https://dev.to/thattoolsite/how-to-export-your-chatgpt-history-and-open-it-privately-4emb</guid>
      <description>&lt;p&gt;ChatGPT can email you a ZIP of every conversation you've had. Here is how to request it, what's inside, and how to read and search it on your own device.&lt;/p&gt;

&lt;p&gt;Drop the ZIP into the &lt;a href="https://thattoolsite.com/chatgpt-export-viewer" rel="noopener noreferrer"&gt;ChatGPT and Claude export viewer&lt;/a&gt; to search every chat and save any of them as PDF or Markdown. It runs in your browser, so your history is never uploaded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/chatgpt-export-viewer" rel="noopener noreferrer"&gt;Open your export&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why export your ChatGPT history
&lt;/h2&gt;

&lt;p&gt;Your chats live on OpenAI's servers, and the app is built for carrying on a conversation, not for finding one from last spring. An export gives you a copy you control. People usually want one for a few reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  a backup before deleting chats, closing an account or leaving a job where they used a personal account for work;&lt;/li&gt;
&lt;li&gt;  finding an answer again with a proper full-text search instead of scrolling the sidebar;&lt;/li&gt;
&lt;li&gt;  keeping research, drafts or code from many chats in one place, as files that open without ChatGPT;&lt;/li&gt;
&lt;li&gt;  moving notes into a knowledge app such as Obsidian or Notion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The export is the only official way to get everything at once. Copying chats one by one works for a handful, not for hundreds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: request the export from ChatGPT
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Open settings.&lt;/strong&gt; Sign in to ChatGPT, click your profile picture or name, and choose Settings.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Go to Data controls.&lt;/strong&gt; Under Export data, choose Export, then confirm.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Wait for the email.&lt;/strong&gt; OpenAI sends a download link when the file is ready. It is often quick, but OpenAI says it can take up to 7 days.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Download within 24 hours.&lt;/strong&gt; The link expires 24 hours after you receive it, and you need to be signed in to the same account to use it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Settings exports are available on Free, Plus and Pro accounts and on eligible Edu workspaces. On ChatGPT Business and Enterprise workspaces the option isn't there; the workspace owner handles data requests. If you can't sign in at all, OpenAI's Privacy Portal has a separate “Download my data” request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: know what's inside the ZIP
&lt;/h2&gt;

&lt;p&gt;The download is a single ZIP file. Inside you'll usually find:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;What it holds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;conversations.json&lt;/td&gt;
&lt;td&gt;Every chat with every message, as data. Large exports split it into conversations-000.json, conversations-001.json and so on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;chat.html&lt;/td&gt;
&lt;td&gt;A web page that shows your chats. It loads the whole history at once, which can be very slow for a long one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;user.json&lt;/td&gt;
&lt;td&gt;Basic account details.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;message_feedback.json&lt;/td&gt;
&lt;td&gt;The thumbs up and down you gave answers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;shared_conversations.json&lt;/td&gt;
&lt;td&gt;Chats you shared with a public link.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image files&lt;/td&gt;
&lt;td&gt;Pictures you uploaded or generated, referenced from the chats.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The conversations file is written for programs. Each chat is a tree of message nodes rather than a simple list, because every time you edit a prompt or regenerate an answer ChatGPT keeps the old version as a separate branch. A shortened chat looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lisbon trip on a budget"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"create_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1736935200.12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"current_node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mapping"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"u1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"parent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"a1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                      &lt;/span&gt;&lt;span class="nl"&gt;"parts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Plan 3 days in Lisbon."&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"a1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"parent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"u1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"u2"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;current_node&lt;/code&gt; marks the last message you were looking at. Reading the chat as you saw it means starting there and following each &lt;code&gt;parent&lt;/code&gt; back to the first message. A plain JSON viewer can show you the tree, but not the conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: open it without uploading it anywhere
&lt;/h2&gt;

&lt;p&gt;Many “ChatGPT export viewer” websites ask you to upload the ZIP to their server. That file holds every conversation you've had, including anything personal or confidential you typed. Uploading it means trusting a stranger's server with all of it. A viewer that runs in the browser reads the file on your own device instead.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Open the export viewer.&lt;/strong&gt; Go to the &lt;a href="https://thattoolsite.com/chatgpt-export-viewer" rel="noopener noreferrer"&gt;ChatGPT and Claude export viewer&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Drop the ZIP as it is.&lt;/strong&gt; No need to unzip it. Exports of hundreds of megabytes are read in pieces, with progress shown.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Search or browse.&lt;/strong&gt; Search looks through every title and message. Put a phrase in quotes to match it exactly.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Open a chat.&lt;/strong&gt; It's shown as it appeared in the app: the branch you last saw, with tables, code and formulas drawn properly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can check that nothing is uploaded: open your browser's developer tools, switch to the Network tab and drop the file. The page makes no request carrying your data. Tool steps such as code runs, web searches and reasoning summaries are hidden until you tick the option to include them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude works the same way
&lt;/h2&gt;

&lt;p&gt;Claude has its own export: Settings, Privacy, Export data, on the web or in the desktop app (not from the iOS or Android apps). The link also arrives by email and also expires after 24 hours. On Team and Enterprise plans only the organisation's Primary Owner can export.&lt;/p&gt;

&lt;p&gt;Claude's ZIP holds a conversations.json too, but each chat is a simple list of messages with a sender, a time and content blocks for text, thinking and tool use. The same viewer opens both, and you can drop a ChatGPT export and a Claude export together to search all of your chats in one list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping a usable archive
&lt;/h2&gt;

&lt;p&gt;The raw JSON is a faithful backup, but you can't read it. Keep the original ZIP, and next to it a readable copy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Markdown files.&lt;/strong&gt; Download all as Markdown gives one file per chat, named by date and title, such as &lt;code&gt;2025-01-15-lisbon-trip-on-a-budget.md&lt;/code&gt;, plus an index.md that links to every chat. Markdown opens in any text editor and in note apps.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;PDFs for the chats that matter.&lt;/strong&gt; A PDF is the easiest thing to send to someone or attach to a record. The &lt;a href="https://thattoolsite.com/guides/save-chatgpt-conversation-as-pdf" rel="noopener noreferrer"&gt;guide to saving a chat as PDF&lt;/a&gt; covers the options.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Somewhere private.&lt;/strong&gt; Treat the export like a diary: store it on an encrypted drive or in a private folder, not a shared one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Request a new export now and then. Each one is a full snapshot, so the newest replaces the old one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Letting the link expire.&lt;/strong&gt; After 24 hours it stops working and you have to request the export again, which means waiting for another email.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Opening the export in a spreadsheet.&lt;/strong&gt; Excel and Google Sheets can't turn the nested message tree into rows, so you get one cell full of text or an error.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Expecting deleted chats.&lt;/strong&gt; The export contains the chats in your account when it was made. Chats you deleted before requesting it aren't included.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Dropping the wrong file.&lt;/strong&gt; If you unzipped the export, drop conversations.json (or all the numbered conversations files together), not user.json or chat.html.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://help.openai.com/en/articles/7260999-how-do-i-export-my-chatgpt-history-and-data" rel="noopener noreferrer"&gt;OpenAI Help Center: Exporting your ChatGPT history and data&lt;/a&gt;. Data controls, Export data; 24-hour link; eligible plans&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://support.claude.com/en/articles/9450526" rel="noopener noreferrer"&gt;Claude Help Center: How can I export my Claude data?&lt;/a&gt;. Settings, Privacy, Export data; 24-hour link; supported apps&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://thattoolsite.com/guides/export-chatgpt-history" rel="noopener noreferrer"&gt;ThatToolSite&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>chatgpt</category>
      <category>privacy</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Save a ChatGPT, Claude or Gemini Answer as a PDF</title>
      <dc:creator>ThatToolSite</dc:creator>
      <pubDate>Tue, 20 Oct 2026 00:00:06 +0000</pubDate>
      <link>https://dev.to/thattoolsite/how-to-save-a-chatgpt-claude-or-gemini-answer-as-a-pdf-52ii</link>
      <guid>https://dev.to/thattoolsite/how-to-save-a-chatgpt-claude-or-gemini-answer-as-a-pdf-52ii</guid>
      <description>&lt;p&gt;Printing the chat page gives you sidebars, cut-off tables and broken code. Copy the answer as Markdown, lay it out as a document, then save it as a PDF.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/markdown-to-pdf" rel="noopener noreferrer"&gt;Markdown to PDF&lt;/a&gt; turns a pasted ChatGPT, Claude or Gemini answer into a clean, paginated PDF with real text, tables, code and maths. Nothing is uploaded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/markdown-to-pdf" rel="noopener noreferrer"&gt;Make a PDF&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why printing the chat page goes wrong
&lt;/h2&gt;

&lt;p&gt;Pressing Ctrl+P on a chat prints the web app, not the answer. You get the sidebar, the message box and every earlier message. Wide tables and long code lines are cut off at the page edge because the chat lets them scroll sideways, and a dark theme can come out as grey text on black. The page was built for scrolling, not for paper.&lt;/p&gt;

&lt;p&gt;The reliable route is to take the answer out of the chat as Markdown, which every assistant writes underneath, and lay it out as a document. It takes under a minute and works the same for all three assistants.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: copy the answer, ideally as Markdown
&lt;/h2&gt;

&lt;p&gt;ChatGPT, Claude and Gemini each have a copy button under every answer. Depending on the app and its version, it copies either the Markdown source or formatted text. Markdown keeps the most: headings, lists, tables, code blocks and maths all survive. If you only get formatted text, headings and lists usually still come through, but tables may not.&lt;/p&gt;

&lt;p&gt;You can always ask: “Give me that answer as Markdown in a code block.” The assistant then shows the raw source, and copying the code block gives you exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: lay it out and save it as a PDF
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Paste into Markdown to PDF.&lt;/strong&gt; The preview shows the pages as they will print.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Choose the page.&lt;/strong&gt; A4 or Letter, narrow, normal or wide margins, a text size, and the Clean, Serif or Dark style.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Press Save as PDF.&lt;/strong&gt; Your browser's print window opens with only the document in it.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Pick Save as PDF as the destination.&lt;/strong&gt; Untick Headers and footers so the date and web address aren't printed, then save.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The PDF is made by the browser's own print engine from real text, so you can select, search and copy it later. The file is named after the first heading of the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each assistant offers itself
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Assistant&lt;/th&gt;
&lt;th&gt;Built-in option&lt;/th&gt;
&lt;th&gt;Good for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemini&lt;/td&gt;
&lt;td&gt;Share &amp;amp; export, then Export to Docs creates a Google Doc. From Docs, File, Download, PDF.&lt;/td&gt;
&lt;td&gt;One answer, if you already use Google Docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ChatGPT&lt;/td&gt;
&lt;td&gt;Settings, Data controls, Export sends a download link for a ZIP of your whole history.&lt;/td&gt;
&lt;td&gt;Backing up every chat, not printing one answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude&lt;/td&gt;
&lt;td&gt;Settings, Privacy, Export data emails a download link to your chats and account data. Share creates a link to a snapshot of the chat.&lt;/td&gt;
&lt;td&gt;Backups and sharing a link, not a PDF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of the three has a button that turns a single answer into a PDF, so copying the Markdown is still the most direct way. Browser extensions that save chats exist too, but they ask to read every page you open on the chat site; check what an extension can access before installing it for private conversations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping tables, code and maths intact
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Maths.&lt;/strong&gt; ChatGPT often writes formulas with &lt;code&gt;\(&lt;/code&gt; … &lt;code&gt;\)&lt;/code&gt; and &lt;code&gt;\[&lt;/code&gt; … &lt;code&gt;\]&lt;/code&gt; instead of dollar signs, which many Markdown tools show as plain backslashes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tex"&gt;&lt;code&gt;The area of a circle is &lt;span class="p"&gt;\(&lt;/span&gt;&lt;span class="nb"&gt;A &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt; &lt;/span&gt;&lt;span class="nv"&gt;\pi&lt;/span&gt;&lt;span class="nb"&gt; r&lt;/span&gt;&lt;span class="p"&gt;^&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;\)&lt;/span&gt;.

&lt;span class="p"&gt;\[&lt;/span&gt;&lt;span class="nb"&gt;
E &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt; mc&lt;/span&gt;&lt;span class="p"&gt;^&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="nb"&gt;
&lt;/span&gt;&lt;span class="p"&gt;\]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Markdown to PDF converts these to &lt;code&gt;$A = \pi r^2$&lt;/code&gt; and a &lt;code&gt;$$&lt;/code&gt; block before drawing them, so both formulas appear typeset. A price written as &lt;code&gt;\$5&lt;/code&gt; stays a dollar sign.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tables and code.&lt;/strong&gt; Rows of a table, code blocks, diagrams and formulas are kept together on one page where they fit, and headings stay with the paragraph after them. A table longer than a page continues on the next one with its header row repeated. For a wide table, pick narrow margins and a smaller text size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagrams.&lt;/strong&gt; Mermaid diagrams in the answer are drawn and embedded as vector graphics, so they stay sharp when zoomed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common problems and fixes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The PDF shows a date and a web address.&lt;/strong&gt; Those are the browser's headers and footers. Untick them in the print window; the browser remembers the choice.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Asterisks and pound signs everywhere.&lt;/strong&gt; The text was pasted somewhere that doesn't read Markdown. Paste it into a Markdown tool, such as the &lt;a href="https://thattoolsite.com/markdown-viewer" rel="noopener noreferrer"&gt;Markdown viewer&lt;/a&gt;, instead.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The table came through as lines of text.&lt;/strong&gt; The copy gave formatted text without the table. Ask the assistant for the table in Markdown and copy again.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;A download button doesn't appear.&lt;/strong&gt; The PDF is saved from the print window. Choose Save as PDF (or Microsoft Print to PDF on Windows) as the printer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://support.google.com/gemini/answer/14184041" rel="noopener noreferrer"&gt;Gemini Apps Help: Export responses from Gemini Apps&lt;/a&gt;. Share &amp;amp; export, Export to Docs&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://help.openai.com/en/articles/7260999-how-do-i-export-my-chatgpt-history-and-data" rel="noopener noreferrer"&gt;OpenAI Help Center: Exporting your ChatGPT history and data&lt;/a&gt;. Settings, Data controls, Export&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://support.claude.com/en/articles/9450526" rel="noopener noreferrer"&gt;Claude Help Center: Export your Claude data&lt;/a&gt;. Settings, Privacy, Export data&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://thattoolsite.com/guides/save-ai-answer-as-pdf" rel="noopener noreferrer"&gt;ThatToolSite&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>chatgpt</category>
      <category>productivity</category>
      <category>pdf</category>
    </item>
    <item>
      <title>How to Copy a ChatGPT Table into Excel or Google Sheets</title>
      <dc:creator>ThatToolSite</dc:creator>
      <pubDate>Mon, 19 Oct 2026 16:08:10 +0000</pubDate>
      <link>https://dev.to/thattoolsite/how-to-copy-a-chatgpt-table-into-excel-or-google-sheets-419o</link>
      <guid>https://dev.to/thattoolsite/how-to-copy-a-chatgpt-table-into-excel-or-google-sheets-419o</guid>
      <description>&lt;p&gt;Copy the table you can see, not its Markdown, and it usually pastes into columns. When it lands in one column or the numbers come out wrong, these are the fixes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/markdown-table-to-excel" rel="noopener noreferrer"&gt;Markdown table to Excel&lt;/a&gt; takes a whole ChatGPT, Claude or Gemini answer, finds the tables in it and gives you an .xlsx or .csv file with every value in its own cell. It runs in your browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/markdown-table-to-excel" rel="noopener noreferrer"&gt;Convert a table to Excel&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The quick way: copy the table you can see
&lt;/h2&gt;

&lt;p&gt;Select the finished table in the chat window with your mouse, from the first header cell to the last cell, and copy it. Click cell A1 in Excel or Google Sheets and paste. When the browser copies a table that is on screen, it puts a real table on the clipboard, so the spreadsheet usually keeps the columns.&lt;/p&gt;

&lt;p&gt;Check two things before you rely on it: that every row landed in the right columns, and that numbers are numbers. Click a price cell; if the formula bar shows &lt;code&gt;$24.50&lt;/code&gt; as text, or the cell has a small green triangle in Excel, the values came across as text. The sections below fix both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the table sometimes lands in one column
&lt;/h2&gt;

&lt;p&gt;AI assistants write tables in Markdown and turn them into a table on screen. The copy button under an answer may give you that Markdown source instead of the table, depending on the app and its version, and so does copying from a code block or a text file. Markdown looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| Item | SKU | Qty | Unit price | Ordered |
|:--|:--:|--:|--:|--|
| Desk lamp | 00412 | 2 | $24.50 | 2026-09-14 |
| &lt;span class="gs"&gt;**USB-C cable**&lt;/span&gt; | 00977 | 10 | $6.99 | 14/09/2026 |
| Monitor arm &lt;span class="se"&gt;\|&lt;/span&gt; steel | 01230 | 1 | $1,299.00 | Sep 30, 2026 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A spreadsheet has no idea that the &lt;code&gt;|&lt;/code&gt; characters separate cells. Each line goes into column A as one long piece of text, and the &lt;code&gt;|:--|:--:|&lt;/code&gt; line comes along as an extra row. You can split it inside the spreadsheet, or convert the Markdown first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convert the Markdown instead of splitting it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Copy the whole answer.&lt;/strong&gt; Use the copy button, or select everything. Text before and after the table doesn't matter.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Paste it into Markdown table to Excel.&lt;/strong&gt; It lists every table it finds, with a preview of the cells.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Download, or copy and paste.&lt;/strong&gt; Download Excel (.xlsx) gives one sheet per table. Copy for Excel copies tab-separated cells, which Excel and Google Sheets both split into columns when you paste.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is what the converter does with the sample table above:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cell in the Markdown&lt;/th&gt;
&lt;th&gt;In the spreadsheet&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;**USB-C cable**&lt;/td&gt;
&lt;td&gt;USB-C cable (the bold markers are removed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitor arm \\&lt;/td&gt;
&lt;td&gt;steel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;00412&lt;/td&gt;
&lt;td&gt;Text 00412, so the leading zeros stay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$1,299.00&lt;/td&gt;
&lt;td&gt;The number 1299, formatted as $1,299.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-09-14, 14/09/2026, Sep 30, 2026&lt;/td&gt;
&lt;td&gt;Real dates. 14/09 can only be day first, because there is no month 14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;|:--|&lt;/code&gt; alignment row is dropped. A date such as 03/04/2024, which could be 3 April or 4 March, is left as text so that it can't silently change meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Already pasted into one column? Fix it in Excel
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Select column A.&lt;/strong&gt; Only the pasted rows need to be selected.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Open Data, then Text to Columns.&lt;/strong&gt; Choose Delimited, then Next.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Tick Other and type a pipe (|).&lt;/strong&gt; Untick Tab and Space. The preview shows the columns appear.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Set ID columns to Text.&lt;/strong&gt; In the last step, click a column such as SKU in the preview and choose Text, so 00412 isn't turned into 412. Then Finish.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Tidy up.&lt;/strong&gt; Delete the row of dashes, and the empty first and last columns that the outer pipes create. Remove ** and other Markdown marks with Find and Replace.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Fix it in Google Sheets
&lt;/h2&gt;

&lt;p&gt;Select the pasted column, open &lt;strong&gt;Data&lt;/strong&gt;, choose &lt;strong&gt;Split text to columns&lt;/strong&gt;, and set the separator to Custom with a &lt;code&gt;|&lt;/code&gt;. Right after pasting, the paste options next to the cells also offer Split text to columns. Then delete the dashes row and the empty edge columns, as in Excel. Sheets guesses number formats as it splits, so check ID columns with leading zeros afterwards, and format them as plain text before splitting if they matter.&lt;/p&gt;

&lt;p&gt;If the table came from Gemini, there may be a shortcut: Google's help says tables in Gemini answers have an Export to Sheets button at the bottom right, which saves the table as a new spreadsheet in your Drive. It isn't offered for tables that contain images.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leading zeros, dates and long numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Leading zeros vanish.&lt;/strong&gt; Excel treats 00412 as the number 412. Format the column as Text before pasting, or type an apostrophe in front of the value. The converter keeps codes like this as text for you.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Day and month swap.&lt;/strong&gt; 03/04/2024 is read using your computer's regional settings. If the AI wrote the date the other way round, it changes meaning without warning. Ask the assistant for dates written as 2024-04-03, which every spreadsheet reads the same way.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Long IDs end in zeros.&lt;/strong&gt; Excel keeps 15 significant digits, so a 16-digit card or order number loses its last digits. Keep such columns as text; the converter leaves numbers longer than 15 digits as text.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Numbers stay as text.&lt;/strong&gt; Currency signs, spaces used as thousands separators and footnote marks such as 12.5* stop a value being read as a number. Remove the extra characters, or let the converter read prices, percentages and thousands separators.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Need the table in Word or Google Docs rather than a spreadsheet? Paste the answer into the &lt;a href="https://thattoolsite.com/markdown-viewer" rel="noopener noreferrer"&gt;Markdown viewer&lt;/a&gt; and press Copy as rich text; it pastes as a real table with borders. To go the other way, from a spreadsheet to a table you can paste back into a chat, use &lt;a href="https://thattoolsite.com/excel-to-markdown" rel="noopener noreferrer"&gt;Excel to Markdown&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://support.microsoft.com/topic/1bf7b935-36e1-4985-842f-5dfa51f85fe7" rel="noopener noreferrer"&gt;Microsoft Support: Keeping leading zeros and large numbers&lt;/a&gt;. 15 significant digits, Text format and the apostrophe&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://support.google.com/docs/answer/6325535" rel="noopener noreferrer"&gt;Google Docs Editors Help: Split text, remove duplicates, or trim whitespace&lt;/a&gt;. Split text to columns in Sheets&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://support.google.com/gemini/answer/14184041" rel="noopener noreferrer"&gt;Gemini Apps Help: Export responses from Gemini Apps&lt;/a&gt;. Export to Sheets for tables&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://thattoolsite.com/guides/copy-chatgpt-table-to-excel" rel="noopener noreferrer"&gt;ThatToolSite&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>chatgpt</category>
      <category>productivity</category>
      <category>excel</category>
    </item>
    <item>
      <title>How to Create a Markdown Table Without Typing Pipes</title>
      <dc:creator>ThatToolSite</dc:creator>
      <pubDate>Mon, 19 Oct 2026 00:00:05 +0000</pubDate>
      <link>https://dev.to/thattoolsite/how-to-create-a-markdown-table-without-typing-pipes-39nc</link>
      <guid>https://dev.to/thattoolsite/how-to-create-a-markdown-table-without-typing-pipes-39nc</guid>
      <description>&lt;p&gt;Type into a grid or paste cells from a spreadsheet and let a generator write the pipes, dashes and escapes. Here is how, plus the rules for when you edit a table by hand.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://thattoolsite.com/markdown-table-generator" rel="noopener noreferrer"&gt;Markdown table generator&lt;/a&gt; gives you a grid to type in or paste spreadsheet cells into, and writes the Markdown: pipes, dashes, alignment and escapes included.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/markdown-table-generator" rel="noopener noreferrer"&gt;Open the table generator&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the table in a grid instead
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Open the generator.&lt;/strong&gt; Type the header row first, then the rows below. Add rows and columns as you go.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Set each column's alignment.&lt;/strong&gt; Left, centre or right. Right suits numbers, so the digits line up.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Copy the Markdown.&lt;/strong&gt; Paste it into a README, an issue, a wiki page or a chat message.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Line up columns pads every cell so the raw Markdown is readable in a text editor or a code review. It doesn't change how the table looks once rendered, so untick it if you want the shortest text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paste from Excel or Google Sheets
&lt;/h2&gt;

&lt;p&gt;When you copy cells from a spreadsheet, the clipboard holds them as text with a tab between cells and a line break between rows. That is already a table; it just needs converting. Copy this range from a sheet:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Up to 3 users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team&lt;/td&gt;
&lt;td&gt;12.50&lt;/td&gt;
&lt;td&gt;SSO | audit log&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Paste it into the generator's grid, set the Price column to right-aligned, and the Markdown comes out like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| Plan | Price | Notes            |
| ---- | ----: | ---------------- |
| Free |     0 | Up to 3 users    |
| Team | 12.50 | SSO &lt;span class="se"&gt;\|&lt;/span&gt; audit log |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipe inside “SSO | audit log” is escaped so it stays in its cell, and the colon in &lt;code&gt;----:&lt;/code&gt; right-aligns the prices. For a whole workbook or a CSV file, use &lt;a href="https://thattoolsite.com/excel-to-markdown" rel="noopener noreferrer"&gt;Excel to Markdown&lt;/a&gt;, which opens the file, lets you choose the sheet and right-aligns number columns for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The syntax, for when you edit by hand
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  The first line is the header row. Cells are separated by &lt;code&gt;|&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  The second line is the delimiter row: one cell of hyphens per column. GitHub's documentation asks for at least three hyphens per cell. A colon sets alignment: &lt;code&gt;:---&lt;/code&gt; left, &lt;code&gt;:---:&lt;/code&gt; centre, &lt;code&gt;---:&lt;/code&gt; right.&lt;/li&gt;
&lt;li&gt;  The header row and the delimiter row must have the same number of cells, or there is no table.&lt;/li&gt;
&lt;li&gt;  Pipes at the start and end of a row are optional, but they make the table easier to read and edit.&lt;/li&gt;
&lt;li&gt;  The cells don't have to line up in the source. Padding is for people, not for the renderer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pipes, line breaks and other things inside cells
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A pipe in a cell.&lt;/strong&gt; Write it as &lt;code&gt;\|&lt;/code&gt;. The GitHub Flavored Markdown spec says this works inside code spans too, so &lt;code&gt;`a \| b`&lt;/code&gt; shows as the code &lt;code&gt;a | b&lt;/code&gt;. An HTML entity such as &lt;code&gt;&amp;amp;#124;&lt;/code&gt; also works in plain text, but inside backticks it is shown literally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A line break in a cell.&lt;/strong&gt; A table row must stay on one line, so write &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; where the break goes. GitHub and most renderers show it as a new line inside the cell. The generator does this for you when a pasted cell contains a line break.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lists, headings and code blocks.&lt;/strong&gt; These can't go in a cell. Keep cells short, and put longer content below the table with a reference to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a table won't render
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;No blank line before it.&lt;/strong&gt; A table directly under a paragraph can be read as part of that paragraph. GitHub's docs ask for a blank line before the table.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cell counts don't match.&lt;/strong&gt; Count the cells in the header and the delimiter row. A missing &lt;code&gt;|---&lt;/code&gt; is the usual cause.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Inside a code block.&lt;/strong&gt; A table indented by four spaces, or inside &lt;code&gt;&lt;/code&gt;`&lt;code&gt;&lt;/code&gt; fences, is shown as code. AI answers sometimes do this on purpose so you can copy the source.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;An unescaped pipe in a cell.&lt;/strong&gt; The row gets an extra column. Escape the pipe as &lt;code&gt;\|&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The renderer doesn't support tables.&lt;/strong&gt; Tables are a GitHub extension, not part of core CommonMark. Check the result in the &lt;a href="https://thattoolsite.com/markdown-viewer" rel="noopener noreferrer"&gt;Markdown viewer&lt;/a&gt;, which uses GitHub's rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://github.github.com/gfm/#tables-extension-" rel="noopener noreferrer"&gt;GitHub Flavored Markdown spec: Tables (extension)&lt;/a&gt;. Delimiter row, alignment and escaped pipes&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables" rel="noopener noreferrer"&gt;GitHub Docs: Organizing information with tables&lt;/a&gt;. Blank line before the table, three hyphens per column&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://thattoolsite.com/guides/markdown-table-without-pipes" rel="noopener noreferrer"&gt;ThatToolSite&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>markdown</category>
      <category>productivity</category>
      <category>documentation</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to Preview Markdown Online: Tables, Mermaid and Math</title>
      <dc:creator>ThatToolSite</dc:creator>
      <pubDate>Sun, 18 Oct 2026 16:11:59 +0000</pubDate>
      <link>https://dev.to/thattoolsite/how-to-preview-markdown-online-tables-mermaid-and-math-43oi</link>
      <guid>https://dev.to/thattoolsite/how-to-preview-markdown-online-tables-mermaid-and-math-43oi</guid>
      <description>&lt;p&gt;Open the file or paste the text into a previewer that supports GitHub-style Markdown. Use the test file below to see what any previewer can handle.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://thattoolsite.com/markdown-viewer" rel="noopener noreferrer"&gt;Markdown viewer&lt;/a&gt; shows a .md file or pasted Markdown with tables, task lists, footnotes, Mermaid diagrams and maths, as you type. The file stays on your device.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/markdown-viewer" rel="noopener noreferrer"&gt;Open the Markdown viewer&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Preview a Markdown file in a minute
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Open the viewer.&lt;/strong&gt; It works in any modern browser, on a computer or a phone. Nothing to install.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Open the .md file or paste the text.&lt;/strong&gt; Choose the file from your device, or paste Markdown, a README or an AI answer into the editor.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Read the preview.&lt;/strong&gt; It updates as you type. Headings get anchors, so links within the document work.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Export if you need to.&lt;/strong&gt; Copy it as rich text for Word or Google Docs, download HTML, or save it as a PDF.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Previewing is different from editing on GitHub or in an editor: you see the document the way a reader will, which is when broken tables, unclosed code blocks and diagrams with errors become obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  A test file that shows what a previewer supports
&lt;/h2&gt;

&lt;p&gt;Markdown comes in flavours. The core, CommonMark, has no tables at all; tables, task lists, strikethrough and footnotes are GitHub extensions, and Mermaid and maths are extensions on top of those. Paste this file into any previewer to see which parts it understands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Preview test&lt;/span&gt;

| Feature | Status | Notes |
|---|:---:|---:|
| Table | done | 3 columns |
| Pipe &lt;span class="se"&gt;\|&lt;/span&gt; in a cell | done | escaped |
&lt;span class="p"&gt;
-&lt;/span&gt; [x] Task list item, done
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Task list item, open

~~Strikethrough~~ and a footnote.[^1]

Inline maths $a^2 + b^2 = c^2$, and a price: it costs &lt;span class="se"&gt;\$&lt;/span&gt;5.

$$
&lt;span class="se"&gt;\i&lt;/span&gt;nt_0^1 x^2 &lt;span class="se"&gt;\,&lt;/span&gt; dx = &lt;span class="se"&gt;\f&lt;/span&gt;rac{1}{3}
$$

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;mermaid
&lt;/span&gt;&lt;span class="sb"&gt;flowchart LR
    A[Write] --&amp;gt; B[Preview] --&amp;gt; C[Share]&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;js
&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;code block&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;^1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="sx"&gt;Footnotes&lt;/span&gt; are a GitHub extension.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the Markdown viewer, every part renders: a three-column table with a real pipe in one cell, two checkboxes, a footnote link, typeset formulas, a small flowchart and a highlighted code block. The &lt;code&gt;\$5&lt;/code&gt; stays a dollar sign. A previewer that shows rows of pipes, a &lt;code&gt;&lt;/code&gt;`&lt;code&gt;mermaid&lt;/code&gt; block as code, or dollar signs around raw TeX doesn't support that extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  What renders where
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;Tables&lt;/th&gt;
&lt;th&gt;Mermaid&lt;/th&gt;
&lt;th&gt;Maths&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub (files, issues, pull requests, wikis)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes, with $ and $$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VS Code built-in preview&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes, with KaTeX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A plain CommonMark previewer&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Markdown viewer on this site&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes, also \( \) and \[ \]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Support changes over time, so when a document must look right somewhere specific, test it there. GitHub, for example, documents which Mermaid version it uses: a &lt;code&gt;mermaid&lt;/code&gt; block containing only the word &lt;code&gt;info&lt;/code&gt; shows it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Previewing private files
&lt;/h2&gt;

&lt;p&gt;Some online previewers send your text to their server to render it. That matters for internal documentation, contracts written in Markdown, or AI answers containing customer data. A previewer that renders in the browser never needs to. You can check: open your browser's developer tools on the Network tab, paste some text and watch whether a request carries it.&lt;/p&gt;

&lt;p&gt;The Markdown viewer here also holds back images linked from other websites until you press Show images, because loading an image tells that website your IP address. Images embedded in the file as data URLs show straight away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common problems and fixes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The table shows as lines of pipes.&lt;/strong&gt; GitHub's documentation asks for a blank line before the table and at least three hyphens in each cell of the dashes row. The header row and the dashes row must also have the same number of cells.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Images are missing.&lt;/strong&gt; A relative path such as &lt;code&gt;images/chart.png&lt;/code&gt; only works next to the folder it points to. Pasting the text alone leaves the images behind.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Prices turned into maths.&lt;/strong&gt; Two dollar signs on one line can be read as a formula. Write &lt;code&gt;\$5&lt;/code&gt; for a literal dollar sign.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Everything after a code block looks like code.&lt;/strong&gt; A &lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;/code&gt; ` fence wasn't closed. Count the fences; AI answers cut off mid-reply often end inside one.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The diagram shows an error.&lt;/strong&gt; Open it in the &lt;a href="https://thattoolsite.com/mermaid-editor" rel="noopener noreferrer"&gt;Mermaid editor&lt;/a&gt;, which shows the line and the token it didn't expect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the preview looks right and you need the HTML for a website or an email template, &lt;a href="https://thattoolsite.com/markdown-to-html" rel="noopener noreferrer"&gt;Markdown to HTML&lt;/a&gt; gives you clean markup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://github.github.com/gfm/" rel="noopener noreferrer"&gt;GitHub Flavored Markdown spec&lt;/a&gt;. Tables, task lists, strikethrough and autolinks&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables" rel="noopener noreferrer"&gt;GitHub Docs: Organizing information with tables&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/writing-mathematical-expressions" rel="noopener noreferrer"&gt;GitHub Docs: Writing mathematical expressions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-diagrams" rel="noopener noreferrer"&gt;GitHub Docs: Creating diagrams&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://code.visualstudio.com/docs/languages/markdown" rel="noopener noreferrer"&gt;Visual Studio Code: Markdown&lt;/a&gt;. Built-in Mermaid and KaTeX&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.gitlab.com/user/markdown/" rel="noopener noreferrer"&gt;GitLab Docs: GitLab Flavored Markdown&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://thattoolsite.com/guides/preview-markdown-online" rel="noopener noreferrer"&gt;ThatToolSite&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>markdown</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>tools</category>
    </item>
    <item>
      <title>How to Render Mermaid Diagrams in Markdown</title>
      <dc:creator>ThatToolSite</dc:creator>
      <pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/thattoolsite/how-to-render-mermaid-diagrams-in-markdown-4pbe</link>
      <guid>https://dev.to/thattoolsite/how-to-render-mermaid-diagrams-in-markdown-4pbe</guid>
      <description>&lt;p&gt;Put the diagram code in a fenced block tagged mermaid. GitHub, GitLab and VS Code draw it; many other places show it as code. Here is how to make it render, and what to do where it can't.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://thattoolsite.com/mermaid-editor" rel="noopener noreferrer"&gt;Mermaid editor&lt;/a&gt; draws the diagram as you type, points at the line with an error, and exports PNG or SVG for places that can't render Mermaid. It runs in your browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/mermaid-editor" rel="noopener noreferrer"&gt;Open the Mermaid editor&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to add a Mermaid diagram to Markdown
&lt;/h2&gt;

&lt;p&gt;Write the diagram code inside a fenced code block and put the word &lt;code&gt;mermaid&lt;/code&gt; straight after the opening three backticks. That word is the whole trick: renderers that support Mermaid look for it and draw a diagram instead of showing code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="err"&gt;```&lt;/span&gt;&lt;span class="nv"&gt;mermaid&lt;/span&gt;
&lt;span class="nv"&gt;flowchart&lt;/span&gt; &lt;span class="nv"&gt;TD&lt;/span&gt;
    &lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Order&lt;/span&gt; &lt;span class="nv"&gt;placed&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;Paid&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;Yes&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Ship&lt;/span&gt; &lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;No&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;D&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Send&lt;/span&gt; &lt;span class="nv"&gt;reminder&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line inside the block names the diagram type: &lt;code&gt;flowchart&lt;/code&gt; (or the older &lt;code&gt;graph&lt;/code&gt;), &lt;code&gt;sequenceDiagram&lt;/code&gt;, &lt;code&gt;classDiagram&lt;/code&gt;, &lt;code&gt;gantt&lt;/code&gt;, &lt;code&gt;pie&lt;/code&gt;, &lt;code&gt;mindmap&lt;/code&gt; and others. After it, &lt;code&gt;TD&lt;/code&gt; means top to bottom and &lt;code&gt;LR&lt;/code&gt; left to right. Square brackets make a box, curly braces a decision diamond, and &lt;code&gt;--&amp;gt;&lt;/code&gt; an arrow. Text between &lt;code&gt;--&lt;/code&gt; and the arrow labels it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Mermaid renders, and where it doesn't
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Place&lt;/th&gt;
&lt;th&gt;Renders Mermaid?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub: Markdown files, issues, pull requests, discussions, wikis&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VS Code's built-in Markdown preview&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Markdown viewer and Markdown to PDF on this site&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A GitHub Pages site built with Jekyll&lt;/td&gt;
&lt;td&gt;Not by itself; the site has to load Mermaid's script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Word, Google Docs, email, most chat apps&lt;/td&gt;
&lt;td&gt;No; use an exported image&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A renderer can also be behind the latest Mermaid release, so a newer diagram type may show an error on one site and work on another. On GitHub, a &lt;code&gt;mermaid&lt;/code&gt; block that contains only the word &lt;code&gt;info&lt;/code&gt; shows the version in use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The errors that stop a diagram rendering
&lt;/h2&gt;

&lt;p&gt;Each of these was typed into the Mermaid editor here (Mermaid 12). The left column fails, the right column draws.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fails&lt;/th&gt;
&lt;th&gt;Works&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;start --&amp;gt; end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;start --&amp;gt; End&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A node called end in lower case ends the flowchart. Capitalise it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A[Install (v2)]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A["Install (v2)"]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Brackets inside a label look like shape syntax. Put the label in double quotes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A —&amp;gt; B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A --&amp;gt; B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;An em dash is not two hyphens. Word processors and some AI answers swap them.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Flowchart TD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;flowchart TD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The diagram type is case-sensitive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alice-&amp;gt;&amp;gt;Bob: Hi; how are you?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Alice-&amp;gt;&amp;gt;Bob: Hi #59; how are you?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;In a sequence diagram a semicolon ends the line. Write it as the code #59;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Error messages name a line and the token the parser didn't expect. The real mistake is usually on that line or just before it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagrams written by ChatGPT, Claude or Gemini
&lt;/h2&gt;

&lt;p&gt;AI assistants write Mermaid well, but the mistakes above turn up often, especially labels with brackets or colons copied from your own wording. When an answer contains a diagram:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Copy only what is inside the fence, or the whole block including the &lt;code&gt;&lt;/code&gt;`&lt;code&gt;mermaid&lt;/code&gt; line, not the text around it.&lt;/li&gt;
&lt;li&gt;  Paste it into the Mermaid editor first. If it draws there, it's valid Mermaid, and any remaining problem is the renderer's version.&lt;/li&gt;
&lt;li&gt;  Ask the assistant to “quote every node label”. Quoted labels avoid most bracket and punctuation errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When the place can't render Mermaid: use an image
&lt;/h2&gt;

&lt;p&gt;For a slide, a Word document or an email, export the diagram. In the Mermaid editor, choose SVG for a sharp vector image that scales to any size, or PNG for apps that don't accept SVG. In a Markdown file on a site without Mermaid support, link the image and keep the source next to it:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;markdown&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/order-flow.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/order-flow.svg" alt="Order flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;Keeping the &lt;code&gt;.mmd&lt;/code&gt; source means the next person can change the diagram instead of redrawing it. To check how the whole document looks with the diagram in place, paste it into the &lt;a href="https://thattoolsite.com/markdown-viewer" rel="noopener noreferrer"&gt;Markdown viewer&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-diagrams" rel="noopener noreferrer"&gt;GitHub Docs: Creating diagrams&lt;/a&gt;. Where diagrams render, and the info block&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://mermaid.js.org/syntax/flowchart.html" rel="noopener noreferrer"&gt;Mermaid: Flowchart syntax&lt;/a&gt;. The lowercase end warning and quoted text&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://code.visualstudio.com/docs/languages/markdown" rel="noopener noreferrer"&gt;Visual Studio Code: Markdown&lt;/a&gt;. Mermaid in the built-in preview&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.gitlab.com/user/markdown/" rel="noopener noreferrer"&gt;GitLab Docs: GitLab Flavored Markdown&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://thattoolsite.com/guides/mermaid-diagrams-in-markdown" rel="noopener noreferrer"&gt;ThatToolSite&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>markdown</category>
      <category>documentation</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Convert JSON to TypeScript Interfaces</title>
      <dc:creator>ThatToolSite</dc:creator>
      <pubDate>Sat, 17 Oct 2026 16:10:23 +0000</pubDate>
      <link>https://dev.to/thattoolsite/how-to-convert-json-to-typescript-interfaces-5d1i</link>
      <guid>https://dev.to/thattoolsite/how-to-convert-json-to-typescript-interfaces-5d1i</guid>
      <description>&lt;p&gt;Paste a real sample and generate the interfaces, then check the few things a generator can't know from the data: optional fields, nulls, empty arrays and dates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/json-to-typescript" rel="noopener noreferrer"&gt;JSON to TypeScript&lt;/a&gt; turns sample JSON into interfaces or type aliases, merges every record so optional fields are found, and runs in your browser, so real API responses stay private.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/json-to-typescript" rel="noopener noreferrer"&gt;Convert JSON to TypeScript&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate interfaces from a sample
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Get a real response.&lt;/strong&gt; Copy it from the browser's Network tab, an API client or a log. Real data beats a hand-written example.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Paste it and name the root type.&lt;/strong&gt; Here the root is called Order. Nested objects get names from their keys.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Pick interface or type, and the options.&lt;/strong&gt; Export, readonly, and whether fields that are sometimes null should also be optional.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Copy the result into a .ts file.&lt;/strong&gt; Then fix the few things a generator can't know, below.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For this order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A-1001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"vip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"qty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"B7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"qty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"discount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"placedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-01T09:30:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"notes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"1st-choice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"red"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the generator gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;placedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1st-choice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Customer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;vip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Line&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The array &lt;code&gt;lines&lt;/code&gt; got an interface called &lt;code&gt;Line&lt;/code&gt;, the singular of the key. Because only the second line has a discount, &lt;code&gt;discount&lt;/code&gt; is optional. The key &lt;code&gt;1st-choice&lt;/code&gt; is quoted, since it isn't a valid identifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why one sample isn't enough
&lt;/h2&gt;

&lt;p&gt;A generator can only describe the data it sees. In the order above, &lt;code&gt;notes&lt;/code&gt; is typed as &lt;code&gt;null&lt;/code&gt;, because that's the only value it ever had. In real use it is probably &lt;code&gt;string | null&lt;/code&gt;. Give the generator more than one record, and it merges them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ada@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"manager"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Linus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"manager"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"last-login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-01T09:30:00Z"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserItem&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;last-login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;last-login&lt;/code&gt; are optional, because one record lacks each, and &lt;code&gt;manager&lt;/code&gt; is &lt;code&gt;number | null&lt;/code&gt;. Paste a list of records, or several responses wrapped in &lt;code&gt;[ ]&lt;/code&gt;, to get the same effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nulls, empty arrays, mixed values and dates
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;In the JSON&lt;/th&gt;
&lt;th&gt;Generated type&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"notes": null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Replace with the real type, such as string | null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"items": []&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;unknown[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add a sample with items, or write the item type yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[1, "two", null]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;`(string \&lt;/td&gt;
&lt;td&gt;number \&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;{% raw %}&lt;code&gt;"2026-10-01T09:30:00Z"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JSON has no date type. Convert with new Date() after parsing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;12345678901234567890&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JavaScript rounds this. Ask the API for a string&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;unknown[]&lt;/code&gt; is deliberate: it compiles, but TypeScript makes you check an item before using it, unlike &lt;code&gt;any[]&lt;/code&gt;, which silently allows anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  interface or type?
&lt;/h2&gt;

&lt;p&gt;For object shapes like these, both work and mean almost the same. The TypeScript handbook's advice is to use &lt;code&gt;interface&lt;/code&gt; until you need a feature only &lt;code&gt;type&lt;/code&gt; has, such as unions of different shapes. One practical difference: an interface can be reopened and extended by another declaration with the same name, a type alias can't. If your codebase already uses one style, match it; the generator offers both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types don't check the data at runtime
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;JSON.parse&lt;/code&gt; returns &lt;code&gt;any&lt;/code&gt;. Writing &lt;code&gt;const order: Order = JSON.parse(text)&lt;/code&gt; tells the compiler to trust you; nothing checks that the response really has an &lt;code&gt;orderId&lt;/code&gt;. If the API changes, the error shows up later, somewhere else.&lt;/p&gt;

&lt;p&gt;For data from outside your program, check it once at the edge: a small type guard that tests the fields you rely on, or a schema library such as Zod that validates and gives you the type in one step. The generated interfaces are still useful as the starting point for that schema.&lt;/p&gt;

&lt;p&gt;If the sample won't paste because it isn't valid JSON, which is common with JSON copied from AI answers, run it through the &lt;a href="https://thattoolsite.com/json-validator" rel="noopener noreferrer"&gt;JSON validator&lt;/a&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html" rel="noopener noreferrer"&gt;TypeScript Handbook: Everyday Types&lt;/a&gt;. Differences between type aliases and interfaces, optional properties and unions&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://thattoolsite.com/guides/json-to-typescript-interfaces" rel="noopener noreferrer"&gt;ThatToolSite&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>json</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to Compare Two JSON Files (Ignoring Key Order)</title>
      <dc:creator>ThatToolSite</dc:creator>
      <pubDate>Fri, 16 Oct 2026 16:00:03 +0000</pubDate>
      <link>https://dev.to/thattoolsite/how-to-compare-two-json-files-ignoring-key-order-26b6</link>
      <guid>https://dev.to/thattoolsite/how-to-compare-two-json-files-ignoring-key-order-26b6</guid>
      <description>&lt;p&gt;&lt;a href="https://thattoolsite.com/json-diff" rel="noopener noreferrer"&gt;JSON diff&lt;/a&gt; compares two JSON documents by their data, ignoring key order and formatting, and lists every change by path. Both documents stay in your browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/json-diff" rel="noopener noreferrer"&gt;Compare JSON&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a text diff gives the wrong answer
&lt;/h2&gt;

&lt;p&gt;A JSON object is, in the words of the standard, an unordered collection of names and values. These two documents hold exactly the same data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"editor"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"plan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pro"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"editor"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"plan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A text diff compares characters, so it marks the whole line as changed. Pretty-printed, every line would differ. Real changes hide among fake ones, and a reformatted file looks completely rewritten. Comparing by structure fixes this: each value is matched by its path, such as &lt;code&gt;$.plan&lt;/code&gt; or &lt;code&gt;$.items[2].price&lt;/code&gt;, and only real changes are reported. JSON diff says “No differences” for the pair above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare two JSON files by structure
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Paste or open the two versions.&lt;/strong&gt; The old one on the left, the new one on the right. Both must be valid JSON.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Decide how arrays should be compared.&lt;/strong&gt; Leave order on for ordered lists; tick Ignore the order of array items for sets.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Read the list of changes.&lt;/strong&gt; Each line shows the path, the old value and the new value. Copy the summary for a ticket or a review.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If a file won't parse, fix it first in the &lt;a href="https://thattoolsite.com/json-validator" rel="noopener noreferrer"&gt;JSON validator&lt;/a&gt;, which shows the line and column of the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arrays: an ordered list or a set?
&lt;/h2&gt;

&lt;p&gt;Key order never matters in JSON, but array order can. Take document A from above and this new version, where the plan changed and the tags were listed the other way round:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"plan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"team"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"editor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;JSON diff reports&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Order matters (default)&lt;/td&gt;
&lt;td&gt;3 differences: $.tags[0] “admin” removed, $.tags[1] “admin” added, $.plan “pro” → “team”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ignore the order of array items&lt;/td&gt;
&lt;td&gt;1 difference: $.plan “pro” → “team”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both answers are right; they answer different questions. Tags, permissions and IDs are usually sets, so ignore order. Steps of a process, ranked results or the lines of an order are lists, so keep it. For a list of records that should be matched by an &lt;code&gt;id&lt;/code&gt; field, sort both arrays by that field first, then compare with order on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is 1 the same as "1" or 1.0?
&lt;/h2&gt;

&lt;p&gt;JSON diff compares values by type and by numeric value. That catches the changes that actually break programs, while ignoring ones that don't:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Old&lt;/th&gt;
&lt;th&gt;New&lt;/th&gt;
&lt;th&gt;Reported?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"id": 1&lt;/td&gt;
&lt;td&gt;"id": "1"&lt;/td&gt;
&lt;td&gt;Yes: a number became a string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"zip": "02134"&lt;/td&gt;
&lt;td&gt;"zip": 2134&lt;/td&gt;
&lt;td&gt;Yes: and the leading zero is lost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"total": 31.5&lt;/td&gt;
&lt;td&gt;"total": 31.50&lt;/td&gt;
&lt;td&gt;No: the same number written differently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12345678901234567890&lt;/td&gt;
&lt;td&gt;12345678901234567891&lt;/td&gt;
&lt;td&gt;Yes: compared exactly, not rounded&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row matters for IDs. Many JavaScript-based tools round both numbers to the same value and call them equal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing JSON on the command line
&lt;/h2&gt;

&lt;p&gt;With jq installed, you can sort keys and then use an ordinary diff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;diff &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;jq &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; old.json&lt;span class="o"&gt;)&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;jq &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; new.json&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-S&lt;/code&gt; sorts the keys of every object. It does not sort arrays, so reordered arrays still show up as changes. Sort a list of records by a field with &lt;code&gt;jq 'sort_by(.id)'&lt;/code&gt; before comparing. In Python, &lt;code&gt;json.load(a) == json.load(b)&lt;/code&gt; answers yes or no, ignoring key order, but doesn't say what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timestamps, IDs and other noise
&lt;/h2&gt;

&lt;p&gt;Two API responses often differ in fields that change on every call: &lt;code&gt;updatedAt&lt;/code&gt;, request IDs, tokens. They drown out the changes you care about. Remove them from both documents before comparing, for example with &lt;code&gt;jq 'del(.updatedAt, .requestId)'&lt;/code&gt;, or by deleting the lines by hand in a copy.&lt;/p&gt;

&lt;p&gt;For comparing code, config written as YAML or any other text where order and whitespace matter, use the &lt;a href="https://thattoolsite.com/diff-checker" rel="noopener noreferrer"&gt;diff checker&lt;/a&gt; instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://www.rfc-editor.org/rfc/rfc8259" rel="noopener noreferrer"&gt;RFC 8259, section 1&lt;/a&gt;. An object is an unordered collection of name/value pairs&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://jqlang.org/manual/" rel="noopener noreferrer"&gt;jq manual&lt;/a&gt;. --sort-keys / -S sorts the fields of each object&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://thattoolsite.com/guides/compare-two-json-files" rel="noopener noreferrer"&gt;ThatToolSite&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>json</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>testing</category>
    </item>
    <item>
      <title>What Is Base64? How Base64 Encoding Works</title>
      <dc:creator>ThatToolSite</dc:creator>
      <pubDate>Thu, 15 Oct 2026 16:04:36 +0000</pubDate>
      <link>https://dev.to/thattoolsite/what-is-base64-how-base64-encoding-works-323d</link>
      <guid>https://dev.to/thattoolsite/what-is-base64-how-base64-encoding-works-323d</guid>
      <description>&lt;p&gt;Paste text or Base64 into &lt;a href="https://thattoolsite.com/base64-encode-decode" rel="noopener noreferrer"&gt;Base64 encode and decode&lt;/a&gt;, or drop in an image or file to get its Base64 or a data URI. It runs in your browser, so nothing you paste is sent anywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/base64-encode-decode" rel="noopener noreferrer"&gt;Encode or decode Base64&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Base64? The short answer
&lt;/h2&gt;

&lt;p&gt;Base64 is a way of writing any data, such as an image, a PDF or text in any language, using only 64 safe characters: A to Z, a to z, 0 to 9, plus + and /. It is defined in RFC 4648, the internet standard for it.&lt;/p&gt;

&lt;p&gt;It exists because many systems were built to carry plain text and can damage raw binary data. Email, URLs, JSON and HTTP headers all handle letters and digits reliably. Base64 lets you send a file through them and get the exact same bytes back at the other end.&lt;/p&gt;

&lt;p&gt;“Hi!” in Base64 is SGkh&lt;/p&gt;

&lt;h2&gt;
  
  
  How Base64 encoding works
&lt;/h2&gt;

&lt;p&gt;Computers store data as bytes of 8 bits. Base64 takes 3 bytes at a time (24 bits) and splits them into 4 groups of 6 bits. Six bits can hold a number from 0 to 63, and each number maps to one character:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Values&lt;/th&gt;
&lt;th&gt;Characters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0 to 25&lt;/td&gt;
&lt;td&gt;A to Z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26 to 51&lt;/td&gt;
&lt;td&gt;a to z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;52 to 61&lt;/td&gt;
&lt;td&gt;0 to 9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Encoding “Hi!” step by step:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Characters&lt;/td&gt;
&lt;td&gt;H, i, !&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bytes&lt;/td&gt;
&lt;td&gt;72, 105, 33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In binary&lt;/td&gt;
&lt;td&gt;01001000 01101001 00100001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regrouped in sixes&lt;/td&gt;
&lt;td&gt;010010 000110 100100 100001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;As numbers&lt;/td&gt;
&lt;td&gt;18, 6, 36, 33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;As characters&lt;/td&gt;
&lt;td&gt;S, G, k, h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Decoding runs the same steps backwards, which is why Base64 always gives back exactly what went in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the = signs at the end mean
&lt;/h2&gt;

&lt;p&gt;When the data isn’t a multiple of 3 bytes, the last group is short. Base64 fills the gap with = as padding, so the output is always a multiple of 4 characters:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;th&gt;Base64&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;f&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Zg==&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fo&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Zm8=&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;foo&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Zm9v&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;foob&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Zm9vYg==&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;foobar&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Zm9vYmFy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are the test values from RFC 4648 itself. Two = signs mean the last group held one byte, one = means two bytes, none means three.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much bigger Base64 makes a file
&lt;/h2&gt;

&lt;p&gt;Every 3 bytes become 4 characters, so Base64 is about a third larger than the original:&lt;/p&gt;

&lt;p&gt;Base64 size ≈ original size × 4 ÷ 3&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Original&lt;/th&gt;
&lt;th&gt;As Base64&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;30 KB icon&lt;/td&gt;
&lt;td&gt;about 40 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;300 KB photo&lt;/td&gt;
&lt;td&gt;about 400 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 MB PDF&lt;/td&gt;
&lt;td&gt;about 4 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Email adds a little more, because MIME, the email format, breaks Base64 into lines of at most 76 characters. That is one reason a 20 MB attachment can push an email over a 25 MB limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you see Base64
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Email attachments.&lt;/strong&gt; Files attached to emails travel as Base64 inside the message.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Data URIs.&lt;/strong&gt; A small image can be written straight into HTML or CSS as &lt;code&gt;data:image/png;base64,...&lt;/code&gt; instead of a separate file.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;JSON APIs.&lt;/strong&gt; JSON has no binary type, so files and images are often sent as Base64 strings.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Login headers.&lt;/strong&gt; HTTP Basic authentication sends “username:password” in Base64.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;JSON Web Tokens.&lt;/strong&gt; The three parts of a JWT are base64url-encoded; the &lt;a href="https://thattoolsite.com/jwt-decoder" rel="noopener noreferrer"&gt;JWT decoder&lt;/a&gt; reads them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Base64url, the URL-safe version
&lt;/h2&gt;

&lt;p&gt;The + and / characters have special meanings in URLs and file names. RFC 4648 defines a second alphabet, base64url, that swaps them out:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Base64&lt;/th&gt;
&lt;th&gt;Base64url&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Value 62&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;- (minus)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Value 63&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;td&gt;_ (underscore)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Padding&lt;/td&gt;
&lt;td&gt;=&lt;/td&gt;
&lt;td&gt;Often left off&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a decoder rejects a string full of - and _, it is probably base64url. Swap them back to + and /, add = until the length is a multiple of 4, and it decodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to put an image in Base64
&lt;/h2&gt;

&lt;p&gt;A data URI puts the whole image inside the HTML or CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"data:image/png;base64,iVBORw0KGgo..."&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Logo"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It suits some jobs and not others:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Good for&lt;/strong&gt; small icons, an email signature logo, or a single HTML file that has to work offline with no separate image files.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Bad for&lt;/strong&gt; photos and large images. They become a third bigger, can’t be cached separately from the page, and make the HTML slow to load and hard to edit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Base64 tool turns an image into a ready-made data URI. If the image is large, make it smaller first with &lt;a href="https://thattoolsite.com/compress-image" rel="noopener noreferrer"&gt;compress image&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Base64, hex and Base32 compared
&lt;/h2&gt;

&lt;p&gt;RFC 4648 defines three related encodings. They all turn bytes into text; they differ in size and alphabet:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Encoding&lt;/th&gt;
&lt;th&gt;Characters used&lt;/th&gt;
&lt;th&gt;Size versus the original&lt;/th&gt;
&lt;th&gt;“Hi!” becomes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base16 (hex)&lt;/td&gt;
&lt;td&gt;0 to 9, A to F&lt;/td&gt;
&lt;td&gt;2 times&lt;/td&gt;
&lt;td&gt;486921&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base32&lt;/td&gt;
&lt;td&gt;A to Z, 2 to 7&lt;/td&gt;
&lt;td&gt;1.6 times&lt;/td&gt;
&lt;td&gt;JBUSC===&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base64&lt;/td&gt;
&lt;td&gt;A to Z, a to z, 0 to 9, + and /&lt;/td&gt;
&lt;td&gt;1.33 times&lt;/td&gt;
&lt;td&gt;SGkh&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Hex is easiest to read byte by byte, which is why hashes and colours use it. Base32 avoids lowercase letters and look-alike digits, so it suits codes people type by hand. Base64 is the most compact of the three, which is why it is the usual choice for files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Base64 isn’t encryption
&lt;/h2&gt;

&lt;p&gt;Anyone can decode Base64; there is no key. A password or API key “hidden” in Base64 is as readable as plain text to anyone who sees it. HTTP Basic authentication is only safe over HTTPS for this reason, and a JWT’s contents can be read by anyone who has the token.&lt;/p&gt;

&lt;p&gt;Use Base64 to carry data safely through text-only systems. Use real encryption to keep it secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encoding and decoding Base64
&lt;/h2&gt;

&lt;p&gt;In a terminal on Mac or Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'Hi!'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt;        &lt;span class="c"&gt;# SGkh&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'SGkh'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt;  &lt;span class="c"&gt;# Hi!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In JavaScript, for plain English text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hi!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// "SGkh"&lt;/span&gt;
&lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SGkh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// "Hi!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Python, which works on bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hi!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# b'SGkh'
&lt;/span&gt;&lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SGkh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# b'Hi!'
&lt;/span&gt;&lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hi?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# base64url, with - and _
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;btoa only accepts characters that fit in one byte, so it throws an error on text like “café” or “नमस्ते”. Turn the text into UTF-8 bytes first with TextEncoder, or use a tool that does it for you. The &lt;a href="https://thattoolsite.com/base64-encode-decode" rel="noopener noreferrer"&gt;Base64 tool&lt;/a&gt; handles any language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Base64 won’t decode
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;It’s base64url.&lt;/strong&gt; Contains - or _ instead of + or /.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Missing padding.&lt;/strong&gt; The length isn’t a multiple of 4. Add = signs.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Extra text at the start.&lt;/strong&gt; A data URI begins with &lt;code&gt;data:...;base64,&lt;/code&gt;; remove everything up to and including the comma.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Line breaks or spaces&lt;/strong&gt; from an email or a copied block. Most decoders ignore them; strict ones don’t.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Accents and other scripts come out garbled.&lt;/strong&gt; The bytes decoded correctly but are being read with the wrong character set. Text encoded from UTF-8 has to be read back as UTF-8; a decoder that assumes one byte per character turns “café” into “cafÃ©”.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;It decodes to nonsense.&lt;/strong&gt; The result is a file, not text. Save it with the right extension, such as .png or .pdf, instead of reading it as text.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://www.rfc-editor.org/rfc/rfc4648" rel="noopener noreferrer"&gt;RFC 4648: The Base16, Base32, and Base64 Data Encodings&lt;/a&gt;. The Base64 alphabet, padding, base64url and the test vectors&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.rfc-editor.org/rfc/rfc2045" rel="noopener noreferrer"&gt;RFC 2045: MIME Part One&lt;/a&gt;. Base64 in email, lines of at most 76 characters&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.rfc-editor.org/rfc/rfc2397" rel="noopener noreferrer"&gt;RFC 2397: The “data” URL scheme&lt;/a&gt;. data: URIs&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.rfc-editor.org/rfc/rfc7617" rel="noopener noreferrer"&gt;RFC 7617: The “Basic” HTTP Authentication Scheme&lt;/a&gt;. user-id:password sent in Base64&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa" rel="noopener noreferrer"&gt;MDN: btoa()&lt;/a&gt;. Throws on characters outside a single byte&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://thattoolsite.com/guides/what-is-base64" rel="noopener noreferrer"&gt;ThatToolSite&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Fix Common JSON Syntax Errors (Incl. AI Output)</title>
      <dc:creator>ThatToolSite</dc:creator>
      <pubDate>Tue, 13 Oct 2026 16:00:06 +0000</pubDate>
      <link>https://dev.to/thattoolsite/how-to-fix-common-json-syntax-errors-incl-ai-output-16gb</link>
      <guid>https://dev.to/thattoolsite/how-to-fix-common-json-syntax-errors-incl-ai-output-16gb</guid>
      <description>&lt;p&gt;The &lt;a href="https://thattoolsite.com/json-validator" rel="noopener noreferrer"&gt;JSON validator&lt;/a&gt; shows the exact line and column of the first error, explains it in plain words, and can repair the usual mistakes. Your JSON never leaves the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thattoolsite.com/json-validator" rel="noopener noreferrer"&gt;Check your JSON&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the error message, then look one line up
&lt;/h2&gt;

&lt;p&gt;A JSON parser reads from the top and stops at the first character that can't be right. The line and column it reports are where it stopped, which is often just after the real mistake. In this example the comma is missing at the end of line 3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Report"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The validator reports &lt;strong&gt;line 4, column 3: Missing comma between two properties&lt;/strong&gt;, because it only knows something is wrong when it meets &lt;code&gt;"pages"&lt;/code&gt;. So when the reported line looks fine, check the end of the line before it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistakes behind most invalid JSON
&lt;/h2&gt;

&lt;p&gt;JSON is stricter than the JavaScript or Python it resembles. RFC 8259, the JSON standard, allows only double-quoted strings, lowercase &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt; and &lt;code&gt;null&lt;/code&gt;, and commas only between items.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mistake&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Trailing comma&lt;/td&gt;
&lt;td&gt;["a", "b",]&lt;/td&gt;
&lt;td&gt;Delete the comma before ] or }&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single quotes&lt;/td&gt;
&lt;td&gt;{'name': 'Ada'}&lt;/td&gt;
&lt;td&gt;Use double quotes for keys and strings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key without quotes&lt;/td&gt;
&lt;td&gt;{name: "Ada"}&lt;/td&gt;
&lt;td&gt;Quote every key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comments&lt;/td&gt;
&lt;td&gt;// settings&lt;/td&gt;
&lt;td&gt;Remove them; JSON has no comments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python values&lt;/td&gt;
&lt;td&gt;True, None&lt;/td&gt;
&lt;td&gt;true, null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Curly quotes&lt;/td&gt;
&lt;td&gt;“hello”&lt;/td&gt;
&lt;td&gt;Straight double quotes, " … "&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Numbers JSON can't read&lt;/td&gt;
&lt;td&gt;.5, 007, NaN&lt;/td&gt;
&lt;td&gt;0.5, "007" (as text, to keep the zeros), null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing comma&lt;/td&gt;
&lt;td&gt;"a": 1 "b": 2&lt;/td&gt;
&lt;td&gt;Add a comma between items&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Fixing JSON from ChatGPT, Claude or Gemini
&lt;/h2&gt;

&lt;p&gt;JSON copied from an AI answer has its own typical problems, on top of the ones above. The answer wraps the JSON in a Markdown code fence and a sentence, and models trained on JavaScript slip in trailing commas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Here&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JSON:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;```json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"math"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;,],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;```&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A strict parser stops at the very first character, the H of “Here”. Try to fix in the validator removes the sentence and the fence and deletes the two trailing commas, leaving valid JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"math"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other common AI problem is a reply that stops mid-way because it reached its length limit. The JSON then ends inside a string or without its closing brackets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check what an automatic repair changed
&lt;/h2&gt;

&lt;p&gt;Repairs are guesses, and most are safe: removing a comment or a trailing comma can't change your data. Two kinds need a human look. Take this cut-off reply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lamp"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cab
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try to fix closes the string and the brackets, so the result is valid, and lists both changes. But the second item now has the name &lt;code&gt;"Cab"&lt;/code&gt;, and whatever came after it is gone. Valid is not the same as complete. Ask the assistant to continue, or to send the JSON again in smaller parts.&lt;/p&gt;

&lt;p&gt;The second kind is a missing comma or quote in the middle of a document, where the repair has to decide where one value ends and the next begins. Read the list of changes, and compare the item count with what you expected. If you need to see exactly what moved, put the original and the repaired version side by side in &lt;a href="https://thattoolsite.com/json-diff" rel="noopener noreferrer"&gt;JSON diff&lt;/a&gt; once both parse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Valid JSON that a program still rejects
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Duplicate keys.&lt;/strong&gt; The standard says names should be unique but doesn't forbid repeats. JavaScript and Python keep the last value without warning. The validator flags them.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;A byte order mark.&lt;/strong&gt; The standard says JSON sent between systems must not start with one, and Python's &lt;code&gt;json.loads&lt;/code&gt; refuses a string that starts with one. Save the file as UTF-8 without a BOM.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Big numbers.&lt;/strong&gt; IDs above 9,007,199,254,740,991 lose their last digits in JavaScript. Send them as strings.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Lenient parsers.&lt;/strong&gt; Python accepts &lt;code&gt;NaN&lt;/code&gt; and &lt;code&gt;Infinity&lt;/code&gt; by default, so a file can work in one program and fail in another. Check against the standard, not one parser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once it's valid, the &lt;a href="https://thattoolsite.com/json-formatter" rel="noopener noreferrer"&gt;JSON formatter&lt;/a&gt; lays it out with indentation so it's easy to read and review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://www.rfc-editor.org/rfc/rfc8259" rel="noopener noreferrer"&gt;RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.python.org/3/library/json.html" rel="noopener noreferrer"&gt;Python documentation: json&lt;/a&gt;. NaN, duplicate keys and the BOM&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://thattoolsite.com/guides/fix-json-syntax-errors" rel="noopener noreferrer"&gt;ThatToolSite&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>json</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Count Your Tokens Before You Paste: Practical LLM Token Math</title>
      <dc:creator>ThatToolSite</dc:creator>
      <pubDate>Tue, 13 Oct 2026 06:10:27 +0000</pubDate>
      <link>https://dev.to/thattoolsite/count-your-tokens-before-you-paste-practical-llm-token-math-4j5b</link>
      <guid>https://dev.to/thattoolsite/count-your-tokens-before-you-paste-practical-llm-token-math-4j5b</guid>
      <description>&lt;p&gt;You paste a long doc into ChatGPT, Claude or Gemini and one of three things happens: the answer ignores the end of it, you hit a context limit, or the API bill is bigger than you expected. All three come down to the same number: &lt;strong&gt;tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's the practical version of token maths: what a token is, quick estimates you can do in your head, how to count exactly in a few lines of code, and how to work out what a prompt will cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a token actually is
&lt;/h2&gt;

&lt;p&gt;Models don't read characters or words. A tokenizer splits text into chunks from a fixed vocabulary, and the model sees a list of chunk IDs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Common English words are usually &lt;strong&gt;one token&lt;/strong&gt;: &lt;code&gt;the&lt;/code&gt;, &lt;code&gt;house&lt;/code&gt;, &lt;code&gt;token&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Longer or rarer words get &lt;strong&gt;split&lt;/strong&gt;: &lt;code&gt;tokenization&lt;/code&gt; → &lt;code&gt;token&lt;/code&gt; + &lt;code&gt;ization&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Spaces and punctuation count too. A leading space is often part of the next token (&lt;code&gt;world&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Numbers, code, URLs and non-Latin scripts use &lt;strong&gt;more tokens per character&lt;/strong&gt; than plain English.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the one that surprises people. The same sentence in Hindi, Japanese or Arabic can take two or three times the tokens of its English version, and minified JSON or a stack trace is far denser than prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules of thumb (good enough for a quick check)
&lt;/h2&gt;

&lt;p&gt;For English prose with OpenAI's current tokenizers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1 token ≈ 4 characters&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 token ≈ ¾ of a word&lt;/strong&gt;, so &lt;strong&gt;100 words ≈ 130 tokens&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A dense A4 page of text ≈ &lt;strong&gt;600–800 tokens&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other providers publish their own rough ratios, and they differ a little because each model family has its own tokenizer. Treat these as estimates; for anything that matters, count.&lt;/p&gt;

&lt;h2&gt;
  
  
  Count exactly in code
&lt;/h2&gt;

&lt;p&gt;OpenAI's tokenizers are open source. In JavaScript, &lt;code&gt;js-tiktoken&lt;/code&gt; runs anywhere, including the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Tiktoken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;js-tiktoken/lite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;o200k_base&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;js-tiktoken/ranks/o200k_base&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Tiktoken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o200k_base&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Count your tokens before you paste.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// number of tokens&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;o200k_base&lt;/code&gt; is the encoding used by GPT-4o and newer OpenAI models; older ones like GPT-4 and GPT-3.5 use &lt;code&gt;cl100k_base&lt;/code&gt;. In Python it's the &lt;code&gt;tiktoken&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;

&lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o200k_base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Count your tokens before you paste.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Claude and Gemini the tokenizers aren't published, but both APIs have a token-counting endpoint (Anthropic's &lt;code&gt;count_tokens&lt;/code&gt; for messages, Gemini's &lt;code&gt;countTokens&lt;/code&gt;) if you need an exact number before sending a request.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a prompt will cost
&lt;/h2&gt;

&lt;p&gt;Every API price list is per &lt;strong&gt;million tokens&lt;/strong&gt;, and input and output are priced separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cost = input_tokens  / 1,000,000 × input_price
     + output_tokens / 1,000,000 × output_price
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worked example with made-up round prices, so the maths is easy to follow: say input costs &lt;strong&gt;$2&lt;/strong&gt; per million and output &lt;strong&gt;$8&lt;/strong&gt; per million. A 3,000-token prompt with a 500-token answer costs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3,000 / 1,000,000 × $2 = $0.006
  500 / 1,000,000 × $8 = $0.004
                 total = $0.010 per request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One cent sounds like nothing until it runs 50,000 times a day. Two things people forget:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Chat history is input every time.&lt;/strong&gt; In a long conversation, every new message resends the whole thread, so input tokens grow with each turn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output is usually the expensive side.&lt;/strong&gt; Asking for "brief" answers or a fixed format often saves more than trimming the prompt.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Five ways to cut tokens without losing quality
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Strip HTML and boilerplate&lt;/strong&gt; before you paste a web page. Navigation, footers and tags are pure token waste.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minify or trim JSON&lt;/strong&gt;: drop fields the model doesn't need instead of sending the whole API response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summarise old context.&lt;/strong&gt; Replace the first 20 messages of a long thread with a five-line summary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't repeat instructions&lt;/strong&gt; in every message; put them once in the system prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check non-English and code-heavy inputs&lt;/strong&gt;, because they're where token counts surprise you most.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A quick way to check without writing code
&lt;/h2&gt;

&lt;p&gt;If you just want to paste something and see the count, I made a free &lt;a href="https://thattoolsite.com/token-counter?utm_source=devto&amp;amp;utm_medium=article" rel="noopener noreferrer"&gt;token counter&lt;/a&gt; that runs entirely in your browser, so nothing you paste is uploaded. It gives exact counts for OpenAI's tokenizers, clearly labelled estimates for Claude and Gemini, and a cost calculator with a dated, editable price table.&lt;/p&gt;

&lt;p&gt;What's the biggest token surprise you've run into: a huge chat history, a non-English document, or a JSON blob that ate the whole context window?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>openai</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
