# HTML to image: your HTML and CSS, rendered to PNG in real Chrome

An HTML to image API lays your markup and CSS out in a browser and returns a picture of it. With Shareglint you save the HTML and CSS once as a template; after that every image is one HTTP request — POST /v1/images with the template name and the text that changes — and the answer is the URL of a PNG, JPEG, WebP or PDF file, usually in about a second.

It is real Chrome, not a re-implementation of CSS: grid, flexbox, gradients, shadows, emoji and right-to-left text render as they do in your browser. Try it below without an account.

## How it works in the API

1. Save the template once. POST /v1/templates with your HTML, CSS, size and the names of the fields that change. Fields go into the HTML as {{ name }}; the HTML is sanitised on save.
2. Render an image. POST /v1/images with the template slug, the field values and a format. With ?wait=true the URL of the finished file comes back on the same connection.
3. Use the URL. In og:image, an email, a feed or a download. The same fields again return the existing file and use no quota.

While you design, POST /v1/templates/preview renders a draft without saving it and uses no quota.

## Code: curl, JavaScript, PHP and Python

### curl

```bash
# 1. Save the template once
curl -X POST "https://shareglint.com/v1/templates" \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Article card",
  "width": 1200,
  "height": 630,
  "fields": [
    "kicker",
    "title",
    "author"
  ],
  "html": "<div class=\"card\"><p class=\"kicker\">{{ kicker }}</p><div class=\"fitbox\"><h1 class=\"fit\" data-autofit>{{ title }}</h1></div><p class=\"by\">{{ author }}</p></div>",
  "css": ".card{height:100%;padding:72px;display:grid;grid-template-rows:auto 1fr auto;gap:24px;background:#0f172a;color:#f8fafc;font-family:Roboto,sans-serif} .kicker{color:#fbbf24;font-size:26px;letter-spacing:.1em;text-transform:uppercase} h1{font-size:76px;line-height:1.08} .by{font-size:28px;color:#94a3b8}"
}'
# → {"id": "tpl_…", "slug": "article-card", …}

# 2. One call per image (use the slug from step 1)
curl -X POST "https://shareglint.com/v1/images?wait=true" \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "template": "article-card",
  "format": "png",
  "modifications": {
    "kicker": "Engineering",
    "title": "How we cut our build time in half",
    "author": "Ada Lovelace"
  }
}'
```

### JavaScript

```js
const API = 'https://shareglint.com/v1';
const headers = {
  Authorization: 'Bearer sk_live_YOUR_KEY',
  'Content-Type': 'application/json',
};

// 1. Save the template once
const template = await fetch(`${API}/templates`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    name: "Article card",
    width: 1200,
    height: 630,
    fields: ['kicker', 'title', 'author'],
    html: "<div class=\"card\"><p class=\"kicker\">{{ kicker }}</p><div class=\"fitbox\"><h1 class=\"fit\" data-autofit>{{ title }}</h1></div><p class=\"by\">{{ author }}</p></div>",
    css: ".card{height:100%;padding:72px;display:grid;grid-template-rows:auto 1fr auto;gap:24px;background:#0f172a;color:#f8fafc;font-family:Roboto,sans-serif} .kicker{color:#fbbf24;font-size:26px;letter-spacing:.1em;text-transform:uppercase} h1{font-size:76px;line-height:1.08} .by{font-size:28px;color:#94a3b8}",
  }),
}).then((r) => r.json());

// 2. One call per image
const image = await fetch(`${API}/images?wait=true`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    template: template.slug,
    format: 'png',
    modifications: {
      kicker: "Engineering",
      title: "How we cut our build time in half",
      author: "Ada Lovelace",
    },
  }),
}).then((r) => r.json());

console.log(image.url);
```

### PHP (Laravel)

