A powerful HTML to image conversion service built on Cloudflare Workers using Puppeteer. Convert HTML templates to high-quality PNG/JPEG images with variable replacement and R2 storage.
- Template Variables: Use
{{VARIABLE}}syntax for dynamic content - Real Puppeteer: Actual browser rendering with @cloudflare/puppeteer
- R2 Storage: Images stored in Cloudflare R2 with public URLs
- Multiple formats: PNG, JPEG support with quality control
- Variable Processing: Extract, validate, and replace template variables
- Production Ready: Error handling, validation, security headers
- Fast & scalable: Powered by Cloudflare Workers edge network
- Cloudflare account with R2 and Browser bindings enabled
- Node.js and npm installed
- Wrangler CLI:
npm install -g wrangler
-
Clone the repository
git clone https://github.com/mkurecka/html-to-image-worker.git cd html-to-image-worker -
Install dependencies
npm install
-
Configure environment variables
# Copy the example file cp .dev.vars.example .dev.vars # Edit .dev.vars with your actual values # Add your Cloudflare credentials and generate secure API keys
Generate secure API keys:
openssl rand -hex 32
-
Create R2 bucket
npx wrangler r2 bucket create html-images
-
Set production API keys (secure)
wrangler secret put API_KEYS # Enter your comma-separated API keys when prompted -
Deploy to Cloudflare Workers
npm run deploy
The worker will automatically set up browser and R2 bindings as configured in wrangler.toml.
All API endpoints (except / and /health) require API key authentication.
Three ways to authenticate:
-
X-API-Key header (Recommended)
-H "X-API-Key: YOUR_API_KEY" -
Authorization Bearer token
-H "Authorization: Bearer YOUR_API_KEY" -
Api-Key header
-H "Api-Key: YOUR_API_KEY"
Public endpoints (no authentication required):
GET /- API documentationGET /health- Health check
Protected endpoints (authentication required):
POST /render- Generate image from HTMLPOST /template/render- Generate image from templatePOST /template/preview- Preview processed HTMLPOST /template/variables- Extract template variables
Live API: https://html-to-image-worker.kureckamichal.workers.dev
- GET
/- Complete API documentation with examples (public) - POST
/template/render- Generate images from templates with variables (protected) - POST
/template/preview- Preview processed HTML without generating image (protected) - POST
/template/variables- Extract all variables from template (protected) - POST
/render- Generate image from plain HTML (protected) - GET
/health- Service health check (public)
POST /template/render
{
"template": "<div style='background: #FF6B6B; color: white; padding: 30px; text-align: center;'><h1>{{title}}</h1><p>{{message}}</p></div>",
"variables": {
"title": "Hello World!",
"message": "Generated with API"
},
"width": 400,
"height": 250,
"format": "png",
"quality": 90,
"returnUrl": true
}POST /render
{
"html": "<div style='padding: 40px; background: #007bff; color: white; text-align: center;'><h1>Simple HTML</h1><p>No variables needed</p></div>",
"width": 400,
"height": 200,
"format": "png",
"returnUrl": true
}| Parameter | Type | Default | Description |
|---|---|---|---|
template |
string | required | HTML template with {{VARIABLE}} placeholders |
variables |
object | {} | Key-value pairs for variable replacement |
html |
string | required | Static HTML content (for /render endpoint) |
width |
number | 1200 | Viewport width in pixels |
height |
number | 800 | Viewport height in pixels |
format |
string | "png" | Output format: "png", "jpeg" |
quality |
number | 90 | JPEG quality (1-100) |
deviceScaleFactor |
number | 1 | Device pixel ratio (1x, 2x, 3x) |
returnUrl |
boolean | true | Return R2 URL instead of binary data |
sanitize |
boolean | true | Sanitize variables to prevent XSS |
- Story: 1080 x 1920 pixels (9:16 ratio)
- Post Square: 1080 x 1080 pixels (1:1 ratio)
- Post Portrait: 1080 x 1350 pixels (4:5 ratio)
- Profile Picture: 320 x 320 pixels
- Post Image: 1200 x 628 pixels
- Profile Picture: 196 x 196 pixels
- Cover Photo: 1584 x 396 pixels
- Story: 1080 x 1920 pixels (9:16 ratio)
- Post Image Landscape: 1200 x 628 pixels
- Post Image Square: 1200 x 1200 pixels
- Profile Picture: 400 x 400 pixels
- Header: 1500 x 500 pixels
- Post Image: 1200 x 627 pixels
- Profile Picture: 400 x 400 pixels
- Cover Photo: 1584 x 396 pixels
- Company Logo: 300 x 300 pixels
- Thumbnail: 1280 x 720 pixels (16:9 ratio)
- Channel Art: 2560 x 1440 pixels
- Shorts: 1080 x 1920 pixels (9:16 ratio)
- Video: 1080 x 1920 pixels (9:16 ratio)
- Profile Picture: 200 x 200 pixels
- Pin: 1000 x 1500 pixels (2:3 ratio)
- Square Pin: 1000 x 1000 pixels
- Profile Picture: 165 x 165 pixels
{
"success": true,
"data": {
"url": "https://pub-0f88a89fca694876be6529864f42efa7.r2.dev/template-xxx.png",
"filename": "template-xxx.png",
"size": 24080,
"format": "png",
"dimensions": { "width": 400, "height": 250 },
"template": {
"variables": ["title", "message"],
"processed": { "title": "Hello World!", "message": "Generated with API" },
"validation": { "isValid": true, "missing": [] }
}
}
}Template with variables:
curl -X POST https://html-to-image-worker.kureckamichal.workers.dev/template/render \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "<div style=\"background: #FF6B6B; color: white; padding: 30px; text-align: center;\"><h1>{{title}}</h1><p>{{message}}</p></div>",
"variables": {"title": "Hello World!", "message": "Generated with API"},
"width": 400,
"height": 250,
"format": "png"
}'Simple HTML rendering:
curl -X POST https://html-to-image-worker.kureckamichal.workers.dev/render \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<div style=\"padding: 40px; background: #007bff; color: white; text-align: center;\"><h1>Simple HTML</h1><p>No variables needed</p></div>",
"width": 400,
"height": 200,
"format": "png"
}'Extract template variables:
curl -X POST https://html-to-image-worker.kureckamichal.workers.dev/template/variables \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template": "<div>Invoice #{{invoiceNumber}} for {{companyName}} - Amount: ${{amount}}</div>"}'Visit the live API for complete documentation with examples:
https://html-to-image-worker.kureckamichal.workers.dev/
The API documentation includes:
- Complete endpoint documentation
- Copy-paste ready curl examples
- Template variable syntax
- Response format details
- Real-world use cases (invoices, certificates, business cards)
- Requests: $0.50 per million requests (first 100K free daily)
- CPU Time: $0.02 per 100,000 GB-seconds
- Browser Rendering: Browser usage included in worker
- R2 Storage: $0.015 per GB stored, $0.36 per million Class A operations
- Worker execution: ~$0.02-0.05
- R2 storage: ~$0.01-0.02
- Total: ~$0.03-0.07 per 1,000 renders
Note: First 100K requests daily are free, R2 has 10GB free storage
- API Key Authentication: All endpoints (except
/and/health) require valid API key - Multiple Auth Methods: Supports X-API-Key, Authorization Bearer, and Api-Key headers
- Secure Key Storage: Production keys stored as Wrangler secrets (never in git)
- XSS Protection: Template variables are sanitized by default
- CORS enabled: For browser requests with security headers
- Input validation: All inputs validated and error handling
- R2 Security: Images stored with public URLs (no authentication needed)
- Template Variables: Automatic HTML escaping prevents XSS
Development:
- Keys stored in
.dev.vars(never committed to git) - Example file:
.dev.vars.example
Production:
# Set API keys securely (not stored in git)
wrangler secret put API_KEYS
# Enter: key1,key2,key3
# List secrets
wrangler secret list
# Delete a secret
wrangler secret delete API_KEYSGenerate secure keys:
# Generate a 32-byte hex key
openssl rand -hex 32# Start development server
npm run dev
# The server will run at http://localhost:8787βββ src/
β βββ index.js # Main worker code
β βββ utils/
β βββ auth-middleware.js # API key authentication
β βββ template-processor.js # Template variable processing
β βββ response-utils.js # Response helpers
β βββ r2-storage.js # R2 storage utilities
βββ wrangler.toml # Cloudflare Workers configuration
βββ package.json # Dependencies and scripts
βββ .dev.vars.example # Environment variables template
βββ CLAUDE.md # Project documentation
βββ README.md # This file
npm run dev- Start local development servernpm run deploy- Deploy to Cloudflare Workersnpm run deploy:production- Deploy to production environment
GET /healthReturns: OK (200 status)
POST /html-to-imageReturns: Binary image data with appropriate Content-Type header
GET /Returns: Interactive HTML demo page
-
"Invalid API token"
- Verify your API token has "Browser Rendering - Edit" permissions
- Check that the token is correctly set in environment variables
-
"Account ID not found"
- Ensure you're using the correct Account ID from Cloudflare dashboard
- Account ID should be a 32-character hex string
-
"Unrecognized keys" error
- The Browser Rendering API has specific parameter requirements
- Ensure you're using supported parameters only
-
JSON parsing errors
- Use proper JSON escaping in curl commands
- Consider using JSON files with
@filenamesyntax
Enable debug logging by setting:
wrangler secret put DEBUG_MODE
# Enter: true- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
MIT License - see LICENSE file for details
Built with β€οΈ using Cloudflare Workers and Browser Rendering API