OG Image API Comparison: Price and What Renders
We earn commissions when you shop through the links below, at no extra cost to you. We only link products we would use ourselves.
Open Graph image tools split three ways, and the split matters more than the price. Satori converts JSX to SVG with its own flexbox-only layout engine, then rasterizes with resvg: no browser, fast, and it renders a strict subset of CSS. Headless Chromium renders whatever a browser renders, costs more per image, and has no surprises because it is the same engine that drew your design in the first place. Template APIs hand you a visual editor and fill in variables, which is a different product entirely: you are not shipping HTML at all.
Pick the wrong one and you find out at the worst moment, usually when a designer adds display: grid or someone puts an emoji in a headline. Below: per-image prices from each vendor's live pricing page, the documented limits of each engine, and pixel-level tests we ran against our own render API on August 15, 2026, because "supports CSS" is not a testable claim.
Per-image price, six options
Every price checked against the vendor's own pricing page on August 15, 2026. "Per image at entry" divides the entry plan by its included images, which is the number that decides things, not the sticker.
| Option | Approach | Entry price | Per image at entry | Cheapest published |
|---|---|---|---|---|
| Skillforge Render API | Chromium | $10 prepaid, 4,000 units | $0.0025 (an OG image is 1 unit) | $0.0021 ($50 / 24,000 units) |
| @vercel/og | Satori + resvg, your function | free library | your function cost | n/a |
| workers-og | Satori + resvg WASM, your Worker | free library | your Worker cost | n/a |
| HTML/CSS to Image | Chrome | $14/mo, 1,000 images | $0.014 | $0.0037 ($749 / 200,000) |
| Placid | Template editor | $19/mo, 500 credits | $0.038 | $0.0025 ($249 / 100,000) |
| Bannerbear | Template editor | $49/mo, 1,000 credits | $0.049 | $0.0149 ($149 / 10,000) |
Things that change those numbers in practice:
- The two free libraries are not free to run.
@vercel/ogcosts whatever the function running it costs: Vercel Hobby is free, Pro is a $20/month platform fee that includes one deploying seat and $20 of monthly usage credit.workers-ogruns on Cloudflare Workers, where the free plan allows 100,000 requests a day but only 10 milliseconds of CPU per invocation. Satori layout plus resvg rasterization is CPU work, not I/O, so measure your actual CPU time against that 10ms before you budget for free. Workers Paid starts at $5/month with 30 million CPU milliseconds included. - Placid credits roll over, most do not. Unused credits carry to the next month up to 2x your monthly budget while the subscription is active, and previews are unlimited and free. One image is one credit up to 4000px on the longest side, then it scales (8 credits for 4001 to 8000px).
- Bannerbear's cheapest per-image rate is at $149, not at the top. Automate is $49 for 1,000 credits and Scale is $149 for 10,000, so the mid tier is three times cheaper per image than the entry tier. The free trial is 30 API credits with no card.
- HTML/CSS to Image gives you a hosted URL. As long as the account is active they keep the generated image stored and served from its own URL, so you never build storage. They run Google Chrome and say they track stable releases within a few weeks. The free tier is 50 images a month with no card.
- We do not host anything. Our endpoint returns PNG bytes and nothing else: no image URL, no CDN, no retention. You put it in R2 or S3 and serve it yourself. If you want a URL to paste into a
<meta>tag with no storage of your own, HTML/CSS to Image, Placid, and Bannerbear all beat us on that, and it is the most common reason to pick one of them.
What Satori will not render
This is documented rather than tested, straight from the Satori README and Vercel's docs, because it is a hard boundary and not a performance nuance:
- Flexbox only.
displayisflex,contents, ornone. Vercel's docs put it plainly: "Advanced layouts (display: grid) will not work." No float, no block, no table, no inline-block. - You must supply the font bytes. "You must specify the font if any text is rendered with Satori, and pass the font data as ArrayBuffer or Buffer." TTF, OTF, and WOFF only. WOFF2 is not supported, which is the format Google Fonts serves to modern browsers by default, so you fetch and convert rather than link.
- Emoji need a callback. Satori has no emoji font. You wire up
graphemeImagesorloadAdditionalAssetto map each grapheme to an image, or the emoji does not appear. - Also missing:
calc(),z-index, three-dimensional transforms, kerning and ligatures, and RTL languages. - 500KB total bundle on Vercel, including your JSX, CSS, fonts, and images.
None of that makes Satori a bad choice. It is fast, it runs where a browser cannot, and it caches on the CDN automatically on Vercel. It means your OG template has to be designed for Satori, and you cannot paste in a card your design system already renders and expect a match.
What Chromium renders, tested at the pixel level
We ran eight requests through our live /v1/og endpoint and decoded the returned PNGs in Node to check actual pixels, not vibes. The harness is ops/render-og-probe.js in our repo.
| Test | Result |
|---|---|
Default size, no width/height given | 1200x630 PNG, RGB, 17,405 bytes. Matches the size every platform recommends. |
display: grid, red cell in column 2 of 2 | Left-center pixel #ffffff, right-center #ff0000. Grid laid out correctly. |
Google Fonts <link> vs the same markup without it | 2,940 of 84,000 sampled pixels differ. The linked WOFF2 font downloaded and applied, with no font plumbing. |
| A rocket emoji at 300px | 2,953 distinct colors in the glyph box. Full-color emoji, no callback, no config. |
Remote <img> from another origin | Center pixel is the exact image color. The render waited for the network image. |
That is the whole argument for the Chromium side in five rows: grid works, <link href="fonts.googleapis.com"> works, emoji work, remote images work. The card you built in the browser is the card you get.
Three gotchas we found in our own endpoint, which we would rather publish than have you discover:
- Dimensions clamp silently. We asked for 2400x1400 and got 1920x1080 back with a
200, no warning. The ceiling is 1920x1080 and the floor is 320x240. If you are generating a 2x retina card at 2400px wide, you will get a smaller image and no error telling you so. formatis ignored on/v1/og. Pass"format": "jpeg"and you still get a PNG. The parameter is accepted by the validator and then overridden by the OG renderer.urlis rejected. OG takeshtmlonly, capped at 100KB, and returns{"error":{"code":"bad_params","message":"og requires html (not url)"}}. Screenshotting a page into an OG card is the/v1/screenshotendpoint's job.
Real requests
Get a key with no signup (100 trial units, one key per IP per day):
curl -sX POST https://render.skillforge99.com/v1/keys \
-H 'content-type: application/json' -d '{}'
# {"apiKey":"sfr_..."}
Render a card:
curl -X POST https://render.skillforge99.com/v1/og \
-H "Authorization: Bearer $RENDER_KEY" \
-H 'content-type: application/json' \
-d '{"html":"<h1 style=\"font:700 84px system-ui;margin:80px 60px\">Release 2.4 is out</h1>"}' \
-o card.png
That returns 200 image/png at 1200x630 with the billing state in the headers:
content-type: image/png
x-render-units: 1
x-credit-remaining: 87
Then you store card.png yourself and point a meta tag at wherever you put it:
<meta property="og:image" content="https://cdn.example.com/cards/release-2-4.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
Choosing, in one pass
- Your OG card reuses your real design system, or has grid, or has emoji in user-supplied text: headless Chromium. Ours, HTML/CSS to Image, or your own browser render. Satori will cost you a rewrite of the template plus an emoji asset pipeline.
- You are on Next.js, the card is a simple flex layout you control, and volume is high:
@vercel/og. It is free, it caches on the CDN for you, and at scale nothing beats not paying per image. Design the template against Satori from the start. - You want a URL to paste in a meta tag and no storage to run: HTML/CSS to Image at $14 a month, or Placid if a non-developer needs to edit the design.
- A marketing team owns the card design: Placid or Bannerbear. The template editor is the product, and paying $0.038 an image to keep design out of a code review is often correct.
- Cards are generated once at build time, not per request: none of this. Render them in CI with Playwright and commit the PNGs. Zero per-image cost, zero runtime risk.
- Very high volume, steady: the flat server wins here as it does everywhere. We ran that math for screenshots in render API or self-hosted droplet; at 100,000 cards a month, a DigitalOcean droplet running Playwright is cheaper than every hosted row in the table above.
FAQ
What size should an Open Graph image be? 1200x630 pixels. Vercel's docs, and effectively every platform, recommend it, and both @vercel/og and our /v1/og default to exactly that when you pass no dimensions. Declare og:image:width and og:image:height alongside the URL so crawlers do not have to fetch the file to lay out a preview.
Why is my emoji missing from the OG image? Almost certainly Satori. It ships no emoji font, so an emoji renders as nothing unless you configure graphemeImages or loadAdditionalAsset to map graphemes to images (Twemoji is the usual choice). On headless Chromium this does not come up: our probe rendered a full-color emoji, 2,953 distinct colors in the glyph, with no configuration at all.
Can I use a Google Font with @vercel/og? Yes, but you fetch the font file and pass the bytes; you cannot just link the stylesheet. Satori accepts TTF, OTF, and WOFF, and not WOFF2, which is what the standard Google Fonts CSS serves. Vercel documents downloading a character subset from Google Fonts to stay under the 500KB bundle limit. On a browser-based API, a plain <link> tag works, which our A/B test confirms at the pixel level.
Is there a free OG image API? For evaluation, several. HTML/CSS to Image gives 50 images a month with no card, Bannerbear a 30-credit trial, Placid a trial with a few credits, and ours mints a 100-unit trial key (100 OG images) per IP per day with no signup. For genuinely free at volume, the answer is a library rather than an API: @vercel/og on Vercel Hobby or workers-og on the Workers free plan, with the CPU limit above as the real constraint.
Should I cache generated OG images? Yes, and this is the biggest cost lever in the whole comparison. A crawler may refetch your card many times, and regenerating it per request is how a $10 prepaid pack disappears in a week. Generate once per unique input, store the bytes, and serve them from a CDN. @vercel/og does this for you; with a raw byte API like ours, it is your job, and it is the difference between paying for 500 cards and paying for 50,000 fetches.