```php
use Illuminate\Support\Facades\Http;

$api = Http::withToken('sk_live_YOUR_KEY')->baseUrl('https://shareglint.com/v1');

// 1. Save the template once
$template = $api->post('/templates', [
    'name' => 'Article card',
    'width' => 1200,
    'height' => 630,
    'fields' => ['kicker', 'title', 'author'],
    'html' => '<div class="card"><p class="kicker">{{ kicker }}</p><div class="fitbox"><h1 class="fit" data-autofit>{{ title }}</h1></div><p class="by">{{ author }}</p></div>',
    'css' => '.card{height:100%;padding:72px;display:grid;grid-template-rows:auto 1fr auto;gap:24px;background:#0f172a;color:#f8fafc;font-family:Roboto,sans-serif} .kicker{color:#fbbf24;font-size:26px;letter-spacing:.1em;text-transform:uppercase} h1{font-size:76px;line-height:1.08} .by{font-size:28px;color:#94a3b8}',
])->json();

// 2. One call per image
$image = $api->post('/images?wait=true', [
    'template' => $template['slug'],
    'format' => 'png',
    'modifications' => [
        'kicker' => 'Engineering',
        'title' => 'How we cut our build time in half',
        'author' => 'Ada Lovelace',
    ],
])->json();

echo $image['url'];
```

### Python

```python
import requests

API = "https://shareglint.com/v1"
HEADERS = {"Authorization": "Bearer sk_live_YOUR_KEY"}

# 1. Save the template once
template = requests.post(f"{API}/templates", headers=HEADERS, json={
    "name": "Article card",
    "width": 1200,
    "height": 630,
    "fields": ["kicker", "title", "author"],
    "html": "<div class=\"card\"><p class=\"kicker\">{{ kicker }}</p><div class=\"fitbox\"><h1 class=\"fit\" data-autofit>{{ title }}</h1></div><p class=\"by\">{{ author }}</p></div>",
    "css": ".card{height:100%;padding:72px;display:grid;grid-template-rows:auto 1fr auto;gap:24px;background:#0f172a;color:#f8fafc;font-family:Roboto,sans-serif} .kicker{color:#fbbf24;font-size:26px;letter-spacing:.1em;text-transform:uppercase} h1{font-size:76px;line-height:1.08} .by{font-size:28px;color:#94a3b8}",
}).json()

# 2. One call per image
image = requests.post(f"{API}/images?wait=true", headers=HEADERS, json={
    "template": template["slug"],
    "format": "png",
    "modifications": {
        "kicker": "Engineering",
        "title": "How we cut our build time in half",
        "author": "Ada Lovelace",
    },
}).json()

print(image["url"])
```

### What comes back

```json
{
    "id": "img_01m08s3ez1zcv28h335g9923bw",
    "kind": "image",
    "status": "completed",
    "format": "png",
    "size": "og",
    "width": 1200,
    "height": 630,
    "url": "https://cdn.shareglint.com/renders/img_01m08s3ez1zcv28h335g9923bw.png",
    "duration_ms": 912
}
```

## Output formats

- `png`: Lossless. scale 2 (the default) doubles the pixels: a 1200×630 template gives a 2400×1260 file; scale 1 gives exactly 1200×630.
- `jpeg`: Smaller files for photos; quality from 1 to 100, 85 by default.
- `webp`: Smaller still, same quality setting as JPEG.
- `pdf`: One page exactly the template size, backgrounds printed, text kept as vector and selectable.
- `gif`, `mp4`: Animated: records the template's CSS animations, up to 5 seconds; 5 renders of quota each.

One template renders in every size preset too (og 1200×630, square 1080×1080, portrait 1080×1350, story 1080×1920, pinterest 1000×1500, youtube 1280×720) — which size each platform wants. https://shareglint.com/en/og-image-size

## Why real Chrome, and not a library

There are three common ways to turn HTML into an image yourself. Each works; each has a cost that shows up later.

