Contentful Open Graph Tags: Add OG Meta Using Content Model Fields
How to add og:title, og:image, and og:description to Contentful-powered sites — with SEO content type fields, Next.js generateMetadata, and Contentful Image API transformations.
Contentful OG architecture
Contentful is a headless CMS where OG metadata belongs in your Content Model as dedicated fields. The standard approach is to create a reusable "SEO" content type with OG fields, then reference it from your page/post content types.
Your frontend (typically Next.js) queries these fields via the Contentful Delivery API and uses them in generateMetadata.
Step 1: Create an SEO content type in Contentful
In Contentful → Content Model, create a new content type called "SEO" with these fields:
- metaTitle — Short text (required)
- metaDescription — Long text
- ogImage — Media (single file, image)
- noIndex — Boolean (optional)
Then add a Reference field called seo to your Page, Post, or other content types, pointing to the SEO content type.
Step 2: Query OG fields via the Delivery API
// lib/contentful.ts
import { createClient } from "contentful";
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
});
export async function getPage(slug: string) {
const entries = await client.getEntries({
content_type: "page",
"fields.slug": slug,
include: 2, // Include SEO nested reference
limit: 1,
});
return entries.items[0] || null;
}
// app/[slug]/page.tsx
import type { Metadata } from "next";
import { getPage } from "@/lib/contentful";
export async function generateMetadata({
params,
}: {
params: { slug: string };
}): Promise<Metadata> {
const page = await getPage(params.slug);
if (!page) return { title: "Page Not Found" };
const fields = page.fields as any;
const seo = fields.seo?.fields;
const title = seo?.metaTitle || fields.title;
const description = seo?.metaDescription || fields.excerpt || "";
const ogImageFile = seo?.ogImage?.fields?.file;
// Contentful image URL with OG-optimized transforms
const ogImageUrl = ogImageFile
? `https:${ogImageFile.url}?w=1200&h=630&fit=fill&f=faces&fm=jpg&q=80`
: undefined;
return {
title,
description,
openGraph: {
title,
description,
type: "website",
images: ogImageUrl ? [{ url: ogImageUrl, width: 1200, height: 630 }] : [],
},
twitter: {
card: "summary_large_image",
title,
description,
images: ogImageUrl ? [ogImageUrl] : [],
},
};
}Contentful Image API for OG optimization
Contentful's Image API lets you transform images at the URL level — no CDN setup needed:
// Transform parameters:
// ?w=1200 — width 1200px
// &h=630 — height 630px
// &fit=fill — fill crop (vs. pad which adds letterboxing)
// &f=faces — smart crop around faces
// &fm=jpg — JPEG format (smaller than PNG for photos)
// &q=80 — 80% quality (good balance of size/quality)
// &fl=progressive — progressive JPEG
const ogImageUrl = `https:${imageUrl}?w=1200&h=630&fit=fill&fm=jpg&q=80&fl=progressive`;
// For text-heavy or logo images, use PNG instead:
const ogLogoBannerUrl = `https:${imageUrl}?w=1200&h=630&fit=fill&fm=png&q=90`;Step 3: Use contentful-management to bulk-add SEO fields
If you have existing entries without SEO fields, use the Contentful Management API to add them in bulk:
// scripts/add-seo-defaults.mjs
import { createClient } from "contentful-management";
const client = createClient({ accessToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN });
const space = await client.getSpace(process.env.CONTENTFUL_SPACE_ID);
const env = await space.getEnvironment("master");
const entries = await env.getEntries({
content_type: "post",
"fields.seo[exists]": false,
limit: 1000,
});
for (const entry of entries.items) {
const seoEntry = await env.createEntry("seo", {
fields: {
metaTitle: entry.fields.title,
metaDescription: entry.fields.excerpt,
},
});
await seoEntry.publish();
// Link SEO entry to post
entry.fields.seo = { "en-US": { sys: { type: "Link", linkType: "Entry", id: seoEntry.sys.id } } };
await entry.update();
await entry.publish();
console.log(`Updated: ${entry.fields.title?.["en-US"]}`);
}Common Contentful OG pitfalls
- Protocol-relative URLs: Contentful returns image URLs as
//images.ctfassets.net/...— always prependhttps: - include depth: Set
include: 2to resolve the nested SEO reference; defaultinclude: 1won't return the SEO fields - Preview API: Use the Preview API (
preview.contentful.com) for draft content, but always use the Delivery API in production for OG tags - ISR: Add
next: { revalidate: 60 }to your fetch calls so OG metadata updates propagate after Contentful edits
Preview your Contentful OG tags
After deploying, paste your page URL into OGFixer to verify how your social previews look on Twitter, LinkedIn, Discord, and Slack.
Check your OG tags free →