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 fetch works in Node 18+; axios adds 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.