| Option | CSS it renders | What you take on |
|---|---|---|
| Satori / @vercel/og | A subset: JSX with flexbox layout, no CSS grid | Write the card in JSX, load font files yourself, run it in your Next.js or edge function |
| wkhtmltoimage | An old Qt WebKit engine: no CSS grid, modern CSS unreliable | A tool whose project was archived in 2023, installed on your servers |
| Puppeteer or Playwright on your servers | Everything Chrome renders | Chrome's memory and crashes, fonts on every machine, a queue, and isolating any HTML you did not write |
| Shareglint | Everything Chrome renders | One HTTP request; Chrome, fonts, sandboxing, cache and storage are ours |

If your site is on Next.js and the card fits in flexbox, @vercel/og is free and runs next to your pages — use it. Running Chrome yourself is the right call when you already operate it at scale. An API is worth it when you would rather not do either.

## Limits, plainly

- No JavaScript: scripts are removed when a template is saved, so animate with CSS. For a live page that needs its scripts, take a screenshot of its URL with the Screenshot API.
- No external URLs inside the HTML or CSS. Images come in through image fields — we download them, check them and embed them — or as data: URIs.
- Fonts come from our list (Roboto, Open Sans, Carlito, DejaVu Sans, DejaVu Serif, DejaVu Sans Mono, Ubuntu, Noto Sans CJK JP, Noto Sans CJK SC, Noto Color Emoji); you cannot upload font files.
- HTML and CSS up to 64 KB each, from 200 to 4,000 pixels a side.

## What it costs

Free: 100 images a month, no card, with a small watermark. Starter: $9 a month for 2,000 images ($4.50 per thousand), no watermark. A render you already have — same template, same fields, same format — is served from cache and uses no quota.

## Questions

### What is an HTML to image API?

A web service that takes HTML and CSS, lays it out in a browser engine and returns an image of the result — PNG, JPEG, WebP or a PDF. You use it when the picture should be built from data: social cards, certificates, receipts, charts with text, email banners.

### How do I convert HTML to PNG with an API?

With Shareglint: save the HTML and CSS once with POST /v1/templates, then call POST /v1/images?wait=true with the template slug, the field values and "format": "png". The response holds the URL of the PNG.

### Can I turn HTML into a PDF instead?

Yes. "format": "pdf" returns one page exactly the size of the template, with backgrounds printed and text kept as vector, so it stays sharp and selectable.

### Does it support CSS grid, flexbox and custom fonts?

Grid, flexbox and the rest of modern CSS work, because the renderer is Chrome. Fonts come from the families installed in the renderer (Roboto, Open Sans, Carlito, DejaVu Sans, DejaVu Serif, DejaVu Sans Mono, Ubuntu, Noto Sans CJK JP, Noto Sans CJK SC, Noto Color Emoji); uploading your own font files is not supported.

### Can the HTML run JavaScript?

No. Templates are sanitised on save and scripts are removed, which is what makes it safe to render HTML from anyone. To capture a page that needs its scripts, use the Screenshot API with the page's URL.

### Is there a free HTML to image API?

The Free plan renders 100 images a month with no card, with a small watermark. The playground on this page needs no account at all.

### How is this different from HTMLCSStoImage?

HTMLCSStoImage takes the full HTML in every request. Shareglint stores the template once and takes only the changing fields, adds text auto-fitting and a cache that uses no quota, and comes with 153 ready-made templates. If your HTML is different on every request, their model fits better.

## Related

- [Your own templates in the docs](https://shareglint.com/en/docs#szablony)
- [API reference: POST /v1/templates](https://shareglint.com/en/docs/api#post-templates)
- [Ready-made OG image templates](https://shareglint.com/en/templates)
- [OG image generator for your framework](https://shareglint.com/en/og-image-generator)
- [Screenshot API (render a URL)](https://shareglint.com/en/screenshot-api)
- [Shareglint vs HTMLCSStoImage](https://shareglint.com/en/alternatives/htmlcsstoimage)
- [OG image size by platform](https://shareglint.com/en/og-image-size)
