DEV Community

Dan E
Dan E

Posted on • Originally published at rendex.dev

Render an HTML Table to PNG in Python (5 Ways, 2026)

You have an HTML table — a financial summary, a report section, a styled DataFrame, a chart your frontend already renders — and you need it as a PNG from a Python backend. A cron job that emails a nightly report, an invoice rasterized into a PDF, a certificate generated per customer. The HTML is the easy part; turning it into a pixel-accurate image is where the options get messy.

This guide covers five working approaches, from a single API call to a headless browser you host yourself, plus an honest note on where each one bites. Every snippet renders the same little table so you can compare them directly. (All facts verified June 2026 — wkhtmltopdf is now archived, which changes the usual advice.)

Method 1: Rendex API (no browser to host)

The fastest path, and the one that needs nothing on your machine. POST the HTML to the Rendex rendering API and get PNG bytes back. A real, modern Chromium runs on Cloudflare's edge — you don't install, patch, or keep a browser alive, and modern CSS (Flexbox, Grid, web fonts) renders exactly as it would in Chrome.

# pip install rendex
from rendex import Rendex
from pathlib import Path

rendex = Rendex("YOUR_API_KEY")

html = """
<table style="border-collapse:collapse;font-family:system-ui;font-size:14px">
  <thead>
    <tr style="background:#f1f5f9">
      <th style="text-align:left;padding:8px 14px">Line item</th>
      <th style="text-align:right;padding:8px 14px">Amount</th>
    </tr>
  </thead>
  <tbody>
    <tr><td style="padding:8px 14px">Subscriptions</td>
        <td style="text-align:right;padding:8px 14px">€482,100</td></tr>
    <tr><td style="padding:8px 14px">Professional services</td>
        <td style="text-align:right;padding:8px 14px">€91,400</td></tr>
  </tbody>
</table>
"""

result = rendex.render_html(html, format="png", device_scale_factor=2)
Path("table.png").write_bytes(result.image)
print("Rendered", result.metadata)
Enter fullscreen mode Exit fullscreen mode

Because the table is the only thing on the page, render at device_scale_factor=2 for a crisp 2× image, and add full_page=True when a long table runs past the viewport. No SDK? The same call over raw HTTP:

curl -X POST https://api.rendex.dev/v1/screenshot \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  --output table.png \
  -d '{
    "html": "<table style=\"border-collapse:collapse;font-family:system-ui\"><tr><th>Line item</th><th>Amount</th></tr><tr><td>Subscriptions</td><td>€482,100</td></tr></table>",
    "format": "png",
    "deviceScaleFactor": 2,
    "fullPage": true
  }'
Enter fullscreen mode Exit fullscreen mode

When to use: production report pipelines, anything you deploy to a serverless function or a slim container, and any time you don't want a Chromium binary in your image. The free tier is 500 renders/month, no cardgrab a key. For the full report-to-PDF-deliverable pattern (templating with a data{} object, embedding the PNG in a generated PDF), see the Python report-rendering recipe.

The trade-off, honestly: it's a network call and a per-render cost, and the HTML leaves your box. For a sync render Rendex processes it transiently and doesn't store it; if that's a dealbreaker for sensitive data, one of the self-hosted options below keeps everything local.

Method 2: html2image (lightweight, but you supply Chrome)

html2image is a thin wrapper around the headless mode of a Chrome/Chromium/Edge that is already installed on the machine — it doesn't bundle one. Maintained (latest 2.0.7, May 2025), MIT, and genuinely the simplest local route when a browser is already present.

# pip install html2image   (Chrome/Chromium/Edge must already be installed)
from html2image import Html2Image

hti = Html2Image()
html = "<table border=1><tr><th>Line item</th><th>Amount</th></tr>" \
       "<tr><td>Subscriptions</td><td>€482,100</td></tr></table>"

hti.screenshot(html_str=html, save_as="table.png", size=(600, 200))
Enter fullscreen mode Exit fullscreen mode

Where it bites: you own keeping that Chrome patched and version-matched across every machine and CI runner, and fonts/emoji are whatever the host browser ships — Linux base images often lack CJK and color-emoji fonts, so you'll install font packages to avoid tofu boxes.

Method 3: Playwright (the modern self-hosted default)

