Skip to content

Instantly share code, notes, and snippets.

@0wwafa
Last active May 21, 2026 23:51
Show Gist options
  • Select an option

  • Save 0wwafa/6569915d936370656c687e014e596f4a to your computer and use it in GitHub Desktop.

Select an option

Save 0wwafa/6569915d936370656c687e014e596f4a to your computer and use it in GitHub Desktop.
Print / Export Claude Chat

Go to https://claude.ai/chat/new (or any other chat you had in the past).

Then run this code:

function printClaude() {   // (C) 2024 by ZeroWw. If you use this code, just give me some credit.
    const centralPart = document.querySelector('.flex-1.flex.flex-col.px-4.max-w-3xl.mx-auto.w-full.pt-1');

    if (centralPart) {
        // Expand all "show more" buttons
        document.querySelectorAll('button').forEach(button => {
            const text = button.textContent.toLowerCase();
            if (text.includes('show more') || text.includes('continue') || text.includes('expand')) {
                button.click();
            }
        });

        // Wait for content to expand
        setTimeout(() => {
            const printWindow = window.open('', '_blank', `width=${window.screen.width * 0.85},height=${window.screen.height * 0.85}`);

            if (!printWindow) {
                alert('Pop-up blocked! Please allow pop-ups for this site and try again.');
                return;
            }

            // Get page stylesheets
            const styles = Array.from(document.styleSheets).map(styleSheet => {
                try {
                    return Array.from(styleSheet.cssRules).map(rule => rule.cssText).join('\n');
                } catch (e) {
                    return '';
                }
            }).join('\n');
            
            // Essential color variables - must stay in HSL format
            const cssVars = `:root{--text-000:49 6.9% 5.5%;--text-100:49 19.6% 13.3%;--text-200:49 18.8% 20%;--text-300:49 9% 30%;--text-400:49 7% 37%;--text-500:51 7.5% 42.1%;--accent-main-000:15 52.7% 43.9%;--accent-main-100:16 53.8% 47.5%;--accent-main-200:15 55.6% 52.4%;--accent-secondary-000:210 74.2% 42.1%;--accent-secondary-100:210 74.8% 49.8%;--accent-secondary-200:210 74.8% 57%;--accent-secondary-900:210 68.8% 93.3%;--accent-pro-000:251 34.2% 33.3%;--accent-pro-100:251 40% 45.1%;--accent-pro-200:251 61% 72.2%;--accent-pro-900:253 33.3% 91.8%;--oncolor-100:0 0% 100%;--bg-000:60 6.7% 97.1%;--bg-100:50 23.1% 94.9%;--bg-200:49 26.8% 92%;--bg-300:49 25.8% 87.8%;--bg-400:46 28.3% 82%;--bg-500:47 27% 71%;--accent-main-900:15 48% 90.2%;--border-100:48 12.5% 39.2%;--border-200:48 12.5% 39.2%;--border-300:48 12.5% 39.2%;--oncolor-200:60 6.7% 97.1%;--oncolor-300:60 6.7% 97.1%;--border-400:48 12.5% 39.2%;--danger-000:5 74% 28%;--danger-100:5 73.9% 37.7%;--danger-200:5 49.5% 58%;--danger-900:0 40.3% 89%;--white:0 0% 100%;--black:0 0% 0%;--kraft:25 49.7% 66.5%;--book-cloth:15 52.3% 58%;--manilla:40 54% 82.9%}`;
            
            printWindow.document.write(`<html><head><title>Claude_${new Date().getTime()}</title><style>${cssVars}${styles}</style></head><body>${centralPart.innerHTML}</body></html>`);

            // Cleanup: remove UI elements not needed in print
            const firstLink = printWindow.document.getElementsByTagName('a')[0];
            if (firstLink) firstLink.parentElement.removeChild(firstLink);

            const replyBox = printWindow.document.getElementsByClassName('border-0.5 border-border-300 flex');
            if (replyBox.length > 0) {
                const last = replyBox[replyBox.length - 1];
                if (last) last.parentElement.removeChild(last);
            }

            ['absolute -bottom-0 -right-1.5 sm:right-2', 'absolute -bottom-0 left-[2.3rem]', 'sticky bottom-0 mx-auto w-full pt-6'].forEach(className => {
                Array.from(printWindow.document.getElementsByClassName(className)).forEach(elem => elem.parentElement.removeChild(elem));
            });

            setTimeout(() => printWindow.print(), 3000);
        }, 1000);
    } else {
        console.error('Central part not found!');
    }
}

