Generating one OG image for a homepage is quick. Generating them for every blog post, service page, case study, and landing page on a 70-page client site is a different problem. If you are doing it manually — opening a design tool, customizing a template, exporting, uploading — you are spending hours on work that should take minutes.
This post shows how to automate that workflow using the MakeOG API. The result is a script you can run against any URL list and get a folder of ready-to-use WebP images.
The Manual Workflow Problem
Most agencies either skip OG images entirely (and hope the client does not notice) or do one generic image for the whole domain. That generic image ends up on every shared link regardless of the page content, which defeats the purpose.
Page-specific OG images — where the image reflects the actual content of that URL — meaningfully improve click-through rate, especially for blog posts and documentation shared in professional contexts like LinkedIn and Slack.
The only way to make per-page OG images viable across a large site is automation.
How the MakeOG API Works
The API is a single endpoint. Send a POST request to https://makeog.com/api/generate-og with a JSON body containing the URL you want to generate an image for. The response returns a base64-encoded WebP image.
Pro and Team plan subscribers include API access with a bearer token. Include the token in the Authorization header on every request.
The API accepts an optional format parameter if you need platform-specific dimensions: "og" (1200x630, default), "twitter" (1200x628), "linkedin" (1200x627), or "facebook" (1200x630).
A Node.js Script for Bulk Generation
The following script reads an array of URLs, calls the API for each one, decodes the base64 response, and saves the image to disk. Adapt the URL list to pull from a sitemap, a CSV, or a CMS query.
import fs from "fs/promises";
import path from "path";
const API_KEY = process.env.MAKEOG_API_KEY;
const OUTPUT_DIR = "./og-images";
const DELAY_MS = 1200; // stay within rate limits
const urls = [
"https://example.com",
"https://example.com/about",
"https://example.com/services",
"https://example.com/blog/post-one",
"https://example.com/blog/post-two",
];
function slugFromUrl(url) {
return url
.replace(/https?:\/\//, "")
.replace(/[^a-z0-9]/gi, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
}
async function generateOgImage(url) {
const res = await fetch("https://makeog.com/api/generate-og", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`,
},
body: JSON.stringify({ url }),
});
if (!res.ok) {
const text = await res.text();
throw new Error(`API error ${res.status}: ${text}`);
}
const { imageBase64, mimeType } = await res.json();
return { imageBase64, mimeType };
}
async function main() {
await fs.mkdir(OUTPUT_DIR, { recursive: true });
for (const url of urls) {
console.log(`Generating: ${url}`);
try {
const { imageBase64 } = await generateOgImage(url);
const filename = slugFromUrl(url) + ".webp";
const filepath = path.join(OUTPUT_DIR, filename);
await fs.writeFile(filepath, Buffer.from(imageBase64, "base64"));
console.log(` Saved: ${filepath}`);
} catch (err) {
console.error(` Failed: ${err.message}`);
}
// brief pause between requests
await new Promise((r) => setTimeout(r, DELAY_MS));
}
console.log("Done.");
}
main();Run it with node --env-file=.env generate-og.mjs after setting MAKEOG_API_KEY in your .env file.
Pulling URLs From a Sitemap
If the client has an XML sitemap, you can replace the hardcoded array with a sitemap fetch. A minimal approach using fast-xml-parser:
import { XMLParser } from "fast-xml-parser";
async function urlsFromSitemap(sitemapUrl) {
const res = await fetch(sitemapUrl);
const xml = await res.text();
const parser = new XMLParser();
const result = parser.parse(xml);
const entries = result?.urlset?.url ?? [];
return entries.map((e) => e.loc);
}
// replace the hardcoded urls array:
const urls = await urlsFromSitemap("https://example.com/sitemap.xml");This scales to any site size without maintaining the URL list by hand.
Injecting Images Into a CMS
Once you have the images on disk, the injection step depends on the CMS:
- WordPress — upload via the REST API (
POST /wp/v2/media), then update the post meta field (POST /wp/v2/posts/:id) with the attachment ID. Plugins like Yoast and RankMath read from their own meta fields; check their field keys. - Webflow — upload images to the Assets panel, then set the OG image field per-page in the Page Settings panel. Webflow's CMS API supports updating collection item fields programmatically if your pages are CMS-driven.
- Contentful / Sanity — use the Management API to upload the image as an asset, then link it to the entry's OG image field. Both platforms have well-documented asset upload flows.
- Static sites (Next.js, Astro, Eleventy) — copy the generated images into
/public/og/and update theog:imagemeta tag in each page's head to point to the corresponding file path.
For static sites, consider naming images to match the route slug so the path is predictable and the meta tag can be set from a pattern rather than a manual mapping.
Making This Part of Your Agency Workflow
The script above runs in under a minute for most sites. Add it to your pre-launch checklist as a standard step alongside sitemap submission and redirect audits. For clients on retainers, run it any time new pages are added.
A Pro plan on MakeOG ($19/month) includes 500 generations and API access — enough to cover most agency engagements several times over. Get started at makeog.com. The free tier includes 10 generations per month so you can test the output before committing.