Placid vs HTML/CSS to Image: Templates Compared

Placid vs HTML/CSS to Image for templated images: who edits the design, template versioning, URL API security defaults, credit rules and Python code.

By Simon Chiu · Updated

Placid and HTML/CSS to Image (HCTI) both generate images from templates, and both can be called from code or from no-code tools. They suit different teams because they keep the template in different places.

  • Placid keeps the template in its visual editor. Layers have names; your data fills them. Its Editor SDK can put that editor inside your own product, so your customers design their own templates.
  • HCTI keeps the template as HTML and CSS with Handlebars variables. You can create and edit templates through the API, so they can live in your Git repository and be deployed by CI.

Pick Placid when non-developers own the design, or when your product needs to offer template editing to its users. Pick HCTI when developers own the design and want it versioned with the code.

Disclosure: I build RenderScreenshot, which renders HTML to images. Placid and HCTI are not our products, and we have no accounts with either. This page compares documented behaviour (checked 2026-10-15) and includes request code run against a local mock of both APIs. It has no latency numbers.

This is one page of our comparison hub. For HCTI against a different template tool, see HTML/CSS to Image vs Bannerbear.

Verdict

Question Placid HCTI
Who edits the template? Designers and marketers, in a visual editor Developers, in HTML and CSS (a visual editor also exists)
Can your customers edit templates inside your app? Yes, Editor SDK No embeddable editor found in its docs
Can templates live in Git and ship through CI? No; they live in Placid Yes; create and edit them through the API
Pin a template version in production? Not covered in the docs we read Yes: /v1/image/:template_id/:template_version
Image URL you can put in <img> without a backend call URL API, public by default, optional signing Signed image URLs
Insert a live website screenshot into a template Yes, Browserframe layer Yes, render a url (not inside a template layer)
PDFs and video from the same template Yes PDF yes (change the URL extension), video no
No-code integrations listed in the docs Airtable, Zapier, Make, n8n, WordPress, Webflow, Ghost Not compared here; HCTI is API-first
Running out of credits Generation stops; no overage Optional overage you switch on

Two template models

Placid: layers you fill

A Placid template is a design with named layers: text, picture, shape, barcode, rating, subtitle and browserframe. An API call names the template and sends values per layer. Nothing about the layout is in your code, which is the point: a designer can move the logo without a deploy.

# Placid: fill a template's layers. create_now asks for a synchronous render;
# without it the image is queued and you poll polling_url (or use webhook_success).
import os
import time
import requests

headers = {"Authorization": f"Bearer {os.environ['PLACID_API_TOKEN']}"}
resp = requests.post(
    "https://api.placid.app/api/rest/images",
    headers=headers,
    json={
        "template_uuid": os.environ["PLACID_TEMPLATE_UUID"],
        "create_now": True,
        "layers": {
            "title": {"text": "Q3 release notes"},
            "img": {"image": "https://example.com/cover.jpg"},
        },
        "modifications": {"image_format": "png"},
    },
    timeout=60,
)
resp.raise_for_status()
image = resp.json()

for _ in range(30):   # fall back to polling if the render was queued
    if image["status"] in ("finished", "error"):
        break
    time.sleep(1)
    image = requests.get(image["polling_url"], headers=headers, timeout=30).json()

if image["status"] != "finished":
    raise RuntimeError(f"Placid render did not finish: {image}")
print("image URL:", image["image_url"])

We ran this against a local mock of Placid's REST API twice: once with create_now: True and once queued, to exercise the polling loop. Placid's docs warn that create_now can fail when your worker is busy or you send many requests at once, so keep the polling fallback.

The Browserframe layer is a feature HCTI does not have in this form. You pass a website URL as the layer's image, Placid screenshots it (1280x1024 by default, set with image_viewport), and places it inside a browser-window frame in your design. That is handy for "new feature" posts and product-update cards.

HCTI: HTML you version

An HCTI template is HTML and CSS with {{variables}}. Because the API can create and edit templates, the template can be a pair of files in your repository:

