Skip to content

Instantly share code, notes, and snippets.

@cdracars
Last active November 24, 2025 04:00
Show Gist options
  • Select an option

  • Save cdracars/1328e34bbe56bbf43654c24fe8a9a36b to your computer and use it in GitHub Desktop.

Select an option

Save cdracars/1328e34bbe56bbf43654c24fe8a9a36b to your computer and use it in GitHub Desktop.
Klipper Print Progress Overlay for OBS - Display printer status, progress, and temps

Klipper Print Progress for OBS

Display your 3D printer's progress, temperatures, and status in OBS using Klipper/Moonraker/Mainsail.

Setup Instructions

1. Configure Moonraker CORS Settings

You need to allow cross-origin requests so OBS can fetch data from your printer.

SSH into your printer and edit the Moonraker config:

nano ~/printer_data/config/moonraker.conf

Find the [authorization] section and add the appropriate CORS domains.

If loading the HTML file directly in OBS (file:// protocol):

Add null to allow local file access:

[authorization]
cors_domains:
    *.lan
    *.local
    *://localhost
    *://localhost:*
    *://my.mainsail.xyz
    *://app.fluidd.xyz
    null

If hosting on a web server:

Add your web server's URL:

cors_domains:
    *.lan
    *.local
    *://localhost
    *://localhost:*
    *://my.mainsail.xyz
    *://app.fluidd.xyz
    http://192.168.1.100
    http://192.168.1.100:*

Replace http://192.168.1.100 with your web server's IP.

Always add your printer's IP address to CORS domains:

cors_domains:
    *.lan
    *.local
    *://localhost
    *://localhost:*
    *://my.mainsail.xyz
    *://app.fluidd.xyz
    http://192.168.1.50
    http://192.168.1.50:*
    null

Replace 192.168.1.50 with your printer's actual IP address (the same one you use in PRINTER_IP config).

Save the file and restart Moonraker:

sudo systemctl restart moonraker

2. Configure the HTML File

Download the HTML file and open it in a text editor. At the top of the file (around line 55-57), you'll find the configuration section:

// CONFIGURATION
const PRINTER_IP = '192.168.1.100';       // Your printer's IP address
const PRINTER_NAME = 'My Printer';        // Display name for your printer
const UPDATE_INTERVAL = 2000;             // Update frequency in milliseconds (2000 = 2 seconds)

Update these values:

  • PRINTER_IP: Your printer's IP address (e.g., 192.168.1.50, 192.168.0.200, etc.)
  • PRINTER_NAME: The name you want displayed (e.g., Prusa MK3S, Voron 2.4, Ender 3, etc.)
  • UPDATE_INTERVAL: How often to poll the printer (2000ms = 2 seconds is recommended)

Save the file.

3. Add to OBS

Option A: Load HTML file directly (Easiest)

  1. Save the HTML file to your computer
  2. Open OBS Studio
  3. Add a new Browser Source to your scene
  4. Enter the full file path to your HTML file (e.g., file:///C:/Users/yourname/klipper-obs-overlay.html)
    • OBS will show this in the URL field automatically
  5. Set the dimensions:
    • Width: 450
    • Height: 280
  6. Check these options:
    • "Shutdown source when not visible" (saves resources)
    • "Refresh browser when scene becomes active" (optional)
  7. Click OK

Option B: Host on a web server

  1. Host the HTML file on a web server (your printer, a NAS, or any web server on your network)
  2. Open OBS Studio
  3. Add a new Browser Source to your scene
  4. Enter the URL to your HTML file (e.g., http://192.168.1.100/klipper-obs-overlay.html)
  5. Set the dimensions as above
  6. Make sure you added your web server's URL to CORS domains (see Step 1)
  7. Click OK

Position and resize the overlay wherever you want on your stream!

What's Displayed

  • Printer Name: Your custom printer name
  • Status: Current state (Printing, Paused, Idle, etc.)
  • Progress Bar: Visual progress with percentage
  • Time Remaining: Estimated time left in the print
  • Filename: Current file being printed
  • Hotend Temperature: Current/Target (e.g., 210°C / 215°C)
  • Bed Temperature: Current/Target (e.g., 60°C / 60°C)

Customization

You can customize the appearance by editing the CSS in the <style> section:

  • Change colors in the progress bar gradient
  • Adjust font sizes and families
  • Modify the background transparency
  • Change border radius and padding

Troubleshooting

"Connection Error" displayed:

  • Verify your printer IP/hostname is correct
  • Make sure you can access http://your-printer-ip in a web browser
  • Check that Moonraker is running: systemctl status moonraker

CORS errors in browser console:

  • If using file:// (Option A): Add null to cors_domains in moonraker.conf
  • If using web server (Option B): Add your web server's URL to cors_domains
  • Always add your printer's IP address to cors_domains
  • Restart Moonraker after making changes
  • Make sure there are no typos in the config file

Temperatures showing as "--":

  • This is normal when the printer is idle/not printing
  • Temperatures will appear once a print starts or heaters are manually set

Multiple Printers

To display multiple printers, create separate HTML files for each printer with different PRINTER_IP and PRINTER_NAME values, then add each as a separate Browser Source in OBS.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Print Progress</title>
<style>
/* ============================================
CONFIGURATION - Edit these values
============================================ */
/* These are set in JavaScript below - search for "CONFIGURATION" */
/* ============================================
STYLING - Customize appearance here
============================================ */
body {
margin: 0;
padding: 20px;
background-color: transparent;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: #ffffff;
}
.container {
background: rgba(0, 0, 0, 0.7);
border-radius: 10px;
padding: 20px;
max-width: 400px;
}
.progress-bar {
width: 100%;
height: 30px;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 15px;
overflow: hidden;
margin: 10px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4CAF50, #8BC34A);
transition: width 0.5s ease;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.info {
font-size: 18px;
margin: 10px 0;
}
.label {
color: #aaaaaa;
font-size: 14px;
}
.error {
color: #ff5555;
}
.idle {
color: #ffaa00;
}
</style>
</head>
<body>
<div class="container">
<div class="info" style="font-size: 20px; font-weight: bold; margin-bottom: 15px;">
<span id="printerName">Printer</span>
</div>
<div class="info">
<span class="label">Status:</span> <span id="status">Connecting...</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progressBar" style="width: 0%;">
<span id="percentage">0%</span>
</div>
</div>
<div class="info">
<span class="label">Time Remaining:</span> <span id="timeRemaining">--</span>
</div>
<div class="info">
<span class="label">Filename:</span> <span id="filename">--</span>
</div>
<div class="info">
<span class="label">Hotend:</span> <span id="hotendTemp">--</span>
</div>
<div class="info">
<span class="label">Bed:</span> <span id="bedTemp">--</span>
</div>
</div>
<script>
/* ============================================
CONFIGURATION - EDIT THESE VALUES
============================================ */
const PRINTER_IP = 'hoss.dracarsfamily'; // Your printer's IP address or hostname
const PRINTER_NAME = 'Hoss'; // Display name for your printer
const UPDATE_INTERVAL = 2000; // Update frequency in milliseconds (2000 = 2 seconds)
/* ============================================
CODE - Don't edit below unless you know what you're doing
============================================ */
// Set printer name on load
document.getElementById('printerName').textContent = PRINTER_NAME;
async function fetchPrintStatus() {
try {
const response = await fetch(`http://${PRINTER_IP}/printer/objects/query?display_status&print_stats&virtual_sdcard&extruder&heater_bed`);
const data = await response.json();
const status = data.result.status;
const printStats = status.print_stats;
const displayStatus = status.display_status;
const virtualSdcard = status.virtual_sdcard;
const extruder = status.extruder;
const heaterBed = status.heater_bed;
// Update temperatures
if (extruder) {
const hotendTemp = Math.round(extruder.temperature);
const hotendTarget = Math.round(extruder.target);
document.getElementById('hotendTemp').textContent = `${hotendTemp}°C / ${hotendTarget}°C`;
}
if (heaterBed) {
const bedTemp = Math.round(heaterBed.temperature);
const bedTarget = Math.round(heaterBed.target);
document.getElementById('bedTemp').textContent = `${bedTemp}°C / ${bedTarget}°C`;
}
// Update status
const statusElement = document.getElementById('status');
const state = printStats.state;
statusElement.textContent = state.charAt(0).toUpperCase() + state.slice(1);
if (state === 'printing') {
statusElement.className = '';
// Update progress
const progress = displayStatus.progress || virtualSdcard.progress || 0;
const percentage = Math.round(progress * 100);
document.getElementById('progressBar').style.width = percentage + '%';
document.getElementById('percentage').textContent = percentage + '%';
// Update time remaining
const printDuration = printStats.print_duration || 0;
if (progress > 0 && progress < 1) {
const totalTime = printDuration / progress;
const remaining = totalTime - printDuration;
document.getElementById('timeRemaining').textContent = formatTime(remaining);
} else {
document.getElementById('timeRemaining').textContent = '--';
}
// Update filename
document.getElementById('filename').textContent = printStats.filename || 'Unknown';
} else if (state === 'paused') {
statusElement.className = 'idle';
} else {
statusElement.className = 'idle';
document.getElementById('progressBar').style.width = '0%';
document.getElementById('percentage').textContent = '0%';
document.getElementById('timeRemaining').textContent = '--';
document.getElementById('filename').textContent = '--';
}
} catch (error) {
document.getElementById('status').textContent = 'Connection Error';
document.getElementById('status').className = 'error';
console.error('Error fetching print status:', error);
}
}
function formatTime(seconds) {
if (!seconds || seconds < 0) return '--';
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}h ${minutes}m`;
} else if (minutes > 0) {
return `${minutes}m ${secs}s`;
} else {
return `${secs}s`;
}
}
// Initial fetch and set interval
fetchPrintStatus();
setInterval(fetchPrintStatus, UPDATE_INTERVAL);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment