TinaCMS Open Graph Tags: Add OG Meta to Your Git-Based CMS

How to add og:title, og:image, and og:description to TinaCMS-powered sites — with frontmatter fields, Next.js generateMetadata, and dynamic OG images.

TinaCMS and OG tags architecture

TinaCMS is a Git-backed headless CMS where content lives in Markdown/MDX files with frontmatter. This means OG metadata belongs in your frontmatter schema, and your Next.js (or other) frontend reads it at build time or on request via the Tina GraphQL Content API.

The key advantage: every OG field is version-controlled in Git alongside your content. Editors can update social previews in TinaCMS's visual editor without touching code.

Step 1: Define OG fields in your Tina schema

Add OG fields to your collection schema in tina/config.ts:

// tina/config.ts
import { defineConfig } from "tinacms";

export default defineConfig({
  schema: {
    collections: [
      {
        name: "post",
        label: "Posts",
        path: "content/posts",
        format: "mdx",
        fields: [
          { type: "string", name: "title", label: "Title", isTitle: true, required: true },
          { type: "string", name: "excerpt", label: "Excerpt", ui: { component: "textarea" } },
          { type: "image", name: "heroImage", label: "Hero Image" },
          
          // OG-specific fields
          {
            type: "object",
            name: "seo",
            label: "SEO & Open Graph",
            fields: [
              {
                type: "string",
                name: "ogTitle",
                label: "OG Title",
                description: "Leave blank to use post title",
              },
              {
                type: "string",
                name: "ogDescription",
                label: "OG Description",
                ui: { component: "textarea" },
                description: "Leave blank to use excerpt",
              },
              {
                type: "image",
                name: "ogImage",
                label: "OG Image",
                description: "1200×630px recommended. Leave blank to use hero image.",
              },
            ],
          },
        ],
      },
    ],
  },
});

Step 2: Query OG fields via Tina's GraphQL API

Use TinaCMS's client to query content including OG fields in your Next.js generateMetadata:

// app/posts/[slug]/page.tsx
import type { Metadata } from "next";
import client from "@/tina/__generated__/client";

export async function generateMetadata({
  params,
}: {
  params: { slug: string };
}): Promise<Metadata> {
  const { data } = await client.queries.post({
    relativePath: `${params.slug}.mdx`,
  });

  const post = data.post;
  const seo = post.seo;

  const ogTitle = seo?.ogTitle || post.title;
  const ogDescription = seo?.ogDescription || post.excerpt || "";
  const ogImage = seo?.ogImage || post.heroImage;

  return {
    title: ogTitle,
    description: ogDescription,
    openGraph: {
      title: ogTitle,
      description: ogDescription,
      type: "article",
      images: ogImage
        ? [{ url: ogImage, width: 1200, height: 630 }]
        : [],
    },
    twitter: {
      card: "summary_large_image",
      title: ogTitle,
      description: ogDescription,
      images: ogImage ? [ogImage] : [],
    },
  };
}

Step 3: Dynamic OG images with opengraph-image.tsx

For auto-generated OG images (no image upload required), use Next.js's built-inopengraph-image.tsx convention:

// app/posts/[slug]/opengraph-image.tsx
import { ImageResponse } from "next/og";
import client from "@/tina/__generated__/client";

export const runtime = "edge";
export const size = { width: 1200, height: 630 };

export default async function OGImage({
  params,
}: {
  params: { slug: string };
}) {
  const { data } = await client.queries.post({
    relativePath: `${params.slug}.mdx`,
  });

  const title = data.post.seo?.ogTitle || data.post.title;

  return new ImageResponse(
    (
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          background: "linear-gradient(135deg, #1e1b4b, #312e81)",
          width: "100%",
          height: "100%",
          padding: "60px 80px",
          justifyContent: "flex-end",
        }}
      >
        <div style={{ fontSize: 56, fontWeight: 900, color: "#fff", lineHeight: 1.2 }}>
          {title}
        </div>
        <div style={{ fontSize: 28, color: "#a5b4fc", marginTop: 24 }}>
          yoursite.com
        </div>
      </div>
    ),
    { width: 1200, height: 630 }
  );
}

Common TinaCMS OG pitfalls

  • Image field format: Tina image fields return relative paths — prepend your base URL before using in OG tags
  • ISR cache: Use revalidate to ensure OG meta updates propagate after content edits
  • Media manager URLs: If using Tina Cloud media, images are served from Cloudinary — ensure they're accessible to social crawlers
  • Missing alt text: OG images don't need alt text, but your CMS image fields should include it for accessibility

Preview your TinaCMS 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 →