HTML to Image in JavaScript and Node.js
Render HTML to a PNG from Node.js using native fetch or axios.
Best for: Node-based backends, serverless functions on Vercel/Netlify/Cloudflare, and any build script that needs OG images on demand. Native
fetchworks in Node 18+;axiosadds richer interceptors.
Worked examples for the HTML, Screenshot and Templates endpoints, plus a fetch retry pattern.
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 link to the url directly, or download the image from it if you want a local copy.
Node.js Examples
HTML to Image Example
Here’s a simple example using the built-in fetch (Node.js 18+):
import fs from 'fs/promises';
async function htmlToImage(html, css = '') {
const response = await fetch('https://app.html2img.com/api/html', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.HTML2IMG_API_KEY
},
body: JSON.stringify({
html,
css,
width: 800,
height: 600
})
});
const data = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.message || 'Failed to generate image');
}
console.log(`Image available at: ${data.url}`);
console.log(`Credits remaining: ${data.credits_remaining}`);
// Optionally download the image to disk
const imageResponse = await fetch(data.url);
const buffer = Buffer.from(await imageResponse.arrayBuffer());
await fs.writeFile('output.png', buffer);
return data.url;
}
// Example usage
const html = `
<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from Node.js!</h1>
<p>Generated at ${new Date().toLocaleString()}</p>
</div>
`;
const css = `
h1 { color: #2563eb; }
p { color: #4b5563; }
`;
htmlToImage(html, css).catch((error) => {
console.error('Error:', error.message);
});
Screenshot Example
Taking a screenshot of a webpage:
async function takeScreenshot(url) {
const response = await fetch('https://app.html2img.com/api/screenshot', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.HTML2IMG_API_KEY
},
body: JSON.stringify({
url,
width: 1200,
height: 800,
dpi: 2,
fullpage: true
})
});
const data = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.message || 'Failed to take screenshot');
}
return data.url;
}
takeScreenshot('https://example.com')
.then((imageUrl) => console.log(`Screenshot: ${imageUrl}`))
.catch((error) => console.error('Error:', error.message));
Express.js Example
Here’s how to use it with Express.js. Since the API returns a URL, you can simply redirect (or return JSON) rather than proxying the image bytes:
import express from 'express';
const app = express();
app.get('/generate-image', async (req, res) => {
try {
const response = await fetch('https://app.html2img.com/api/html', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.HTML2IMG_API_KEY
},
body: JSON.stringify({
html: `
<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from Express!</h1>
<p>Generated at ${new Date().toLocaleString()}</p>
</div>
`,
width: 800,
height: 600
})
});
const data = await response.json();
if (!data.success) {
throw new Error(data.message || 'Failed to generate image');
}
res.json({ url: data.url, id: data.id });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.get('/screenshot', async (req, res) => {
try {
const { url = 'https://example.com' } = req.query;
const response = await fetch('https://app.html2img.com/api/screenshot', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.HTML2IMG_API_KEY
},
body: JSON.stringify({
url,
width: 1200,
height: 800,
dpi: 2,
fullpage: true
})
});
const data = await response.json();
if (!data.success) {
throw new Error(data.message || 'Failed to take screenshot');
}
res.redirect(data.url);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Browser Examples
Using Fetch API
Here’s how to use the API directly in the browser. Because the response is JSON containing a public image URL, you can use it as an <img> src directly - no blob handling required:
async function generateImage() {
try {
const response = await fetch('https://app.html2img.com/api/html', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'your-api-key' // Be careful with API key exposure!
},
body: JSON.stringify({
html: `
<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from the Browser!</h1>
<p>Generated at ${new Date().toLocaleString()}</p>
</div>
`,
width: 800,
height: 600
})
});
const data = await response.json();
if (!data.success) {
throw new Error(data.message || 'Failed to generate image');
}
// Display the image by pointing an <img> at the returned URL
const img = document.createElement('img');
img.src = data.url;
document.body.appendChild(img);
} catch (error) {
console.error('Error:', error.message);
}
}
React Component Example
Here’s a React component that uses the API:
import { useState } from 'react';
function ImageGenerator() {
const [imageUrl, setImageUrl] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const generateImage = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('https://app.html2img.com/api/html', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.REACT_APP_HTML2IMG_API_KEY
},
body: JSON.stringify({
html: `
<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from React!</h1>
<p>Generated at ${new Date().toLocaleString()}</p>
</div>
`,
width: 800,
height: 600
})
});
const data = await response.json();
if (!data.success) {
throw new Error(data.message || 'Failed to generate image');
}
setImageUrl(data.url);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
return (
<div>
<button
onClick={generateImage}
disabled={loading}
>
{loading ? 'Generating...' : 'Generate Image'}
</button>
{error && (
<div style={{ color: 'red' }}>
Error: {error}
</div>
)}
{imageUrl && (
<div>
<img
src={imageUrl}
alt="Generated content"
style={{ maxWidth: '100%' }}
/>
</div>
)}
</div>
);
}
export default ImageGenerator;
Vue Component Example
Here’s a Vue 3 component that uses the API:
<template>
<div>
<button
@click="generateImage"
:disabled="loading"
>
{{ loading ? 'Generating...' : 'Generate Image' }}
</button>
<div v-if="error" class="error">
Error: {{ error }}
</div>
<div v-if="imageUrl">
<img
:src="imageUrl"
alt="Generated content"
style="max-width: 100%"
/>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const imageUrl = ref(null);
const loading = ref(false);
const error = ref(null);
const generateImage = async () => {
loading.value = true;
error.value = null;
try {
const response = await fetch('https://app.html2img.com/api/html', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': import.meta.env.VITE_HTML2IMG_API_KEY
},
body: JSON.stringify({
html: `
<div style="padding: 20px; background: #f0f0f0;">
<h1>Hello from Vue!</h1>
<p>Generated at ${new Date().toLocaleString()}</p>
</div>
`,
width: 800,
height: 600
})
});
const data = await response.json();
if (!data.success) {
throw new Error(data.message || 'Failed to generate image');
}
imageUrl.value = data.url;
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
</script>
<style scoped>
.error {
color: red;
margin: 1rem 0;
}
</style>
When using the API in the browser, make sure to handle your API key securely. Never expose it directly in client-side code. Instead, proxy your requests through a backend server.
For production apps, add rate limiting, error handling, and loading states for a smoother experience.
Common patterns
fetch with retry
async function renderHtmlWithRetry(html, options = {}, attempts = 3) {
for (let attempt = 0; attempt < attempts; attempt++) {
const response = await fetch('https://app.html2img.com/api/html', {
method: 'POST',
headers: { 'X-API-Key': process.env.HTML2IMG_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ html, ...options }),
});
if (response.ok) {
return (await response.json()).url;
}
if (response.status >= 500 && attempt < attempts - 1) {
await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
continue;
}
throw new Error(`html2img returned ${response.status}`);
}
}
const url = await renderHtmlWithRetry('<h1>Hello</h1>', { width: 1200, height: 630 });
Backs off on 5xx and bails on 4xx so you do not retry validation errors.
Templates in JavaScript
Use a named template when your data is structured. Here is the Invoice Image template:
const response = await fetch('https://app.html2img.com/api/v1/templates/invoice-image', {
method: 'POST',
headers: { 'X-API-Key': process.env.HTML2IMG_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
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',
}),
});
const { url } = await response.json();
The returned url is hosted on i.html2img.com.