A whole campaign of posts, from one template and your data.
A social media post generator API that turns rows of data into feed-ready images – quote cards, stat graphics, product announcements, and multi-slide carousels. Design the layout once, render hundreds of on-brand posts in a single script.
The problem
Content calendars are hungry. A single week of posting can mean a dozen quote cards, a stats roundup, two carousels, and a few announcements – each one a separate Figma export, hand-typed and hand-positioned. The design is always the same; only the words and numbers change.
That's mechanical work pretending to be creative work. If the layout is fixed and the content comes from a spreadsheet, a CMS, or an API, the image should come from there too – rendered on demand, never exported by hand.
What you can render
Quote cards
Pull a quote + attribution from a sheet, drop it into a branded 1080×1080 layout. One row of data, one finished post.
Stat & metric graphics
“We hit 10k users” style cards generated straight from your analytics. The number is live data, the design is fixed.
Carousel slides
Render an N-slide deck from an array – consistent header, footer, and progress dots, numbered slide-01…slide-N.
Product & feature announcements
Fire a render from your release webhook so the launch post is ready the moment the changelog ships.
Event & webinar promos
Speaker name, title, date, headshot URL → a polished promo image per session, batched for the whole agenda.
User-generated highlights
Turn a review, testimonial, or leaderboard entry into a shareable graphic automatically as it comes in.
One template, a full carousel – Node.js example
Take an array of slide data and render a complete multi-slide carousel in parallel. The template receives the slide index and the total so it can draw progress dots and keep branding identical across every frame:
// scripts/render-carousel.ts
import { writeFile } from "node:fs/promises";
const SIZE = { width: 1080, height: 1080 }; // square feed post
const slides = [
{ kicker: "Thread", title: "5 ways agents render images" },
{ kicker: "01", title: "REST API – one POST per image" },
{ kicker: "02", title: "CLI – render straight from your shell" },
{ kicker: "03", title: "MCP – your AI agent does it itself" },
{ kicker: "Recap", title: "One engine. Three interfaces." },
];
function slide(s: { kicker: string; title: string },
i: number, total: number) {
return `
<div style="
width: ${SIZE.width}px; height: ${SIZE.height}px;
display: flex; flex-direction: column; justify-content: space-between;
padding: 80px; box-sizing: border-box;
background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%);
color: white; font-family: 'Inter', sans-serif;
">
<div style="font-size: 28px; letter-spacing: 4px;
text-transform: uppercase; opacity: 0.6;">
${s.kicker}
</div>
<h1 style="font-size: 72px; line-height: 1.1; font-weight: 700;">
${s.title}
</h1>
<div style="display: flex; gap: 10px;">
${Array.from({ length: total }, (_, d) => `
<span style="width: 14px; height: 14px; border-radius: 999px;
background: ${d === i ? "#a5b4fc" : "rgba(255,255,255,0.2)"};">
</span>`).join("")}
</div>
</div>`;
}
await Promise.all(slides.map(async (s, i) => {
const res = await fetch("https://api.codetoimage.app/v1/render", {
method: "POST",
headers: {
"X-API-Key": process.env.CODETOIMAGE_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
html: slide(s, i, slides.length),
...SIZE, format: "png",
}),
});
const buf = Buffer.from(await res.arrayBuffer());
await writeFile(`out/slide-${String(i + 1).padStart(2, "0")}.png`, buf);
}));Every slide fires in parallel and lands as slide-01.png … slide-05.png, ready to upload as a carousel. Swap the slides array for rows from your CMS or sheet and the whole deck regenerates on demand.
Common feed-post sizes
| Format | Size | Ratio |
|---|---|---|
| Instagram – square post / carousel | 1080×1080 | 1:1 |
| Instagram – portrait post | 1080×1350 | 4:5 |
| LinkedIn – square in-feed post | 1200×1200 | 1:1 |
| X / Twitter – in-feed image | 1600×900 | 16:9 |
| Facebook – feed post | 1200×1200 | 1:1 |
| Stories / Reels cover | 1080×1920 | 9:16 |
Reference current as of 2026. Need link-preview thumbnails instead of feed posts? See Social cards.
What you get
Data in, posts out
A spreadsheet row, a CMS entry, or an API response becomes a finished image. No copy-paste into a design tool.
Brand baked into the template
Web fonts, color tokens, and your logo live in the HTML once. Content changes every render; the design never drifts.
Carousels in one pass
Render an N-slide deck in parallel with consistent headers, footers, and progress dots – numbered for upload.
Hosted URLs for publishing APIs
output: "url" hands a public link straight to the Instagram Graph API, LinkedIn, or a scheduler like Buffer or Later.
Pixel-perfect typography
Real headless Chromium with @font-face web fonts – what renders is exactly what your audience sees in the feed.
Slots into automations
Trigger renders from n8n, Make, a cron job, or a release webhook. The image step fits any pipeline you already run.
FAQ
How is this different from social cards / OG images?▾
Social cards (OG images) are the link-preview thumbnails a platform scrapes from your meta tags when someone shares a URL. Social media posts are the creatives you publish directly into the feed – the square or portrait image of a quote, a stat, a product drop, or a carousel slide. Same render engine, different job: cards are about a single 1.91:1 preview pulled by a scraper, posts are about batches of branded 1:1 / 4:5 / carousel images you upload yourself or push through a publishing API.
Can I generate a multi-slide carousel from one template?▾
Yes – that's the most common pattern. You loop over an array of slide data (one object per slide), render each through /v1/render at the same dimensions, and number the filenames slide-01.png, slide-02.png, etc. The HTML template takes the slide index and total count so you can draw the "1 / 7" progress dots and carry consistent branding across every frame. Most teams render the whole deck in parallel in well under a second per slide.
How do I keep posts on-brand without a designer in the loop?▾
Bake the brand into the HTML/CSS template once: web fonts via @font-face, your color tokens as CSS variables, logo as an inline SVG or data URI. After that, every render pulls real data into a fixed layout – the copy and numbers change, the design never drifts. Because we render in real headless Chromium, what you see in a browser preview is exactly what ships.
Can I auto-post the rendered images, or just generate them?▾
We generate the image; publishing is up to you. Use output: "url" to get a hosted URL and hand it straight to the Instagram Graph API, LinkedIn, or a scheduler like Buffer/Later that accepts image URLs. Or render to bytes, drop the PNG into your asset pipeline, and let your social tool pick it up. The render step slots into any publishing workflow – n8n, Make, a cron job, or your own backend.
Stop hand-exporting every post.
Free Sandbox tier – 50 renders/month, no credit card. Turn one template and a list of data into an entire campaign of on-brand posts.