HTML to Image in Python
Render HTML to a PNG from Python using requests or httpx.
Best for: Flask, Django and FastAPI apps, plus data pipelines that produce reports as PNGs. The async
httpxexample below is the right fit for FastAPI workers.
Worked examples covering synchronous calls with requests, async calls with httpx, and storing the returned URL.
Installation
First, install the required package:
pip install requests
API Response Format
Every successful request returns a JSON body with a url pointing to the generated image:
{
"success": true,
"credits_remaining": 95,
"id": "abc123",
"url": "https://i.html2img.com/abc123.png"
}
You can either use url directly, or download the image from it if you need a local copy.
Basic Python Example
Here’s a simple example using the requests library:
import requests
import os
from datetime import datetime
def html_to_image(html: str, css: str = '') -> str:
"""Convert HTML to image and return the hosted image URL."""
api_key = os.getenv('HTML2IMG_API_KEY')
endpoint = 'https://app.html2img.com/api/html'
data = {
'html': html,
'css': css,
'width': 800,
'height': 600,
}
response = requests.post(
endpoint,
json=data,
headers={'X-API-Key': api_key},
)
response.raise_for_status()
result = response.json()
if not result.get('success'):
raise RuntimeError(result.get('message', 'Unknown error'))
return result['url']
def download_image(url: str, path: str) -> None:
"""Download a generated image from its hosted URL."""
response = requests.get(url)
response.raise_for_status()
with open(path, 'wb') as f:
f.write(response.content)
if __name__ == '__main__':
try:
html = '''
<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from Python!</h1>
<p>Generated at {}</p>
</div>
'''.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
css = '''
h1 { color: #2563eb; }
p { color: #4b5563; }
'''
image_url = html_to_image(html, css)
print(f'Image available at: {image_url}')
# Optionally save a local copy
download_image(image_url, 'output.png')
print('Image saved as output.png')
except requests.exceptions.RequestException as e:
print(f'Error: {e}')
Screenshot Example
Taking a screenshot of a webpage:
import requests
import os
def take_screenshot(url: str) -> str:
"""Take a screenshot and return the hosted image URL."""
api_key = os.getenv('HTML2IMG_API_KEY')
api_url = 'https://app.html2img.com/api/screenshot'
data = {
'url': url,
'width': 1200,
'height': 800,
'dpi': 2,
'fullpage': True,
}
response = requests.post(
api_url,
json=data,
headers={'X-API-Key': api_key},
)
response.raise_for_status()
result = response.json()
if not result.get('success'):
raise RuntimeError(result.get('message', 'Unknown error'))
return result['url']
if __name__ == '__main__':
try:
screenshot_url = take_screenshot('https://example.com')
print(f'Screenshot available at: {screenshot_url}')
except requests.exceptions.RequestException as e:
print(f'Error: {e}')
Flask Example
Here’s how to use the API in a Flask application. Because the API returns a hosted URL, you can simply return it as JSON or redirect to it rather than proxying the image bytes:
from flask import Flask, render_template, jsonify, redirect, request
import requests
import os
from datetime import datetime
app = Flask(__name__)
@app.route('/generate-image')
def generate_image():
try:
html = render_template(
'example.html',
title='Hello from Flask!',
timestamp=datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
)
response = requests.post(
'https://app.html2img.com/api/html',
json={
'html': html,
'width': 800,
'height': 600,
},
headers={'X-API-Key': os.getenv('HTML2IMG_API_KEY')},
)
response.raise_for_status()
data = response.json()
if not data.get('success'):
return jsonify({'error': data.get('message', 'Unknown error')}), 500
return jsonify({'url': data['url'], 'id': data['id']})
except requests.exceptions.RequestException as e:
return jsonify({'error': str(e)}), 500
@app.route('/take-screenshot')
def take_screenshot():
try:
url = request.args.get('url', 'https://example.com')
response = requests.post(
'https://app.html2img.com/api/screenshot',
json={
'url': url,
'width': 1200,
'height': 800,
'dpi': 2,
'fullpage': True,
},
headers={'X-API-Key': os.getenv('HTML2IMG_API_KEY')},
)
response.raise_for_status()
data = response.json()
if not data.get('success'):
return jsonify({'error': data.get('message', 'Unknown error')}), 500
# Redirect the browser straight to the hosted screenshot
return redirect(data['url'])
except requests.exceptions.RequestException as e:
return jsonify({'error': str(e)}), 500
# Example template (example.html)
'''
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: system-ui, sans-serif;
margin: 0;
padding: 20px;
}
.container {
background: #f0f0f0;
padding: 20px;
border-radius: 8px;
}
h1 { color: #2563eb; }
p { color: #4b5563; }
</style>
</head>
<body>
<div class="container">
<h1>{{ title }}</h1>
<p>Generated at {{ timestamp }}</p>
</div>
</body>
</html>
'''
FastAPI Example
If you’re using FastAPI, here’s how you can implement it:
from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse
import requests
import os
from datetime import datetime
app = FastAPI()
@app.get("/generate-image")
async def generate_image():
try:
html = f'''
<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from FastAPI!</h1>
<p>Generated at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
</div>
'''
response = requests.post(
'https://app.html2img.com/api/html',
json={
'html': html,
'width': 800,
'height': 600,
},
headers={'X-API-Key': os.getenv('HTML2IMG_API_KEY')},
)
response.raise_for_status()
data = response.json()
if not data.get('success'):
raise HTTPException(status_code=500, detail=data.get('message', 'Unknown error'))
return {'url': data['url'], 'id': data['id']}
except requests.exceptions.RequestException as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/screenshot")
async def take_screenshot(url: str = 'https://example.com'):
try:
response = requests.post(
'https://app.html2img.com/api/screenshot',
json={
'url': url,
'width': 1200,
'height': 800,
'dpi': 2,
'fullpage': True,
},
headers={'X-API-Key': os.getenv('HTML2IMG_API_KEY')},
)
response.raise_for_status()
data = response.json()
if not data.get('success'):
raise HTTPException(status_code=500, detail=data.get('message', 'Unknown error'))
return RedirectResponse(data['url'])
except requests.exceptions.RequestException as e:
raise HTTPException(status_code=500, detail=str(e))
Using with Async/Await
If you prefer using async/await with aiohttp:
import aiohttp
import asyncio
import os
from datetime import datetime
async def html_to_image_async(html: str, css: str = '') -> str:
"""Convert HTML to image using async HTTP requests."""
api_key = os.getenv('HTML2IMG_API_KEY')
url = 'https://app.html2img.com/api/html'
async with aiohttp.ClientSession() as session:
async with session.post(
url,
json={
'html': html,
'css': css,
'width': 800,
'height': 600,
},
headers={'X-API-Key': api_key},
) as response:
data = await response.json()
if response.status != 200 or not data.get('success'):
raise aiohttp.ClientError(
f'API Error: {data.get("message", response.status)}'
)
return data['url']
async def main():
try:
html = '''
<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from Python Async!</h1>
<p>Generated at {}</p>
</div>
'''.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
image_url = await html_to_image_async(html)
print(f'Image available at: {image_url}')
except Exception as e:
print(f'Error: {e}')
if __name__ == '__main__':
asyncio.run(main())
Store your API key in environment variables and never commit it to version control.
Common patterns
Async httpx
import asyncio
import os
import httpx
async def render_html(html: str, options: dict | None = None) -> str:
async with httpx.AsyncClient(timeout=35.0) as client:
response = await client.post(
'https://app.html2img.com/api/html',
headers={'X-API-Key': os.environ['HTML2IMG_API_KEY']},
json={'html': html, **(options or {})},
)
response.raise_for_status()
return response.json()['url']
async def main():
url = await render_html('<h1>Hello</h1>', {'width': 1200, 'height': 630})
print(url)
asyncio.run(main())
The async client is the right fit for FastAPI workers and any data pipeline that batches multiple renders concurrently.
Templates in Python
Use a named template when your data is structured. Here is the Invoice Image template:
import os
import requests
response = requests.post(
'https://app.html2img.com/api/v1/templates/invoice-image',
headers={'X-API-Key': os.environ['HTML2IMG_API_KEY']},
json={
'invoice_number': 'INV-2026-0042',
'business_name': 'Coastline Coffee Co',
'client_name': 'Riverside Bakery',
'items': [{'description': 'Wholesale beans', 'quantity': '50', 'unit_price': '$15.00', 'amount': '$750.00'}],
'total': '$750.00',
},
)
url = response.json()['url']
The returned url is hosted on i.html2img.com.