We can't find the internet
Attempting to reconnect
Template Authoring Guide
How to build OpenGraph OG image templates using Liquid and the available page variables.
OpenGraph templates are HTML documents rendered with Liquid. When someone shares a link, OpenGraph fetches the page, extracts metadata (OG tags, JSON-LD, <title>, etc.), and injects it into your template as Liquid variables. The rendered HTML is then screenshotted at 1200x630 to produce the OG image.
Quick Start
Here’s a minimal template that works on any site:
<div style="width: 1200px; height: 630px; display: flex; align-items: center; justify-content: center; background: #18181b; color: white; font-family: system-ui; padding: 80px;">
<div>
<h1 style="font-size: 48px; margin: 0;">{{ title }}</h1>
<p style="font-size: 24px; color: #a1a1aa; margin-top: 16px;">{{ description }}</p>
<p style="font-size: 18px; color: #71717a; margin-top: 24px;">{{ domain }}</p>
</div>
</div>
This pulls the page title, meta description, and domain name — the three variables available on every page.
Canvas Size
Templates render at 1200x630 pixels (the standard OG image ratio of 1.91:1). Your root element should be exactly this size. You can use inline styles, <style> blocks, or external stylesheets and Google Fonts — they’re all supported.
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
.card { width: 1200px; height: 630px; }
</style>
<div class="card">
<!-- your layout -->
</div>
Variables
Variables are extracted from the shared page’s metadata and injected into your template. They resolve in priority order: JSON-LD first, then OG meta tags, then standard HTML tags.
Core Variables (Available on Every Page)
| Variable | Source Priority | Description |
|---|---|---|
title |
og:title > <title> |
The page title |
description |
og:description > <meta name="description"> |
The page description |
image |
og:image |
The page’s existing OG image URL (useful as a background or fallback) |
domain |
Extracted from URL |
The site’s domain name (e.g. example.com) |
friendly_url |
Computed | A clean version of the full URL without protocol |
Use them with standard Liquid syntax:
<h1>{{ title }}</h1>
<p>{{ description }}</p>
<img src="{{ image }}" />
<span>{{ domain }}</span>
Meta Tag Variables
Access any meta tag directly using the meta prefix:
{{ meta.author }}
{{ meta.keywords }}
{{ meta.og:site_name }}
{{ meta.twitter:creator }}
This reads from the page’s <meta> tags. For example, meta.author resolves <meta name="author" content="...">.
JSON-LD Variables
If the page has JSON-LD structured data (using Schema.org types), you can access it by type and attribute:
<!-- Article schema (https://schema.org/Article) -->
{{ Article.headline }}
{{ Article.author.name }}
{{ Article.datePublished }}
<!-- Product schema (https://schema.org/Product) -->
{{ Product.name }}
{{ Product.brand.name }}
{{ Product.offers.price }}
{{ Product.offers.priceCurrency }}
<!-- Organization schema (https://schema.org/Organization) -->
{{ Organization.name }}
{{ Organization.logo }}
The variable name follows the pattern Type.attribute.nested_attribute. OpenGraph walks the JSON-LD graph to resolve values. See the full list of types and properties at schema.org/docs/schemas.html.
Checking If Variables Exist
Use Liquid conditionals to handle missing data gracefully:
{% if image %}
<img src="{{ image }}" style="width: 100%; height: 100%; object-fit: cover;" />
{% else %}
<div style="background: linear-gradient(135deg, #7c5cf2, #4f46e5); width: 100%; height: 100%;"></div>
{% endif %}
{% if Product.offers.price %}
<span>{{ Product.offers | format_currency }}</span>
{% endif %}
Custom Filters
OpenGraph includes custom Liquid filters beyond the standard set:
format_currency
Formats a price from JSON-LD offers data into a localized currency string.
<!-- With a Product offers object -->
{{ Product.offers | format_currency }}
<!-- Output: "$29.99" -->
This reads price and priceCurrency from the Offer object automatically.
Template Patterns
Text-Only Card
Works on any page. Clean and reliable.
<div style="width: 1200px; height: 630px; display: flex; flex-direction: column; justify-content: space-between; background: #fff; padding: 80px; font-family: system-ui;">
<div>
<h1 style="font-size: 52px; color: #18181b; line-height: 1.1;">{{ title }}</h1>
{% if description %}
<p style="font-size: 24px; color: #71717a; margin-top: 20px; line-height: 1.4;">{{ description }}</p>
{% endif %}
</div>
<p style="font-size: 18px; color: #a1a1aa;">{{ domain }}</p>
</div>
Image Background with Text Overlay
Uses the page’s existing OG image as a background.
<div style="width: 1200px; height: 630px; position: relative; font-family: system-ui;">
{% if image %}
<img src="{{ image }}" style="width: 100%; height: 100%; object-fit: cover;" />
<div style="position: absolute; inset: 0; background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, transparent 60%);"></div>
{% else %}
<div style="width: 100%; height: 100%; background: #18181b;"></div>
{% endif %}
<div style="position: absolute; bottom: 60px; left: 60px; right: 60px; color: white;">
<h1 style="font-size: 48px; line-height: 1.1;">{{ title }}</h1>
<p style="font-size: 20px; opacity: 0.7; margin-top: 12px;">{{ domain }}</p>
</div>
</div>
Product Card (Requires Product JSON-LD)
Pulls structured product data for e-commerce pages.
<div style="width: 1200px; height: 630px; display: flex; background: #fff; font-family: system-ui;">
{% if image %}
<div style="width: 500px; height: 630px; flex-shrink: 0;">
<img src="{{ image }}" style="width: 100%; height: 100%; object-fit: cover;" />
</div>
{% endif %}
<div style="flex: 1; padding: 60px; display: flex; flex-direction: column; justify-content: center;">
{% if Product.brand.name %}
<p style="font-size: 16px; text-transform: uppercase; letter-spacing: 2px; color: #a1a1aa;">{{ Product.brand.name }}</p>
{% endif %}
<h1 style="font-size: 44px; color: #18181b; margin-top: 8px; line-height: 1.1;">{{ Product.name | default: title }}</h1>
{% if Product.offers.price %}
<p style="font-size: 36px; font-weight: 700; color: #18181b; margin-top: 24px;">{{ Product.offers | format_currency }}</p>
{% endif %}
<p style="font-size: 18px; color: #a1a1aa; margin-top: auto;">{{ domain }}</p>
</div>
</div>
Testing Your Template
Use the Preview URL input on the template editor page to test against any live URL. OpenGraph will fetch the page, resolve variables, and render a preview.
You can also use the Inspector to see exactly what metadata a page exposes — this tells you which variables will be available in your template.
Tips
- Always set a fixed canvas size. The root element must be 1200x630. If it’s not, the screenshot will be clipped or padded.
-
Inline styles,
<style>blocks, external CSS, and Google Fonts all work. Use whatever’s easiest for your layout. -
Handle missing variables. Not every page has JSON-LD or even a description. Use
{% if variable %}checks to avoid blank spots. -
{{ image }}pulls fromog:image. If the page has its own OG image set (not a OpenGraph URL), you can use it as a background or fallback in your template. - Keep text large. OG images are often displayed at small sizes in feeds. Headlines should be 40px+ to stay readable when scaled down.
- Test with multiple URLs. A template that looks great on your homepage might break on a blog post with a long title or missing description.