Generate Twitter and Open Graph preview images from any URL. Provide a website
URL, pick a template, tweak a few controlled parameters, and get
twitter_post.png (1600×900) and og.png (1200×630).
Usable two ways from one engine:
- CLI — one-shot generation.
- Web UI — a template gallery with a schema-driven control panel and an instant live preview.
The pipeline is URL in → two PNGs out. The same core runs in the CLI, the HTTP API, and the browser.
URL ─► capture (screenshots + OG metadata)
│
▼
template.render(context) ──► HTML ──► rasterize (Puppeteer → PNG) ──► twitter_post.png + og.png
▲ ▲
isomorphic, pure shared browser service
| Path | Role |
|---|---|
src/templates/ |
Isomorphic core — no Node/DOM APIs. Runs server-side (export) and in the browser (live preview), so the preview is pixel-identical to the export. |
src/templates/types.ts |
The Template contract + schema/field vocabulary. |
src/templates/validate.ts |
One validator used by both client and server — defaults-fill, clamp, reject. |
src/templates/template-a/ |
"Split Showcase" template: render.ts (HTML), config.ts (author tokens), schema.ts (user controls). |
src/pipeline/browser.ts |
Shared Puppeteer lifecycle. CLI: launch→work→shutdown. Server: warm pool. |
src/pipeline/capture.ts |
URL → desktop + mobile screenshot buffers. |
src/pipeline/metadata.ts |
Scrapes og:/twitter: tags from the loaded page. |
src/pipeline/rasterize.ts |
HTML → PNG at 2× then sharp-resized to exact size. |
src/render.ts |
Orchestrator: capture → validate → render → rasterize. |
src/main.ts |
CLI entry. |
src/server.ts |
HTTP API for the Web UI. |
web/ |
React + Vite SPA. Imports the isomorphic templates via the @shared alias. |
Each template = a code render function (full CSS power — gradients, shadows,
typography) + JSON-ish config of author-controlled design tokens. The
end-user editable subset is a separate schema, which drives the UI controls,
validation, and render defaults from a single definition. The layout is fixed;
users toggle/restyle slots but never move them, keeping output consistent.
npm install # backend + CLI
cd web && npm install && cd .. # web UIPuppeteer needs a Chromium download on first install. In restricted networks, run
node node_modules/puppeteer/install.jsonce with network access, or point Puppeteer at a system Chrome.
npm run build
# list templates
node build/main.js templates
# generate
node build/main.js gen \
-u https://www.wikipedia.org/ \
-T template-a \
-o ./output \
--params '{"showMobile":false,"accent":"#22d3ee","background":{"variant":"solid","values":{"color":"#0f172a"}}}'Options: -u/--url (required), -T/--template, -o/--out-dir,
-p/--params (JSON), -l/--logo (image path).
# terminal 1 — API
npm run build && npm run server # http://localhost:3001
# terminal 2 — SPA (proxies /api to :3001)
cd web && npm run dev # http://localhost:5173Flow: paste a URL → Load site (captured once) → pick a template → adjust controls (instant preview) → Generate full-res → download both PNGs.
| Method | Endpoint | Body | Returns |
|---|---|---|---|
| GET | /api/templates |
— | template catalog (id, name, schema, config, outputs) |
| POST | /api/capture |
{ url } |
{ meta, screenshots } (data URIs, cached 5 min) |
| POST | /api/generate |
{ url, templateId, params, logo? } |
{ meta, twitter, og } (data URIs) |
The isomorphic core (validation + render output) is unit-tested without a browser:
npm test- Create
src/templates/template-x/withrender.ts,config.ts,schema.ts,index.ts. - Register it in
src/templates/registry.ts.
That's it — the CLI, API, and Web UI pick it up automatically. The control panel renders from the new template's schema with no UI changes.