Sign Open Graph Image URLs in Next.js and WordPress

Published October 12, 2026 by Balaji

Uncategorized

How-to for Next.js and WordPress: mint signed OG URLs with POST /api/v1/img/sign. The commercial explainer lives on /use-cases/open-graph-images — this post is the code.

Overview

Every time you share a blog post on Twitter, LinkedIn, or Slack, the first thing people see is your Open Graph image. A great OG image can double your click-through rate. A missing or bad one makes your link look broken.

The problem: most blogs have dozens or hundreds of posts, and manually designing a social preview card for each one doesn’t scale. You need an automated pipeline that generates a unique OG image for every page the moment it’s published.

This post walks through building exactly that — an automated OG image generation pipeline using jsontoimg. By the end, you’ll have a system that takes your blog post data and returns a branded social preview card, ready to be served by your tags.

Why You Need Automated OG Images

Open Graph images serve as the thumbnail when your content is shared on social platforms. Here’s why automating them matters:

  • Scale**: A blog with 200 posts needs 200 unique OG images. Manual design doesn’t scale.
  • Consistency**: Automated templates ensure every card follows the same brand guidelines — same fonts, colors, logo placement.
  • SEO/Social lift**: Pages with custom OG images get 2-3x more social engagement than those without.
  • No developer overhead**: Once the pipeline is set up, each new post gets a card automatically with zero manual work.

The jsontoimg Approach

jsontoimg makes this dead simple. You design a template once, then send JSON data for each post. The API handles the rendering, and you get back a PNG ready to serve.

Here’s the flow:

Blog post published
  → Your server sends JSON (title, excerpt, author, etc.) to jsontoimg API
  → jsontoimg renders the template with your data
  → Returns a PNG image
  → You serve it via your OG meta tags

Step 1: Design Your OG Image Template

First, design a template in jsontoimg. A standard OG image is 1200×630 pixels. Your template should include:

  • Background** — a branded color gradient or subtle pattern
  • Logo** — positioned at the top or bottom corner
  • Blog title** — large, bold text, the primary headline
  • Post title** — smaller, readable, fits within the card
  • Author/date** — subtle attribution at the bottom
  • Decorative elements** — lines, shapes, or brand accents

The template becomes a JSON structure with placeholders for dynamic content. For example:

{
  "width": 1200,
  "height": 630,
  "background": {
    "type": "gradient",
    "colors": ["#1a1a2e", "#16213e", "#0f3460"]
  },
  "layers": [
    {
      "type": "image",
      "src": "https://yourdomain.com/logo.png",
      "x": 40,
      "y": 40,
      "width": 120,
      "height": 40
    },
    {
      "type": "text",
      "text": "named layer post_title",
      "font": "Inter-Bold",
      "fontSize": 48,
      "color": "#ffffff",
      "x": 60,
      "y": 220,
      "maxWidth": 1080,
      "lineHeight": 1.3
    },
    {
      "type": "text",
      "text": "named layer excerpt",
      "font": "Inter-Regular",
      "fontSize": 22,
      "color": "#a0a0b0",
      "x": 60,
      "y": 380,
      "maxWidth": 900,
      "lineHeight": 1.5
    },
    {
      "type": "text",
      "text": "named layer author · named layer date",
      "font": "Inter-Regular",
      "fontSize": 16,
      "color": "#606080",
      "x": 60,
      "y": 540
    }
  ]
}

Step 2: Set Up the Generation Pipeline

Once your template is ready, you call the jsontoimg API whenever a post is published. Here’s a Node.js example:

// generate-og-image.js
const JSONTOIMG_API_KEY = process.env.JSONTOIMG_API_KEY;
const TEMPLATE_ID = 'og-blog-card';

async function generateOGImage(post) {
  const response = await fetch('https://app.jsontoimg.com/api/v1/render', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${JSONTOIMG_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      template: TEMPLATE_ID,
      format: 'png',
      layers: {
        post_title: { text: post.title },
        excerpt: { text: post.excerpt.substring(0, 120) },
        author: { text: post.author },
        date: { text: new Date(post.publishedAt).toLocaleDateString('en-US', {
          month: 'short',
          day: 'numeric',
          year: 'numeric'
        }) }
      }
    })
  });

  const data = await response.json();
  return data.assetUrl;
}

Python version

import requests

JSONTOIMG_API_KEY = os.environ.get('JSONTOIMG_API_KEY')
TEMPLATE_ID = 'og-blog-card'

def generate_og_image(post):
    response = requests.post(
        'https://app.jsontoimg.com/api/v1/render',
        headers={
            'Authorization': f'Bearer {JSONTOIMG_API_KEY}',
            'Content-Type': 'application/json'
        },
        json={
            'template': TEMPLATE_ID,
            'format': 'png',
            'layers': {
                'post_title': { 'text': post['title'] },
                'excerpt': { 'text': post['excerpt'][:120] },
                'author': { 'text': post['author'] },
                'date': { 'text': format_date(post['published_at']) }
            }
        }
    )
    return response.json()['assetUrl']

Step 3: Hook It Into Your CMS

The most common approach is to hook into your CMS’s post-publish event. Here are examples for popular platforms:

Next.js (App Router)

In your blog post page or layout, generate the OG image on build:

// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.slug);

  // Generate OG image using jsontoimg
  const ogUrl = await generateOGImage(post);

  return {
    title: post.title,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [{ url: ogUrl, width: 1200, height: 630 }]
    }
  };
}

WordPress

Use the save_post hook to trigger generation whenever a post is published:

add_action('save_post', 'generate_og_image_for_post', 10, 3);

function generate_og_image_for_post($post_id, $post, $update) {
  if ($post->post_type !== 'post') return;
  if ($post->post_status !== 'publish') return;

  $og_url = call_jsontoimg_api([
    'post_title' => $post->post_title,
    'excerpt'    => wp_trim_words($post->post_excerpt, 20),
    'author'     => get_the_author_meta('display_name', $post->post_author),
    'date'       => get_the_date('M j, Y', $post_id)
  ]);

  update_post_meta($post_id, '_og_image_url', $og_url);
}

Static Site Generator (Hugo/11ty)

Generate all OG images at build time by iterating through your content collection:

// build-og-images.js — run during your build step
const fs = require('fs');
const path = require('path');
const posts = getBlogPosts();

async function buildAllOGImages() {
  for (const post of posts) {
    const imageUrl = await generateOGImage({
      title: post.title,
      excerpt: post.excerpt,
      author: post.author,
      publishedAt: post.date
    });

    // Save the URL or download the image
    const imagePath = path.join('static', 'og', `${post.slug}.png`);
    await downloadImage(imageUrl, imagePath);
  }
}

Before wiring up delivery, it is worth reading the contract you are satisfying. The Open Graph protocol specification defines og:image and its companion og:image:width and og:image:height properties, which is why supplying the dimensions alongside the URL makes crawlers render the card without fetching the file first. In the App Router those values come back from Next.js generateMetadata, which serialises them into the head for you. And because these images are themselves indexable, Google’s image SEO documentation on descriptive filenames and alt text applies to generated assets exactly as it does to hand-made ones.

Step 4: Serve the OG Image

With the image URL stored, add the appropriate meta tags to your page’s <head>:

<meta property="og:image" content="https://yourdomain.com/og/my-awesome-post.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:type" content="image/png" />
<meta name="twitter:card" content="summary_large_image" />

For static sites that pre-render, you can download the image and serve it from your own CDN for faster load times. For dynamic sites, jsontoimg supports webhook delivery — the image URL is pushed to your endpoint when rendering completes.

Batch Generation for Existing Content

What about your existing 150 posts? You don’t need to trigger the publish hook for each one — just run a batch generation:

async function generateForAllExistingPosts() {
  const allPosts = await getPublishedPosts();

  // Send them all at once using jsontoimg's batch endpoint
  const jobs = allPosts.slice(0, 50).map((post) => ({
    template: TEMPLATE_ID,
    format: 'jpg',
    layers: {
      post_title: { text: post.title },
      excerpt: { text: post.excerpt.substring(0, 120) },
      author: { text: post.author },
      date: { text: formatDate(post.date) },
    },
  }));

  const response = await fetch('https://app.jsontoimg.com/api/v1/renders/batch', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${JSONTOIMG_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ jobs })
  });

  const results = await response.json();
  // poll GET /api/v1/renders/batches/{id}; each done job has assetUrl
}

With jsontoimg’s batch endpoint, you can generate 150 OG images in seconds — something that would take hours with manual design tools and days of Puppeteer-infrastructure headache.

Cost Comparison

| Approach | Cost per 1K images | Infrastructure overhead |

|—|—|—|

| Manual design (Canva/Figma) | ~$500 (designer time) | None |

| Puppeteer/Playwright DIY | ~$30 (server cost) | High (Docker, Chrome, scaling) |

| Vercel OG (@vercel/og) | ~$20 (edge functions) | Medium (locked to Vercel) |

| **jsontoimg** | **~$5-10** | **Low (just the API)** |

Beyond cost, jsontoimg saves you from maintaining headless browsers, managing font loading, handling timeouts, and debugging rendering inconsistencies across different environments.

Key Takeaways

  • Design once, generate thousands** — a single jsontoimg template replaces hundreds of manual designs
  • Integrate in minutes** — a few lines of code hook into your CMS or static site generator
  • Batch existing content** — generate OG images for your entire archive in one API call
  • No infrastructure** — no Puppeteer, no Chrome, no Docker containers to maintain
  • Brand consistency** — every social share card follows the same template, every time

FAQ

What size should an Open Graph image be?

1200×630 pixels. It is the ratio Facebook, LinkedIn, and X all crop cleanly, and it stays legible in the small card previews most feeds render.

How do I add an automated Open Graph image to Next.js?

Mint a signed URL with POST /api/v1/img/sign and return it from the route’s metadata.openGraph.images. The URL goes in the HTML head like any other; no build step renders it.

Does every page view re-render the OG image?

No. The first unique GET renders the image and costs one credit; every later request streams the stored file from the CDN. A viral post does not cost more than a quiet one.

Can WordPress generate the og:image automatically?

Yes. The WordPress plugin renders from the post’s own fields on publish and writes the result into the og:image tag, so an editor never has to open a design tool.

Related Resources

Zoom out: How JSON-to-Image Conversion Works maps the whole template, named-layer, and render-job architecture.