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 by default (the standard OG image ratio of 1.91:1). Size your root element to match whatever dimensions you’re rendering at. 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>
Custom sizes (paid plans)
1200x630 is the default, not a hard limit. Custom render sizes are available on the Professional plan and up. Each plan caps the maximum dimensions: Starter stays at 1200x630, Professional allows up to 2048x2048, Enterprise up to 4096x4096 (with an absolute infrastructure ceiling of 8192 in any dimension). On Free/Starter the size stays 1200x630 regardless of what you request.
There are two ways to set a custom size:
-
Origin config — set
width/heighton the origin so every/cdnrender for that origin comes out at that size. Set via GraphQL or the CLI (og origin create/og origin update). -
Per-page ops — control size from the template’s JavaScript with
window.__og. See Using JavaScript in Templates for the full API. The relevant ops:-
['config', { width, height }]— override the viewport dimensions. -
['clip', x, y, w, h]— return only a rectangular region of the render. -
['capture', '#selector']— size the output to a single element’s bounding rect.
-
There is no named preset (no “story”, “square”, or “tiktok” keyword) — you request exact pixel dimensions. The only named ratio is the origin’s default_ratio (h630 = 1.9:1 → 1200x630, h600 = 2:1 → 1200x600, custom = anything else).
Vertical 9:16 example (1080x1920 story / TikTok card)
A vertical “story” card fits under the Professional 2048x2048 cap and works today — request the exact dimensions and size the root element to match. Set the origin (or render) to 1080x1920, then:
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
.story { width: 1080px; height: 1920px; }
</style>
<div class="story" style="display: flex; flex-direction: column; justify-content: flex-end; background: #18181b; color: white; font-family: system-ui; padding: 80px;">
<h1 style="font-size: 72px; line-height: 1.05;">{{ title }}</h1>
<p style="font-size: 32px; color: #a1a1aa; margin-top: 24px;">{{ domain }}</p>
</div>
For animated (GIF / animated WebP) output at any size, see the animation topic.
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. Size the root element to your render dimensions (1200x630 by default, or a custom size on paid plans). If it’s not sized to match, 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.