<div class="card"><h1>{{title}}</h1><p>{{site}}</p></div>
.card { width: 1200px; height: 630px; box-sizing: border-box; padding: 72px; display: flex;
        flex-direction: column; justify-content: center; background: #0f172a; color: #f8fafc;
        font-family: Inter, sans-serif; }
h1 { font-size: 64px; margin: 0; }

A short script in CI pushes them. The first run creates the template; later runs edit it, and each edit returns a new template_version:

# Keep an HTML/CSS to Image template in your repo and push it from CI.
# Creates the template the first time; edits it (new version) when HCTI_TEMPLATE_ID is set.
import os
import pathlib
import requests

auth = (os.environ["HCTI_USER_ID"], os.environ["HCTI_API_KEY"])
src = pathlib.Path("templates")
payload = {
    "name": "og-card",
    "html": (src / "og-card.html").read_text(),
    "css": (src / "og-card.css").read_text(),
    "google_fonts": "Inter",
    "viewport_width": 1200,
    "viewport_height": 630,
}

template_id = os.environ.get("HCTI_TEMPLATE_ID")
url = f"https://hcti.io/v1/template/{template_id}" if template_id else "https://hcti.io/v1/template"
resp = requests.post(url, auth=auth, json=payload, timeout=30)
resp.raise_for_status()
body = resp.json()
# Pin this version in production renders: POST /v1/image/{template_id}/{template_version}
print(body["template_id"], body["template_version"])

Rendering then sends only the values:

# HTML/CSS to Image: render a saved template (HTML with {{handlebars}} variables).
import os
import requests

auth = (os.environ["HCTI_USER_ID"], os.environ["HCTI_API_KEY"])
template_id = os.environ["HCTI_TEMPLATE_ID"]

resp = requests.post(
    f"https://hcti.io/v1/image/{template_id}",
    auth=auth,
    json={"template_values": {"title": "Q3 release notes", "site": "acme.dev"}},
    timeout=60,
)
resp.raise_for_status()
print("image URL:", resp.json()["url"])

All three HCTI snippets ran against a local mock that checks Basic auth and the required html and template_values fields. The free plan allows 5 templates and paid plans 1,000, per HCTI's docs, and edits do not count against that limit.

Pinning matters more than it looks. Without a version in the URL, HCTI renders the latest version, so a CSS change pushed for one page changes every card generated after it. Pin the version in production and move the pin when you have checked the new design.

URL APIs and their security defaults

Both products can render an image from a URL alone, which is what you want for og:image tags. Their defaults differ.

Placid's URL API is open by default. Its docs say you can use it without authentication. A URL looks like https://api.placid.app/u/{template_uuid}?title[text]=.... Anyone who sees one of your og:image URLs can change the query string and render their own text on your template, on your credits. Placid offers signed URLs (an HMAC-SHA256 signature of the query string with your private token, appended as the last parameter s) and a project setting that accepts signed URLs only. Turn that setting on before you put template URLs in public pages.

HCTI's dynamic URLs are signed. Its docs describe signed image URLs, including for templates, that render on first request. Signing happens on your server, so there is no unsigned mode to forget about.

The same rule applies to any image service with URL rendering: the signature must cover every parameter that changes the output, and the secret must never reach the browser. Our own version of this is described in signed URLs.

Credits and billing rules

We could not read Placid's plan prices from its pricing page (they load with JavaScript), so this table compares the billing rules both vendors publish, checked 2026-10-15. HCTI's prices are on its pricing page: a free plan of 50 images a month, then paid plans from $14 a month.

Rule Placid HCTI
Image 1 credit (canvas up to 4,000 px) 1 image
PDF 2 credits per page A PDF is an image request with a .pdf extension
Video 10 credits per 10 seconds Not offered
Test renders Free watermarked previews in test mode Free plan for testing
Unused allowance Rolls over, up to 2x the monthly credits Not stated in the pages we read
Over the limit Generation stops; email at 80% used Optional overage, $10 per 1,000 images on Basic and Pro
Free start Trial with a few credits, no card 50 images a month, no card

