Strapi v5 Open Graph Tags: Headless CMS OG Image Setup
How to configure og:title, og:image, og:description with Strapi v5. Covers SEO plugin, custom fields, API population, and frontend rendering for social share previews.
Strapi v5 and the OG challenge
Strapi v5 is a headless CMS — it provides the API and content management, but doesn't render HTML. This means OG tags aren't Strapi's direct responsibility. They're the job of your frontend framework (Next.js, Nuxt, Astro, etc.). But Strapi is where you manage the OG data.
The key is structuring your Strapi content types to include all the fields your frontend needs to generate complete OG tags, then populating those fields in API responses.
Setting up the SEO component in Strapi v5
Create a reusable SEO component in Strapi that you can add to any content type:
// In Strapi Admin → Content-Type Builder → Components
// Create: shared/seo
{
"collectionName": "components_shared_seos",
"info": {
"displayName": "SEO",
"description": "Open Graph and SEO metadata"
},
"attributes": {
"metaTitle": {
"type": "string",
"required": true,
"maxLength": 60
},
"metaDescription": {
"type": "text",
"required": true,
"maxLength": 160
},
"ogImage": {
"type": "media",
"allowedTypes": ["images"],
"required": false
},
"canonicalURL": {
"type": "string"
},
"ogType": {
"type": "enumeration",
"enum": ["website", "article", "product"],
"default": "article"
}
}
}Then add this component to your Article content type:
// In your Article content type schema
{
"attributes": {
"title": { "type": "string", "required": true },
"content": { "type": "richtext" },
"seo": {
"type": "component",
"component": "shared.seo"
}
}
}Fetching OG data from Strapi v5 API
When fetching content, make sure to populate the SEO component and its media fields:
// Strapi v5 REST API with population
const res = await fetch(
'https://api.mysite.com/api/articles?filters[slug][$eq]=my-post' +
'&populate[seo][populate]=ogImage'
);
const { data } = await res.json();
const article = data[0];
// Access OG fields
const ogTitle = article.seo?.metaTitle || article.title;
const ogDescription = article.seo?.metaDescription;
const ogImage = article.seo?.ogImage?.url; // Strapi media URLRendering OG tags in Next.js from Strapi
// app/blog/[slug]/page.tsx (Next.js App Router + Strapi v5)
import type { Metadata } from "next";
async function getArticle(slug: string) {
const res = await fetch(
`${process.env.STRAPI_URL}/api/articles?filters[slug][$eq]=${slug}&populate[seo][populate]=ogImage`,
{ headers: { Authorization: `Bearer ${process.env.STRAPI_TOKEN}` } }
);
const { data } = await res.json();
return data[0];
}
export async function generateMetadata({ params }): Promise<Metadata> {
const article = await getArticle(params.slug);
const seo = article.seo;
return {
title: seo?.metaTitle || article.title,
description: seo?.metaDescription,
openGraph: {
title: seo?.metaTitle || article.title,
description: seo?.metaDescription,
images: seo?.ogImage?.url ? [{ url: seo.ogImage.url, width: 1200, height: 630 }] : [],
type: seo?.ogType || "article",
},
twitter: {
card: "summary_large_image",
},
};
}Common Strapi OG pitfalls
- Forgetting to
populatethe SEO component in API queries — nested components aren't included by default - Using Strapi's relative media URLs — prepend your Strapi URL or CDN domain for absolute paths
- Not setting fallbacks — if a content editor skips the SEO fields, your page has no OG tags
- Caching stale OG data — set appropriate revalidation in your frontend
Verify your Strapi-powered site's OG tags
Test any page from your Strapi frontend to see how it looks on Twitter, Slack, Discord, and LinkedIn — with specific fix suggestions.
Check your Strapi site's OG tags →