Why OG images matter

When someone shares your blog post on Twitter, LinkedIn, Slack, or iMessage, the platform fetches the og:image tag and renders a preview card. If that tag is missing, they use a generic placeholder. If it points to a low-quality or off-brand image, clicks drop.

Studies consistently show that posts with custom OG images get 2–3x more clicks than posts with generic previews. The problem is that creating a custom OG image for every post takes time — unless you automate it.

The automation pattern

The workflow has three steps:

  1. Trigger — new blog post published (webhook, RSS feed, or CMS event)
  2. Render — inject post metadata into an HTML template, call RenderPix, get a 1200×630px PNG back
  3. Store & link — upload to S3 or Cloudinary, update the post’s og:image metadata

Once set up, every new post gets a unique, on-brand OG image automatically.

Step 1: Install the RenderPix n8n node

In your n8n instance: Settings → Community Nodes → Install. Search for n8n-nodes-renderpix and install. Then add a credential under Credentials → RenderPix API with your API key from the RenderPix dashboard.

Step 2: Build the OG image template

A good OG image template needs to work at 1200×630px and communicate the post title clearly at a glance. Here’s a clean starting point:

<!DOCTYPE html>
<html><head><meta charset="UTF-8">
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    width: 1200px; height: 630px;
    font-family: Arial, sans-serif;
    background: #0a0a0b;
    display: flex; align-items: center; justify-content: center;
    position: relative; overflow: hidden;
  }
  .glow {
    position: absolute; top: -100px; left: 50%;
    transform: translateX(-50%);
    width: 800px; height: 400px;
    background: radial-gradient(ellipse, rgba(34,211,238,0.1) 0%, transparent 70%);
  }
  .card {
    position: relative; z-index: 1;
    width: 1080px; padding: 60px;
    border: 1px solid rgba(42,42,48,0.8);
    border-radius: 16px;
    background: rgba(20,20,22,0.8);
  }
  .tag {
    display: inline-block;
    padding: 4px 12px; border-radius: 999px;
    font-size: 13px; font-weight: 600;
    background: rgba(34,211,238,0.1);
    color: #22d3ee; margin-bottom: 20px;
    text-transform: uppercase; letter-spacing: 0.05em;
  }
  .title {
    font-size: 52px; font-weight: 700;
    color: #e8e8ed; line-height: 1.15;
    letter-spacing: -1px; margin-bottom: 24px;
  }
  .meta {
    font-size: 16px; color: #8b8b96;
  }
  .logo {
    position: absolute; bottom: 60px; right: 60px;
    font-size: 20px; font-weight: 700;
    color: #8b8b96; letter-spacing: -0.5px;
  }
  .logo span { color: #22d3ee; }
</style></head>
<body>
  <div class="glow"></div>
  <div class="card">
    <div class="tag">${category}</div>
    <div class="title">${title}</div>
    <div class="meta">${readingTime} min read &middot; renderpix.dev</div>
  </div>
  <div class="logo">render<span>pix</span></div>
</body></html>

Step 3: Build the n8n workflow

Node 1 — Webhook trigger

Add a Webhook node. Set it to POST. Copy the webhook URL — you’ll configure your CMS or blog platform to call this URL when a post is published.

The payload should include: title, category, slug, and optionally readingTime.

Node 2 — Code node (build HTML)

const { title, category, slug, readingTime = '5' } = $input.first().json;

const truncatedTitle = title.length > 60
  ? title.substring(0, 60) + '...'
  : title;

const html = `<!DOCTYPE html>
<html><head><meta charset="UTF-8">
<style>/* your CSS here */</style></head>
<body>
  <div class="glow"></div>
  <div class="card">
    <div class="tag">${category}</div>
    <div class="title">${truncatedTitle}</div>
    <div class="meta">${readingTime} min read · renderpix.dev</div>
  </div>
  <div class="logo">render<span>pix</span></div>
</body></html>`;

return [{ json: { html, slug, title } }];

Truncate long titles. OG images are viewed at small sizes on most platforms. Titles over 60 characters often get cut off or become unreadable. Truncating in the Code node (not in CSS) gives you a clean result.

Node 3 — RenderPix

Add a RenderPix node: Operation → Render HTML, HTML → {{ $json.html }}, Width → 1200, Height → 630, Format → PNG, Return as → Binary.

Node 4 — Upload to S3

Add an AWS S3 node. Set the key to og/{{ $json.slug }}.png so each post gets a predictable, addressable URL. Set the ACL to public-read.

The resulting URL will be: https://your-bucket.s3.amazonaws.com/og/your-post-slug.png

Node 5 — Update CMS metadata

Use an HTTP Request node (or a CMS-specific node) to update your post’s og:image field with the S3 URL. The exact API call depends on your CMS.

Triggering the workflow from your CMS

CMSTrigger method
WordPressWebhooks plugin or Action hook on publish_post
GhostBuilt-in webhooks (Settings → Integrations)
ContentfulWebhook on entry.publish
SanityGROQ-powered webhook
Notionn8n Notion trigger node
Static site (GitHub)GitHub Actions workflow dispatch

Performance and cost

MetricValue
Render time~230ms per image
Output size (typical)80–150 KB PNG
Cost at 100 posts/monthFree tier (100 renders)
Cost at 2,000 posts/month$9/mo (Starter)

For most blogs publishing fewer than 100 posts per month, this workflow runs entirely within the free tier.

Variations worth building

Replace Figma with a single fetch call

Full HTML and CSS support. Pre-warmed Chromium — no cold starts. Free tier included.

Also see: Instagram carousel automation with n8n and OG images with Next.js.