Two practical consequences:

  • Multi-page PDFs are expensive on Placid. A 10-page report is 20 credits. If PDFs are most of your volume, price that first.
  • A hard stop is safer for budgets and riskier for uptime. With Placid, a viral post cannot run up a bill, but your OG images stop rendering when credits run out. With HCTI overage switched on, images keep working and the bill grows.

How we would test them head-to-head

We have no data for this section, only the method. If you have trial accounts:

  1. Build the same 1200x630 card in both: a title, a subtitle, a logo and a background image.
  2. Render 20 cards with unique titles through each REST API, synchronously (create_now for Placid). Record time to image URL and time to download.
  3. Render the same 20 through each URL API from a browser, which is what social crawlers do. Record time to first byte for a new URL and for a repeat.
  4. Try hostile input: a 200-character title, emoji, right-to-left text, and <b> tags. See which one overflows, which escapes, and which renders markup.
  5. Change the template and check which images change: already-generated ones, new ones only, or both.

Step 5 is where the two products differ most in practice, and it is the one most teams skip.

When each one wins

Placid wins when the template is a design asset owned by non-developers, when your SaaS needs to let its own users edit templates (Editor SDK), when you publish through Airtable, WordPress, Webflow or Ghost, or when you need video from the same template.

HCTI wins when developers own the template, want it reviewed and versioned in Git, need any CSS a browser supports, and also want URL screenshots and HTML renders from the same key.

Where RenderScreenshot fits

RenderScreenshot is closer to HCTI: you send HTML or a URL and it renders in Chromium. We have no hosted template editor, so the template stays in your code and you send the finished HTML. Signed URLs work for og:image tags, and every render is cached with a public CDN link in the X-Cache-URL header.

# RenderScreenshot Python SDK: your own HTML as a 1200x630 image, no template editor.
import html
import os
from renderscreenshot import Client, TakeOptions

client = Client(os.environ['RS_API_KEY'])
title = html.escape("Q3 release notes: <faster> & smaller")
card = f"""<div style="width:1200px;height:630px;box-sizing:border-box;padding:72px;display:flex;
  align-items:center;background:#0f172a;color:#f8fafc;font:700 64px system-ui">{title}</div>"""

image = client.take(TakeOptions.html(card).preset('og_card'))
with open("card-rs.png", "wb") as f:
    f.write(image)
print("saved card-rs.png")

We tested this against a local mock of our API, not the live service. The Python SDK installs from GitHub (pip install git+https://github.com/Render-Screenshot/rs-python.git) until it is published on PyPI. If a designer must edit templates without code, Placid does that and we do not. Get a free API key to try the HTML route.

Last checked 2026-10-15: Placid REST, URL API and layer docs, HCTI image and template docs, and both pricing pages. Python 3.14.8 with requests 2.34.2; vendor calls run against a local mock only.

Frequently asked questions

Is the Placid URL API public by default?
Yes. Placid's docs say you can use the URL API without authentication, and that you can restrict a project to signed URLs in its settings. If your template UUIDs appear in public HTML, turn on the signed-only setting so others cannot render images on your credits.
Can I keep HTML/CSS to Image templates in Git?
Yes. Templates are HTML and CSS with Handlebars variables, and the API can create a template (POST /v1/template) and edit it (POST /v1/template/:template_id). Each edit gets a new template_version, and you can pin a version when you render.
Can my own customers edit templates?
With Placid, yes: its Editor SDK embeds the template editor in your app. HTML/CSS to Image has a visual template editor in its own dashboard, but we found no embeddable editor in its docs.
What happens when I run out of credits?
Placid stops generating (except free previews) and does not charge overage; unused credits roll over up to twice the monthly amount. HTML/CSS to Image offers optional overage billing that is off until you enable it.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.