printClaude();
@mindattic

mindattic commented May 9, 2026

Copy link
Copy Markdown

Thanks!

Previous version was still working for me, and for you?

It said: Central part not found!
Your "bookmarklet" version says the same. I'm using Chrome Version 147.

@sfortina

Copy link
Copy Markdown

Very useful... but not fully working: it was loosing all embedded charts/widgets.
I have created a different version that temporarily changes the properties and uses standard printing.

// printClaude v6 — inlines /api/ images as base64 before printing
(function printClaude() {

const status = document.createElement('div');
status.id = '__printClaudeStatus__';
status.style.cssText = 'position:fixed;bottom:24px;right:24px;z-index:999999;' +
    'background:#1a1a1a;color:#fff;padding:12px 20px;border-radius:8px;' +
    'font-family:sans-serif;font-size:14px;box-shadow:0 4px 20px rgba(0,0,0,0.4);';
document.body.appendChild(status);

const hideStatus = document.createElement('style');
hideStatus.id = '__printClaudeHideStatus__';
hideStatus.textContent = `@media print { #__printClaudeStatus__ { display:none!important; } }`;
document.head.appendChild(hideStatus);

function log(msg) { console.log('[printClaude]', msg); status.textContent = msg; }

// ── Convert an img src to base64 data URL via canvas ─────────────────────
async function toDataURL(img) {
    return new Promise(resolve => {
        // If already loaded and not broken, use canvas
        const tryCanvas = () => {
            try {
                const canvas = document.createElement('canvas');
                canvas.width = img.naturalWidth || img.width || 800;
                canvas.height = img.naturalHeight || img.height || 600;
                const ctx = canvas.getContext('2d');
                ctx.drawImage(img, 0, 0);
                const dataUrl = canvas.toDataURL('image/png');
                resolve(dataUrl.length > 100 ? dataUrl : null);
            } catch(e) {
                console.warn('[printClaude] canvas failed for', img.src, e.message);
                resolve(null);
            }
        };

        if (img.complete && img.naturalWidth > 0) {
            tryCanvas();
        } else {
            img.onload = tryCanvas;
            img.onerror = () => resolve(null);
        }
    });
}

// ── Find scroll container ─────────────────────────────────────────────────
function findScroller() {
    const scrollers = [];
    document.querySelectorAll('*').forEach(el => {
        const oy = window.getComputedStyle(el).overflowY;
        if ((oy === 'auto' || oy === 'scroll') && el.scrollHeight > el.clientHeight + 50) {
            scrollers.push(el);
        }
    });
    return scrollers
        .filter(el => el.tagName !== 'HTML' && el.tagName !== 'BODY')
        .sort((a, b) => b.scrollHeight - a.scrollHeight)[0]
        || document.documentElement;
}

// ── Scroll to force virtual DOM hydration ─────────────────────────────────
async function fullScroll(el) {
    let prev = -1, stable = 0, i = 0;
    while (stable < 5 && i < 150) {
        el.scrollTop = el.scrollHeight;
        await new Promise(r => setTimeout(r, 250));
        const cur = el.scrollHeight;
        if (cur === prev) stable++; else { stable = 0; prev = cur; }
        i++;
    }
    el.scrollTop = 0;
    window.scrollTo(0, 0);
    await new Promise(r => setTimeout(r, 500));
}

async function run() {
    log('Loading all messages…');
    const scroller = findScroller();
    await fullScroll(scroller);

    // ── Inline all /api/ images ───────────────────────────────────────────
    const apiImgs = Array.from(document.querySelectorAll('img'))
        .filter(img => img.src && img.src.includes('/api/'));

    log(`Inlining ${apiImgs.length} image(s)…`);
    console.log('[printClaude] API images found:', apiImgs.map(i => i.src));

    // Store originals so we can restore after print
    const origSrcs = new Map();

    await Promise.all(apiImgs.map(async img => {
        const dataUrl = await toDataURL(img);
        if (dataUrl) {
            origSrcs.set(img, img.src);
            img.src = dataUrl;
            console.log('[printClaude] inlined:', origSrcs.get(img).slice(0, 60));
        } else {
            console.warn('[printClaude] could not inline:', img.src.slice(0, 60));
        }
    }));

    await new Promise(r => setTimeout(r, 300));

    // ── Inject print CSS ──────────────────────────────────────────────────
    const style = document.createElement('style');
    style.id = '__printClaude__';
    style.textContent = `
        @media print {
            @page { margin: 1.5cm 2cm; size: A4; }
            #__printClaudeStatus__ { display: none !important; }
            html, body {
                background: #fff !important; color: #111 !important;
                font-size: 10.5pt !important; height: auto !important;
                overflow: visible !important;
            }
            :root {
                color-scheme: light;
                --bg-000:0 0% 100%; --bg-100:0 0% 97%;
                --text-000:0 0% 8%; --text-100:0 0% 12%;
                --text-300:0 0% 35%; --border-200:0 0% 78%;
            }
            * { overflow: visible !important; max-height: none !important; }

            nav, header,
            [data-chat-input-container],
            [data-testid="wiggle-controls-actions"],
            [id^="mcp-app-modal-"],
            [id^="mcp-app-placeholder-"],
            [role="group"][aria-label="Message actions"],
            button, [role="button"],
            [contenteditable="true"],
            [class*="z-toast"] { display: none !important; }

            [class*="max-w-3xl"], [class*="max-w-2xl"], [class*="mx-auto"] {
                max-width: 100% !important; width: 100% !important; margin: 0 !important;
            }
            [id^="mcp-app-container-"] {
                display: block !important; visibility: visible !important; height: auto !important;
            }
            iframe {
                display: block !important; width: 100% !important;
                min-height: 300pt; border: 0.5pt solid #ccc !important;
                page-break-inside: avoid; margin: 8pt 0;
            }
            img {
                max-width: 100% !important;
                height: auto !important;
                page-break-inside: avoid;
                display: block;
            }
            [data-user-message-bubble="true"] {
                background: #f5f5f2 !important; padding: 6pt 10pt !important;
                color: #111 !important; border-radius: 4pt;
            }
            * { color: #111 !important; }
            a { color: #1a6bbf !important; }
            pre {
                page-break-inside: avoid; background: #f7f7f5 !important;
                border: 0.5pt solid #ddd !important; padding: 6pt;
                font-size: 8.5pt; white-space: pre-wrap;
            }
            code { background: #f7f7f5 !important; font-size: 8.5pt; }
            table { border-collapse: collapse; width: 100%; }
            th, td { border: 0.5pt solid #ccc !important; padding: 3pt 5pt; }
            th { background: #f5f5f2 !important; }
            hr { border: none; border-top: 0.5pt solid #ddd !important; }
        }
    `;
    document.head.appendChild(style);

    log('Opening print dialog…');
    await new Promise(r => setTimeout(r, 150));
    window.print();

    // ── Restore original image srcs ───────────────────────────────────────
    setTimeout(() => {
        origSrcs.forEach((src, img) => { img.src = src; });
        document.getElementById('__printClaude__')?.remove();
        document.getElementById('__printClaudeHideStatus__')?.remove();
        status.remove();
    }, 1500);
}

run().catch(e => {
    log('ERROR: ' + e.message);
    console.error('[printClaude]', e);
});

})();

Force paste it in your console (F12) and then hit Enter.

Known bug: The idea of converting any image pasted by the user into base64 so it can be printed does not work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment