Save chatGPT conversation as HTML file
Jakub T. Jankiewicz

Jakub T. Jankiewicz @jcubic

About: JavaScript expert, mentor, and Open Source developer

Location:
Poland
Joined:
Jul 4, 2018

Save chatGPT conversation as HTML file

Publish Date: Dec 3 '22
42 41

I've started to play with chatGPT from OpenAI and wanted to save the conversation. My first attempt was using GoFullScreen browser extension but the output file didn't look good. So I've figure out that I create some code that will download the file in HTML format.

TL;DR at the end there is bookmarklet that you can copy.

So first we need is to grab div with the thread. It's nice that it's ReactJS with styled components so beginning of the component is nice class name the same as component name.

This is the HTML:

DOM Tree of the ReactJS OpenAI website

NOTE: this is a view of the DOM in Google Dev Tools (to open right click anywhere on the page and in context menu pick "inspect element").

as you can see the class starts with "ThreadLayout__NodeWrapper" so to select that element you need this code:



const html = document.querySelector('[class^="ThreadLayout__NodeWrapper"]').innerHTML;


Enter fullscreen mode Exit fullscreen mode

It's attribute selector that match beginning of the text.

Next is grabbing the HTML of that element and converting it to blob. As I never remember how to convert String to blob (you don't have to remember this stuff if you don't do them often). I did a Google Search and found javascript.info article about blob it looks like this:



const blob = new Blob([html], {type: 'text/html'});


Enter fullscreen mode Exit fullscreen mode

If you ask why we need blob object, it's because we want to convert that to URL, we could use Data URI, but the content may be too big for a URL. So we need to create what so called Object URL. As with Blob I didn't remembered what was the API, so I've searched "Blob to URL JavaScript" and first result was MDN and createObjectURL. So we need to do:



const url = URL.createObjectURL(blob);


Enter fullscreen mode Exit fullscreen mode

So now if we inspect the url you will see something like this:



blob:https://chat.openai.com/8d0a27ec-d8c6-4a76-9aad-4a0e52b0f9d0


Enter fullscreen mode Exit fullscreen mode

It's object created from blob in memory and if you use this browser will use it as reference to HTML content we created.

To download the file there is this trick that I keep using, if you know better way let me know.



function download(url, filename) {
   const a = document.createElement('a');
   a.href = url;
   a.download = filename;
   // we need add link to body since some browsers
   // will ignore the download otherwise
   document.body.appendChild(a);
   a.click();
   // you can also use a.remove(); but I prefer older API
   document.body.removeChild(a);
}


Enter fullscreen mode Exit fullscreen mode

and we can call this function with blob URL and filename:



download(url, 'chatGPT.html');


Enter fullscreen mode Exit fullscreen mode

This will create fake link and download the file.

Last think that you want to do is to clean after yourself and remove blob URL, since after download you don't need it anymore.



URL.revokeObjectURL(url);


Enter fullscreen mode Exit fullscreen mode

