Lupine.dev (Wolfy's Website)
Illustration of Miku the bird-fox shouting through a megaphone while flying in the sky

Export oMLX Chats as Markdown (Bookmarklet)

Sometimes you want to keep an archival copy of a local oMLX Chat conversation. The oMLX web app doesn’t have a built-in export feature, so I made one.

This bookmarklet grabs the live data from an oMLX chat page, formats each turn with "# User" and "# Assistant" headers separated by horizontal rules, then triggers a browser download as chat-[currentChatId].md.

Automatic Installation

For most browsers, you can simply drag the link below directly onto your bookmarks bar:

Export oMLX Chat

Manual Installation

Alternatively, here’s the bookmarklet code, ready for manual installation:

!function(){const e=document.querySelector("[x-html]").closest("[x-data]"),t=Alpine.$data(e);!function(e,t){const n=new Blob([e],{type:"text/markdown;charset=utf-8"}),o=URL.createObjectURL(n),c=document.createElement("a");c.href=o,c.download=t,document.body.appendChild(c),c.click(),document.body.removeChild(c),URL.revokeObjectURL(o)}(function(e){const t=e.messages||e.chatHistory?.[0]?.messages||[];return t.length?t.filter((e=>e.role&&e.content)).map((e=>`# ${e.role.charAt(0).toUpperCase()+e.role.slice(1)}\n\n${e.content.replace(/^\s+|\s+$/g,"")}`)).join("\n\n---\n\n"):"# Chat\nNo messages found."}(t),`chat-${t.currentChatId}.md`)}();

Instructions

  1. On any page, right-click your browser’s bookmarks / favorites bar and choose Add Bookmark (or Add Page).
  2. In the Name field, type anything memorable — I use Export oMLX Chat.
  3. In the URL field, paste the entire minified code block above exactly as-is.
  4. Hit Save (or Add).

Usage

Using the bookmarklet is a two-step process:

  1. Open your chat in oMLX. Open the oMLX web app and load the specific chat conversation you want to export.
  2. Click the bookmark. With that chat tab active, click the Export oMLX Chat bookmark you installed. The file will download immediately as chat-<id>.md.

What it does under the hood

The script works by:

  1. Finding the active Alpine.js component on the page (oMLX stores its state there).
  2. Extracting the messages array from that component’s data.
  3. Formatting each message as # User or # Assistant with horizontal rules between turns.
  4. Generating a blob and triggering a programmatic download via a temporary anchor element.

Here’s the readable source if you’d like to audit it:

(function () {
  /**
   * Downloads the currently viewed oMLX chat as a Markdown file.
   * Labels each turn with H1 headers ("# User", "# Assistant").
   */
  function chatToMarkdown(chatData) {
    const messages =
      chatData.messages || chatData.chatHistory?.[0]?.messages || [];

    if (!messages.length) return "# Chat\nNo messages found.";

    const formattedBlocks = messages
      .filter((msg) => msg.role && msg.content)
      .map((msg) => {
        // Capitalize role for the H1 header (e.g., "user" -> "User")
        const roleHeader = msg.role.charAt(0).toUpperCase() + msg.role.slice(1);
        // Trim only leading/trailing whitespace, preserve internal formatting/newlines
        const cleanContent = msg.content.replace(/^\s+|\s+$/g, "");
        return `# ${roleHeader}\n\n${cleanContent}`;
      });

    // Combine all blocks with a horizontal rule for readability
    return formattedBlocks.join("\n\n---\n\n");
  }

  /**
   * Triggers a browser download for the given text content.
   */
  function triggerDownload(content, filename) {
    const blob = new Blob([content], { type: "text/markdown;charset=utf-8" });
    const url = URL.createObjectURL(blob);

    const a = document.createElement("a");
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);

    // Programmatically click and clean up
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }

  // Grab the data from the oMLX Alpine instance
  const root = document.querySelector("[x-html]").closest("[x-data]");
  const componentData = Alpine.$data(root);

  // Call the formatting function
  const markdownOutput = chatToMarkdown(componentData);

  // Take the output and download it as "chat-[id].md"
  const filename = `chat-${componentData.currentChatId}.md`;
  triggerDownload(markdownOutput, filename);
})();

The exported file is plain Markdown, so you can open it in any text editor or Markdown viewer. Hope it’s helpful!

Disclosure: This code and this blog post was written partly by me and partly by Qwen-3.6. I manually audited all code for safety to the best of my professional ability.

Comments