Chapter 5

Templates

Build custom social cards with dynamic content

Templates let you build completely custom social card layouts. Instead of styling your existing page, you define a new layout that extracts content from your page using JavaScript expressions.

How templates work

Add a <template> element with the id ogplus to your page:

<template id="ogplus">
  <div style="padding: 48px; background: #1a1a2e; color: white; height: 100%;">
    <h1 style="font-size: 48px; margin: 0;">
      ${document.querySelector('h1')?.textContent}
    </h1>
    <p style="font-size: 24px; color: #888; margin-top: 16px;">
      ${document.querySelector('meta[name="description"]')?.content}
    </p>
  </div>
</template>

When OpenGraph+ renders your page, it:

  1. Finds the <template id="ogplus"> element
  2. Evaluates any ${...} expressions against your page’s DOM
  3. Replaces the page body with the rendered template
  4. Captures the result as your social card image

The template content is invisible to regular visitors—<template> elements don’t render in the browser.

Template expressions

Use ${...} to insert dynamic content. Any valid JavaScript expression works:

<template id="ogplus">
  <!-- Text content -->
  <h1>${document.querySelector('h1')?.textContent}</h1>

  <!-- Attribute values -->
  <img src="${document.querySelector('.hero img')?.src}">

  <!-- Meta tags -->
  <p>${document.querySelector('meta[property="og:description"]')?.content}</p>

  <!-- Computed values -->
  <span>${new Date().toLocaleDateString()}</span>

  <!-- Conditional content -->
  <span>${document.querySelector('.author')?.textContent || 'Anonymous'}</span>
</template>

Use optional chaining (?.) to safely handle missing elements without errors.

Platform-specific templates

Create different layouts for different social platforms using ogplus-{platform} ids:

<!-- Twitter-specific template -->
<template id="ogplus-twitter">
  <div style="background: #15202b; color: white; padding: 40px;">
    <h1>${document.querySelector('h1')?.textContent}</h1>
  </div>
</template>

<!-- LinkedIn-specific template -->
<template id="ogplus-linkedin">
  <div style="background: #0077b5; color: white; padding: 60px;">
    <h1>${document.querySelector('h1')?.textContent}</h1>
  </div>
</template>

<!-- Fallback for other platforms -->
<template id="ogplus">
  <div style="background: white; padding: 40px;">
    <h1>${document.querySelector('h1')?.textContent}</h1>
  </div>
</template>

OpenGraph+ checks for templates in this order:

  1. template#ogplus-{platform} (e.g., template#ogplus-twitter)
  2. template#ogplus (generic fallback)

If no template is found, OpenGraph+ falls back to rendering your page normally (with CSS styling via data-ogplus).

Inline styles

Templates render in isolation—your page’s stylesheets don’t apply. Use inline styles for all formatting:

<template id="ogplus">
  <div style="
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    height: 100%;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    font-family: system-ui, sans-serif;
  ">
    <h1 style="font-size: 64px; margin: 0; text-align: center;">
      ${document.querySelector('h1')?.textContent}
    </h1>
  </div>
</template>

Full viewport layout

Your template fills the entire social card viewport. Use height: 100% on your root element to take up the full space:

<template id="ogplus">
  <div style="
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    background: #f5f5f0;
  ">
    <!-- Your content -->
  </div>
</template>

Grabbing existing elements

Instead of rebuilding your layout from scratch, you can grab and restyle existing elements from your page:

<template id="ogplus">
  <div style="padding: 40px; background: white; height: 100%;">
    ${document.querySelector('.article-header')?.outerHTML}
  </div>
</template>

This clones the .article-header element with all its children. Note that styles may not carry over if they depend on parent selectors or external stylesheets.

Example: Blog post card

A complete example for blog posts:

<template id="ogplus">
  <div style="
    display: flex;
    flex-direction: column;
    padding: 48px;
    background: #0f172a;
    color: white;
    font-family: system-ui, sans-serif;
    height: 100%;
    box-sizing: border-box;
  ">
    <div style="
      font-size: 14px;
      text-transform: uppercase;
      letter-spacing: 2px;
      color: #94a3b8;
      margin-bottom: 24px;
    ">
      ${document.querySelector('.category')?.textContent || 'Blog'}
    </div>

    <h1 style="
      font-size: 56px;
      font-weight: bold;
      line-height: 1.1;
      margin: 0 0 24px 0;
      flex: 1;
    ">
      ${document.querySelector('h1')?.textContent}
    </h1>

    <div style="
      display: flex;
      align-items: center;
      gap: 16px;
      font-size: 18px;
      color: #94a3b8;
    ">
      <img 
        src="${document.querySelector('.author-avatar')?.src}" 
        style="width: 48px; height: 48px; border-radius: 50%;"
      >
      <span>${document.querySelector('.author-name')?.textContent}</span>
      <span>·</span>
      <span>${document.querySelector('.publish-date')?.textContent}</span>
    </div>
  </div>
</template>

Example: Product card

For e-commerce or product pages:

<template id="ogplus">
  <div style="
    display: flex;
    height: 100%;
    background: white;
    font-family: system-ui, sans-serif;
  ">
    <img 
      src="${document.querySelector('.product-image')?.src}"
      style="width: 50%; height: 100%; object-fit: cover;"
    >
    <div style="
      flex: 1;
      padding: 48px;
      display: flex;
      flex-direction: column;
      justify-content: center;
    ">
      <h1 style="font-size: 36px; margin: 0 0 16px 0;">
        ${document.querySelector('.product-name')?.textContent}
      </h1>
      <p style="font-size: 24px; color: #16a34a; font-weight: bold; margin: 0;">
        ${document.querySelector('.product-price')?.textContent}
      </p>
    </div>
  </div>
</template>

Combining with meta tags

Templates work alongside other OpenGraph+ meta tags:

<head>
  <!-- Set viewport width for template rendering -->
  <meta property="og:plus:viewport:width" content="800">

  <!-- Add extra styles to the body -->
  <meta property="og:plus:style" content="margin: 0; padding: 0;">
</head>

<body>
  <template id="ogplus">
    <!-- Your template -->
  </template>
</body>

Templates vs CSS styling

Feature CSS Styling Templates
Complexity Simple show/hide Full custom layouts
Styles Uses your existing CSS Inline styles only
Layout Adapts your page Completely custom
Content Shows/hides existing elements Extracts and rearranges content
Best for Minor adjustments Branded social cards

Use CSS styling when you just need to hide navigation and adjust spacing. Use templates when you want a completely different layout for social cards.