As the end as I promise whole code as Bookmarklet (NOTE: the selector doesn't work anymore, but I've created repository on GitHub where I will track working version of the bookmark if you're interested).



javascript:(function() {
  const a = document.createElement('a');
  a.href = URL.createObjectURL(new Blob([document.querySelector('[class^="ThreadLayout__NodeWrapper"]').innerHTML], {type: 'text/html'}));
  a.download = 'chatGPT.html';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(a.href);
})()


Enter fullscreen mode Exit fullscreen mode

You need to save this as URL into your bookmarks that you will be able to click and invoke that code. If you want to copy/paste into console, than just remove the javascript: at the beginning, it's special protocol that tell the browser to run this code.

As a fun exercise I've tried to make chatGPT create this code for me. Here is the result the solution is almost the same. He only made mistake in CSS selector, but pointing that out and he fixed the code. Above file was download using copy paste of the last snippet.

EDIT: look at my comment where you can find a bookmark that make the conversation with chatGPT look better.

EDIT 2: I just found that they changed the classes on ChatGPT output, so this doesn't work anymore, I wanted to update the code in the article but it will probably change multiple times in the future, so I've created a repository for the boomark:

GitHub logo jcubic / chat-gpt

ChatGPT conversation saving bookmark

chatGPT Bookmark

ChatGPT conversation saving bookmark.

It's started as DEV.to article But I've decided that It would be better to track it on GitHub and update so it will still work when OpenAI change their web application.

What is Bookmarklet?

Bookmarklet is a JavaScript code that run as URL from your bookmarks. To run the code you need to create new Bookmark and copy the code from the bookmark.js JavaScript file. If you don't know how you can open the website and drag & drop the look to your bookmarks.

You can read more about Bookmarklets on Wikipedia.

Interesting projects

The main reason for this project is to allow saving the chatGPT conversation as files on the disk. Here is interesting usage of the bookmark that people made:

Contribution

If you want to add something to the bookmark, please do…

When they will change the ReactJS application I will try to update the bookmark. So if the bookmark doesn't work please create an issue on GitHub.

And that's it. If you like this post, you can follow me on twitter at @jcubic and check my home page.

Comments 41 total

  • Jordi Hermoso
    Jordi HermosoDec 3, 2022

    Cheers for the article!
    This version removes: svg, img and button tags, cleaning up the output a bit more :)

    javascript:(function() { const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([document.querySelector('[class^="ThreadLayout__NodeWrapper"]').innerHTML.replace(/<img[^>]*>/g, '').replace(/<button[^>]*>.*?<\/button>/g, '').replace(/<svg[^>]*>.*?<\/svg>/g, '')], {type: 'text/html'})); a.download = 'chatGPT.html'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); })()
    
    Enter fullscreen mode Exit fullscreen mode
    • Jakub T. Jankiewicz
      Jakub T. JankiewiczDec 3, 2022

      Thanks for the follow up. I actually didn't care and only wanted to save the conversation for later. Something that could be processed when needed. Like cleaning up, adding HTML structure, and adding CSS so it looks good.

  • Aggermor
    AggermorDec 4, 2022

    New to this website just for this post. I do not understand how this works in the slightest but I enjoy being able to save my chatGPT now. Is it possible to make my messages a different color to the chatbot? for example making my input text blue... and the AI's response black text. also idk if it's too hard to add the code box instead of just plain text. so it can show formatting. thanks, this is awesome.

    • Jakub T. Jankiewicz
      Jakub T. JankiewiczDec 4, 2022

      TL;DR at the end you have the full code

      You need a little bit of CSS knowledge if you want to make it looks good. But if you want quick tip add this CSS:

      [class^="ConversationItem__ConversationItemWrapper-sc"]:nth-child(2n+1) {
          background: lightgray;
      }
      [class^="ConversationItem__ConversationItemWrapper-sc"]:nth-child(2n+2) {
          background: darkgray;
      }
      
      Enter fullscreen mode Exit fullscreen mode

      and if you want to make little bit nicer you can add

      [class^="ConversationItem__ConversationItemWrapper-sc"] {
          padding: 10px;
          margin: 10px;
          border-radius: 5px;
      }
      
      Enter fullscreen mode Exit fullscreen mode

      it will make prompts light gray and bot reply dark gray and add some spacing. You can customize it bit frther if you spend some time learning basic of CSS.

      To update the bookmark you need to put that inside <style>...</style>.

      Another tip if you ask for source code, you add this line:

      <link rel="stylesheet"
            href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.7.0/build/styles/default.min.css"/> 
      
      Enter fullscreen mode Exit fullscreen mode

      To make the source code looks nicer.

      Here is whole bookmark including @jordiup updates (I've also added HTML structure so it's valid HTML document).

      javascript:(function() {
        const a = document.createElement('a');
        a.href = URL.createObjectURL(new Blob([`<!DOCTYPE html><html><head><style>
      [class^="ConversationItem__ConversationItemWrapper-sc"]:nth-child(2n+1) {
          background: lightgray;
      }
      [class^="ConversationItem__ConversationItemWrapper-sc"]:nth-child(2n+2) {
          background: darkgray;
      }
      [class^="ConversationItem__ConversationItemWrapper-sc"] {
          padding: 10px;
          margin: 10px;
          border-radius: 5px;
      }
      </style>
      <link rel="stylesheet"
            href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.7.0/build/styles/default.min.css"/>
      </head>
      <body>`+  document.querySelector('[class^="ThreadLayout__NodeWrapper"]').innerHTML
        .replace(/<img[^>]*>/g, '')
        .replace(/<button[^>]*>.*?<\/button>/g, '')
        .replace(/<svg[^>]*>.*?<\/svg>/g, '') + '</body></html>'], {type: 'text/html'}));
        a.download = 'chatGPT.html';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(a.href);
      })()
      
      Enter fullscreen mode Exit fullscreen mode

      And if you want to give a filename you can change this line:

      a.download = 'chatGPT.html';
      
      Enter fullscreen mode Exit fullscreen mode

      into:

      a.download = prompt('Enter filename');
      
      Enter fullscreen mode Exit fullscreen mode
      • Aggermor
        AggermorDec 4, 2022

        This is exciting and I've learned so much about CSS just working out how this code works, now you've even improved upon it. Thank you so much.

  • Enzi
    EnziDec 5, 2022

    Update: 12/11/22 Check out github.com/jcubic/chat-gpt
    Can check out my fork for Fancy CSS - V1 on github.com/EnziTheViking/chat-gpt-...

    "Thank you, Jakub T. Jankiewicz, for sharing this valuable information in your article. Their post was incredibly helpful, and I made some modifications to the code with the help of ChatGPT.

    I hope that you find this slight CSS change to be useful. It is worth keeping in mind that there may be some unique identifiers in the classes, so it may be necessary to do some research, such as with the classes Avatar__Wrapper-sc-1yo2jqv-3 hQqhqY and hljs.

    EDIT: Looks like they have changed how the CSS Classes are named, so this code below wont work. (Check out OP github)

    javascript: (function () {
      const a = document.createElement("a");
      a.href = URL.createObjectURL(
        new Blob(
          [
            `<!DOCTYPE html><html><head><style>
    [class^="ConversationItem__ConversationItemWrapper-sc"]:nth-child(2n+1) {
        background: lightgray;
    }
    [class^="ConversationItem__ConversationItemWrapper-sc"]:nth-child(2n+2) {
        background: darkgray;
    }
    [class^="ConversationItem__ConversationItemWrapper-sc"] {
        padding: 10px;
        margin: 10px;
        border-radius: 5px;
    }
    [class^="CodeSnippet__CodeContainer-sc"] {
        background: #0D0D0D;
        padding: 10px;
        border-radius: 5px;
    }
    [class^="hljs"] {
        font-weight: !important;
    }
    [class^="hljs-comment"] {
        color: #DAD9D8 !important;
    }
    [class^="hljs-keyword"] {
        color: #4CA3D8 !important;
    }
    [class^="hljs-params"] {
        color: #ff6c87 !important;
    }
    [class^="hljs-variable language_"] {
        color: #E24B8A !important;
    }
    [class^="hljs-title function_"] {
        color: #F24554 !important;
    }
    [class^="hljs-string"] {
        color: #56FEC1 !important;
    }
    [class^="hljs-property"] {
        color: #FFFFFF !important;
    }
    [class^="hljs-built_in"] {
        color: #F3AC35 !important;
    }
    [class^="hljs-attribute"] {
        color: #60FED7 !important;
    }
    [class^="hljs-attr"] {
        color: #E24B8A !important;
    }
    [class^="hljs-regexp"] {
        color: #dad9d8 !important;
    }
    [class^="hljs-selector-attr"] {
        color: #E24B8A !important;
    }
    [class^="hljs-selector-pseudo"] {
        color: #E24B8A !important;
    }
    [class^="hljs-number"] {
        color: #dad9d8 !important;
    }
    [class^="Avatar-sc"] {
        background-color: darkgray !important;
        margin: 2px !important;
        padding: 2px !important;
        border-radius: 5px !important;
    }
    [class^="h3"]{
        margin: 2px !important;
        padding: 2px !important;
        border-radius: 5px !important;
        color: #cc1a58;
    }
    .h3 {
        text-align: left;
    }
    [class^="h3svg"]{
        margin: 2px !important;
        padding: 2px !important;
        border-radius: 5px !important;
        color: #19886D;
    }
    </style>
    <body>` +
              document
                .querySelector('[class^="ThreadLayout__NodeWrapper"]')
                .innerHTML.replace(
                  /<div class="Avatar__Wrapper-sc-1yo2jqv-3 hQqhqY">(.*?)<\/div>/g,
                  '<h3 class="h3">You</h3>'
                )
    
                .replace(/<button[^>]*>.*?<\/button>/g, "")
                .replace(/<svg[^>]*>.*?<\/svg>/g, '<h3 class="h3svg">Bot</h3>') +
              "</body></html>",
          ],
          { type: "text/html" }
        )
      );
      const date = new Date();
      const dateString =
        date.getMonth() + 1 + "/" + date.getDate() + "/" + date.getFullYear();
      a.download = "chatGPT Save Chat -" + dateString + ".html";
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(a.href);
    })();
    
    Enter fullscreen mode Exit fullscreen mode

    Random quick example for an image
    Image description

    • Jakub T. Jankiewicz
      Jakub T. JankiewiczDec 5, 2022

      Looks nice.

      • James Ratcliff
        James RatcliffJan 3, 2023

        can we merge in Enzi changes? Need the better css for our projects, his seems to work great!

        • Jakub T. Jankiewicz
          Jakub T. JankiewiczJan 4, 2023

          @falazar he didn't created a fork and a PR. So there is nothing to merge. if he decide to create a PR to my project, I will be happy to merge.

    • Aggermor
      AggermorDec 5, 2022

      Once again I can barely even read how this works, let alone understand it. I just did a simple google search to find my way into this world of you smart guys. This looks impressive however, it's not working for me. Maybe it's the unique identifiers in the classes you mentioned... I have no clue what that is anyway. When I put it in my bookmark bar like the original post it doesn't work. Could you help me understand how to fix it?

      Edit: Actually now the original post isn't working for me either.. any suggestions?

    • Jakub T. Jankiewicz
      Jakub T. JankiewiczDec 10, 2022

      Hey, I've created a repository for the bookmark. It seems they changed how they structure the conversation. So your code will probably not work anymore. But if you want to contribute your changes you're welcome to do that. We can make it work like YouTube-DL or uBlock that updates they code when YouTube changes the application.

    • Enzi
      EnziDec 11, 2022

      Fancy CSS - V1 on github.com/EnziTheViking/chat-gpt-...

      javascript:(function() {
          const a = document.createElement('a');
          a.href = URL.createObjectURL(new Blob([
              `<!DOCTYPE html><html><head>
              <style>
          [class^="react-scroll-to-bottom"] > .flex > div:nth-child(2n+1) {
              background: #3B3F41;
              white-space: pre-wrap;
          }
          [class^="react-scroll-to-bottom"] > .flex > div:nth-child(2n+2) {
              background: #52575A;
              white-space: pre-wrap;
          }
          [class^="react-scroll-to-bottom"] > .flex > div:not(:last-child) {
              padding: 10px;
              margin: 10px;
              border-radius: 5px;
          }
          [class^="react-scroll-to-bottom"] > .flex > div .relative.flex [style*="inline-block"] {
              display: none !important;
          }
          p:first-child {
              margin-top: 0;
          }
          p:last-child {
              margin-bottom: 0;
          }
          body {
              color: #D8D7D4 !important;
              background: #232526;
              font-family: Arial, sans-serif !important;
          }
          [class^="p"] {
              font-weight: !important;
              background: #161717 !important;
              padding: 5px;
              border-radius: 5px;
          }
          [class^="hljs"] {
              font-weight: bold !important;
          }
          [class^="hljs-comment"], [class^="hljs-regexp"] {
              color: #DAD9D8 !important;
          }
          [class^="hljs-keyword"] {
              color: #4CA3D8 !important;
          }
          [class^="hljs-params"] {
              color: #ff6c87 !important;
          }
          [class^="hljs-variable language_"] {
              color: #E24B8A !important;
          }
          [class^="hljs-title function_"] {
              color: #F24554 !important;
          }
          [class^="hljs-string"] {
              color: #56FEC1 !important;
          }
          [class^="hljs-property"] {
              color: #FFFFFF !important;
          }
          [class^="hljs-built_in"] {
              color: #F3AC35 !important;
          }
          [class^="hljs-attribute"] {
              color: #60FED7 !important;
          }
          [class^="hljs-attr"], [class^="hljs-selector-attr"], [class^="hljs-selector-pseudo"] {
              color: #E24B8A !important;
          }
          [class^="hljs-number"] {
              color: #dad9d8 !important;
          }
          [class^="bot"]{
              color: #10A37F;
              background-color: #52575A !important;
          }
          [class^="you"]{
              color: #cc1a58;
          }
        </style>
        </head>
        <body>`+ document.querySelector('[class^="react-scroll-to-bottom"]').innerHTML
          .replace(/<img[^>]*>/g, '')
      
          .replace(
              /<div class="relative flex">(.*?)<\/div>/g,
              '<h3 class="you">You</you>'
            )
          .replace(/<button[^>]*>.*?<\/button>/g, '')
          .replace(/<svg[^>]*>.*?<\/svg>/g, '<h3 class="bot">Bot</h3>') + '</body></html>'], {type: 'text/html'}));
          a.download = 'chatGPT.html';
          document.body.appendChild(a);
          a.click();
          document.body.removeChild(a);
          URL.revokeObjectURL(a.href);
        })()
      
      Enter fullscreen mode Exit fullscreen mode

      Image description

  • TashaCourtney
    TashaCourtneyDec 5, 2022

    How do you end a chatbot conversation ? love spell to bring back ex

    • Jakub T. Jankiewicz
      Jakub T. JankiewiczDec 5, 2022

      I usually just close the tab or start over using new tread. You don't need to be polite and say "thank you, see you next time" or sully stuff like this.

    • Pionxzh
      PionxzhDec 5, 2022

      There is a button called "Reset Thread" on the left sidebar.

  • Pionxzh
    PionxzhDec 5, 2022

    I just create a userscript to export the conversation.
    Support pure text and html and Screenshot.
    github.com/pionxzh/chatgpt-exporter

    Image description

    • Aggermor
      AggermorDec 5, 2022

      This looks awesome. You guys are so smart! Again, I stumbled upon this thread just looking for ways to save my cGPT conversations. I'm not skilled enough to even read how this works but I wish I could. So no offense to @pionxzh but I feel I shouldn't run random code I can't read, does anyone vouch for this script?

      • Pionxzh
        PionxzhDec 6, 2022

        You shouldn't. That's why all of the code are open sourced on GitHub pionxzh/chatgpt-expoter and the script was built by CI automatically to make sure that I don't have any chance to put something in.

  • atmadeep Das
    atmadeep DasDec 9, 2022

    I asked this application to write down the same code and it's crazy. It is also updated with latest information. Later on figured out - it's connected with Search engines. Crazy application - accintia.com

    • Jakub T. Jankiewicz
      Jakub T. JankiewiczDec 9, 2022

      It's not connected to Search Engines it was trained in 2021 and only those information are save in the model. I also asked chatGPT to write same code after I've had my solution. With this I could ask for corrections when something was wrong. His first code showed document.querySelector('.ThreadLayout__NodeWrapper') and I've needed to ask him to correct it, he corrected it and in next prompt I've asked to write full code with correction and I've had exact same code.

  • АнонимDec 11, 2022

    [deleted]

    • Jakub T. Jankiewicz
      Jakub T. JankiewiczDec 11, 2022

      What initials, what nickname? Can you elaborate?

      • АнонимDec 12, 2022

        [deleted]

        • Jakub T. Jankiewicz
          Jakub T. JankiewiczDec 12, 2022

          They probably changed the application it have nothing to do with my bookmark. It don't write anything, especially when you refresh the page. You can't modify a website permanently with a boomkark.

  • Pitter Packer
    Pitter PackerDec 15, 2022

    How many languages ​​can chatbots understand?

  • jkapron
    jkapronDec 25, 2022

    It just stopped working doe me :(

    • Jakub T. Jankiewicz
      Jakub T. JankiewiczDec 28, 2022

      You need to use bookmark from GitHub the link at the bottom of the page. If it doesn't work please create an issue on GitHub.

      EDIT: I've just checked GitHub version and it works.

  • lorenz1989
    lorenz1989Dec 27, 2022

    The chatGPT Chrome extension makes it possible to get the answer to your Google search without leaving the page – just install it and try it out.

    https://chrome.google.com/webstore/detail/chatgpt-for-search-engine/feeonheemodpkdckaljcjogdncpiiban/related?hl=en-GB&authuser=0

    • Jakub T. Jankiewicz
      Jakub T. JankiewiczDec 28, 2022

      I was testing the extension (or similar one) and I don't like it.

      • lorenz1989
        lorenz1989Dec 28, 2022

        What do you not like about it?

        • Jakub T. Jankiewicz
          Jakub T. JankiewiczDec 28, 2022

          It's useless to me, I don't see the point of having something like when searching. But it may be useful to others, for me it's not. I've installed it and deleted after first search.

          • Jakub T. Jankiewicz
            Jakub T. JankiewiczDec 28, 2022

            If you want to promote this project, why don't you write an article about it instead of adding comments to my post?

  • Pitter Packer
    Pitter PackerDec 29, 2022

    Thank for sharing. I just found out about GPT and it's been helping me a lot in my work and learning. I also just discovered an extension for search engines. It's very convenient, everyone should try installing it and see how it goes. You need to log in to the main OpenAI page and use it on search engines. https://chrome.google.com/webstore/detail/chatgpt-for-search-engine/feeonheemodpkdckaljcjogdncpiiban/related?hl=en-GB&authuser=0

    Image description

  • jgusta
    jgustaJan 8, 2023

    Thank you for this, Jakub. Your github repo is exactly what I needed and in a format that is perfect for me. Nice work.

  • gader-abdollah
    gader-abdollahJan 25, 2023

    You can also just simply right click and click on 'Save Page As'. A download prompt should appear asking you to name the file:

    Image description

    Image description

    • Jakub T. Jankiewicz
      Jakub T. JankiewiczJan 27, 2023

      You can't save conversation with "save as.." becase the conversation is not written in HTML. And save as save the HTML file not the thing you see on the screeen. Please open the file and look what you will get.

  • chatgptaihub
    chatgptaihubJun 14, 2023

    I found a super useful guide on how any can save chatgpt threads data easily and free. here are some methods.

    1. - Copy and Paste: Manually copy the conversation text and paste it into a document or application.
    2. - SaveGPT Extension: Use the SaveGPT extension in Google Chrome to automatically save ChatGPT conversations.
    3. - Third-Party Applications: Utilise automation platforms like Zapier, Integromat, or Automate.io to save ChatGPT conversations programmatically.
    4. - ChatGPT API: Access and save ChatGPT conversations programmatically using the ChatGPT API.
    5. - Screenshot: Capture the ChatGPT conversation by taking a screenshot.
    • Jakub T. Jankiewicz
      Jakub T. JankiewiczJun 16, 2023

      How did you found it if it's written by you? This looks like SEO spam.

  • daviddonald
    daviddonaldApr 30, 2024

    Thanks a bunch, Sarah! Your comprehensive guide really hit the mark for what I was looking for archive. I appreciate the effort you put into making it so user-friendly. Great job!

Add comment