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 httpx example 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.