If you're going to run a browser yourself, Playwright for Python is the current standard (1.60, May 2026, with evergreen bundled browsers). set_content() loads your HTML; locate the table and screenshot just that element.

# pip install playwright  &&  playwright install chromium
from playwright.sync_api import sync_playwright

html = "<table border=1><tr><th>Line item</th><th>Amount</th></tr>" \
       "<tr><td>Subscriptions</td><td>€482,100</td></tr></table>"

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.set_content(html)
    page.locator("table").screenshot(path="table.png")  # crop to the table
    browser.close()
Enter fullscreen mode Exit fullscreen mode

Where it bites: the bundled browsers are hundreds of MB in your image; you patch them; and a long-running process leaks memory and leaves zombie processes if you don't close contexts. Full-page shots over ~16,384px can OOM. Great control — you just operate the browser fleet now.

Method 4: imgkit / wkhtmltoimage (works, but the engine is archived)

imgkit wraps the wkhtmltoimage binary. It's the classic answer to this question, and the one to be most careful with in 2026.

# pip install imgkit   +   system binary: apt-get install wkhtmltopdf
import imgkit

html = "<table border=1><tr><th>Line item</th><th>Amount</th></tr>" \
       "<tr><td>Subscriptions</td><td>€482,100</td></tr></table>"

imgkit.from_string(html, "table.png")  # headless box: wrap in xvfb-run
Enter fullscreen mode Exit fullscreen mode

Where it bites: the underlying wkhtmltoimage/wkhtmltopdf engine was archived in January 2023 (last stable release June 2020) and gets no security patches. It's a frozen, old WebKit: modern CSS Flex/Grid, web fonts, and emoji are broken or silently dropped, and on a headless server it throws QXcbConnection: Could not connect to display unless you wrap every call in xvfb-run. imgkit itself is effectively unmaintained (last release 1.2.3). Fine for a legacy script; not what you'd start a new project on.

Method 5: Selenium (works, heaviest for one PNG)

Selenium drives a real browser and can screenshot an element. It's maintained and battle-tested, but it's the most boilerplate for "HTML string → one PNG."

# pip install selenium   (Selenium Manager fetches a matching ChromeDriver)
import urllib.parse
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

html = "<table border=1><tr><th>Line item</th><th>Amount</th></tr>" \
       "<tr><td>Subscriptions</td><td>€482,100</td></tr></table>"

opts = Options()
opts.add_argument("--headless=new")
driver = webdriver.Chrome(options=opts)
driver.get("data:text/html;charset=utf-8," + urllib.parse.quote(html))
driver.find_element("tag name", "table").screenshot("table.png")
driver.quit()
Enter fullscreen mode Exit fullscreen mode

Where it bites: the classic SessionNotCreatedException when ChromeDriver and the host Chrome drift apart (Selenium Manager mostly handles this now), full-page screenshots aren't first-class, and it's the same browser-fleet ownership as Playwright with more ceremony.

A note on WeasyPrint

WeasyPrint comes up in every "HTML to image" search, so it's worth being precise: it's an excellent, actively-maintained pure-Python engine — but it renders to PDF only (PNG export was removed in v53) and runs no JavaScript. If your table is static HTML/CSS and you actually want a PDF, WeasyPrint is a great, dependency-light choice and you don't need any of the above. If you need a PNG, or your table/chart is built by JavaScript, it can't do the job without a second PDF→PNG conversion step.

Which one should you use?

Approach Browser to host? PNG? Best for
Rendex API No (runs on the edge) Yes Production pipelines, serverless, no infra to own
html2image Yes (you supply Chrome) Yes Quick local jobs where a browser is already present
Playwright Yes (bundled) Yes Full browser control, if you want to run the fleet
imgkit / wkhtmltoimage No (archived engine) Yes Legacy scripts only — engine archived 2023
Selenium Yes Yes (viewport) Teams already standardized on Selenium
WeasyPrint No No (PDF only) Static HTML → PDF, no JS

The honest summary: the self-hosted libraries all work — the real question is whether you want to own a browser (install, patch, scale, debug fonts, watch memory) for what is often a side-task to your actual report logic. If yes, Playwright is the modern pick. If you'd rather keep your deployment a plain Python service and POST HTML to get a PNG back, that's exactly what Rendex is for — 500 free renders a month to prove the pipeline before you scale.

Top comments (0)