You've got HTML. You need a PNG. Maybe it's a certificate, a receipt, a social media card, or a report that needs to look exactly the same in every email client.
The browser renders HTML beautifully. The problem is getting that rendering out of the browser and into an image file.
Let's fix that.
The Options
Option 1: Headless browser yourself — Install Puppeteer/Playwright, manage Chrome binaries, handle fonts, timeouts, memory leaks, and Docker configs. Works, but it's a lot.
Option 2: Use an API — Send HTML, get an image back. Done in one HTTP call.
We're going with Option 2.
The API Call
Here's the simplest version using curl:
curl -X POST "https://rendly-api.fly.dev/api/v1/html" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<div style=\"padding:40px;font-family:sans-serif;background:#1a1a2e;color:#eee\"><h1>Hello, World!</h1><p>This is an image now.</p></div>",
"width": 800,
"height": 400,
"format": "png"
}' --output hello.png
That's it. You send HTML, you get a PNG.
Node.js Example
const response = await fetch('https://rendly-api.fly.dev/api/v1/html', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
html: `
<div style="padding: 40px; background: linear-gradient(135deg, #667eea, #764ba2); color: white; font-family: system-ui;">
<h1 style="margin: 0;">Invoice #1042</h1>
<p>Amount: $2,400.00</p>
<p>Status: Paid ✓</p>
</div>
`,
width: 600,
height: 300,
format: 'png',
}),
});
const buffer = await response.arrayBuffer();
fs.writeFileSync('invoice.png', Buffer.from(buffer));
Python Example
import requests
resp = requests.post(
"https://rendly-api.fly.dev/api/v1/html",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"html": """
<div style="padding:40px; background:#0f0c29; color:#fff; font-family:monospace;">
<h2>Deploy Report</h2>
<p>✅ 47 tests passed</p>
<p>🚀 Deployed to production</p>
<p>📦 v2.4.1</p>
</div>
""",
"width": 600,
"height": 300,
"format": "png",
},
)
with open("report.png", "wb") as f:
f.write(resp.content)
Ruby Example
require "net/http"
require "json"
uri = URI("https://rendly-api.fly.dev/api/v1/html")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer YOUR_API_KEY"
req["Content-Type"] = "application/json"
req.body = {
html: '<div style="padding:40px;background:#2d3436;color:#dfe6e9;font-family:Georgia"><h1>Certificate of Completion</h1><p>Awarded to: Jane Developer</p></div>',
width: 800,
height: 400,
format: "png"
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
File.write("certificate.png", res.body)
Real Use Cases
- Certificates — Templated HTML, unique per recipient, pixel-perfect every time
- Receipts/Invoices — Email clients mangle HTML; images don't break
- Social cards — Dynamic OG images for blogs, products, user profiles
- Reports — Dashboards → shareable images for Slack/email
- Email banners — Design in HTML/CSS, render to image, embed anywhere
Why Not Just Screenshot a URL?
You can! But HTML-to-image is better when:
- You don't have a hosted URL (the HTML is generated dynamically)
- You need it fast (no page load, no network requests, no waiting for fonts)
- You want full control over the viewport and rendering
Get Started
Rendly gives you 100 free renders per month. Sign up, grab your API key, and start converting HTML to images in under a minute.
No credit card. No headless browser. No Docker. Just HTML in, image out.
Building something cool with HTML-to-image? Drop a comment — I'd love to see it.
Top comments (0)