You fill in a title, a summary, some keywords and a feature image in the editor. This page is what Leed builds out of them — the exact tags that land in every page head, where each value comes from, and what happens when one is missing. Every tag described here is emitted on every plan.
The controls themselves live in the editor’s Page Settings panel; this page is the output side of that seam. Set it there, see it here.
What lands in every head, in order
Leed emits four metadata blocks, always in the same sequence:
leed/metadata/siteMetadata— feed and sitemap discovery, the favicon,charset,generator,title,keywords,description, the canonical link, and apublishedmeta.leed/metadata/twitterCard— thetwitter:*properties.leed/metadata/ogCard— theog:*andarticle:*properties.- The JSON-LD block — or, on almost every page, an HTML comment saying none was generated.
Note what is not in that list. Leed emits no <title> element. It emits <meta name="title">, which no browser tab and no search result uses. The <title> is your master template’s job — see How Templates Work.
Page metadata and the canonical URL
| Tag | Value | Emitted when |
|---|---|---|
<meta charset> | UTF-8 | always |
<meta name="generator"> | Leed Eleventy | always |
<meta name="title"> | the page title | always |
<meta name="keywords"> | the page’s keywords | only when the page has keywords |
<meta name="description"> | the page summary, falling back to the site description | always (one or the other) |
<link rel="canonical"> | canonicalUrl if a template sets one, otherwise the page’s own absolute URL | always except on /stubs/* pages, where it is suppressed entirely |
<meta property="published"> | publishedAt, ISO-8601 | always |
<link rel="icon"> | your favicon | only when a favicon is set |
The canonical URL is derived from where the page’s file sits, which means changing a slug changes the canonical URL. If the old address matters — and it does if anything links to it — add an alias so the old URL redirects rather than leaving it to 404.
Open Graph
| Property | Value source | Emitted when | Fallback |
|---|---|---|---|
og:locale | site locale | always | — |
og:site_name | site title | always | — |
og:site | site URL | always | — |
og:title | the page title | always | the company Social Title (ogCard.title) |
og:description | the page summary | when either exists | the company Social Description (ogCard.description) |
og:image | the page feature image | when an image resolves | ogCard.image from site data |
og:image:width | ogCard.imageWidth | whenever og:image is emitted | 800 |
og:image:height | ogCard.imageHeight | whenever og:image is emitted | 413 |
og:url | the page’s absolute URL | always | — |
og:type | article or website | always | — |
article:published_time | publishedAt, ISO-8601 | og:type: article only | — |
article:modified_time | modifiedAt, ISO-8601 | og:type: article only | — |
article:publisher | site title | og:type: article only | — |
article:author | one tag per visible author, carrying their full name | og:type: article only, and only when the page has authors | — |
article:tag | the page’s keywords | og:type: article only | — |
og:site is not a standard Open Graph property — og:site_name is, and is also emitted. It is harmless, and no consumer is known to read it; do not build anything on it.
Where the card image comes from
flowchart TD
P["A page's head is rendered"] --> FI{"Does the page have<br/>a feature image?"}
FI -- "yes" --> V["absoluteImageUrl(image, 'social')"]
FI -- "no" --> OG{"Does site data set<br/>ogCard.image?"}
OG -- "yes" --> V
V --> BOTH["og:image AND twitter:image"]
OG -- "no" --> T{"Which tag?"}
T -- "Open Graph" --> N1["no og:image at all"]
T -- "Twitter" --> L["the site logo,<br/>through the same transform"]
The two partials share the first two steps and part ways at the end: Open Graph stops, and emits no image tag at all, while the Twitter card falls back once more to your site logo. So a page with no feature image can unfurl with an image on X and without one on a platform that reads Open Graph.
The transform in the middle rewrites a CMS asset URL to its social Cloudflare Images variant, and makes the URL absolute if it is not already. That variant is why og:image:width and og:image:height default to 800 × 413 — those are the variant’s dimensions, and they describe Leed’s file, not yours. A site that supplies its own ogCard.image is not going through that variant at all, so it should state its own ogCard.imageWidth and ogCard.imageHeight alongside it or the two lines will describe someone else’s picture. The social variant is the same machinery behind image variants and responsive images.
Article versus website
og:type follows the Show In Feeds checkbox on the page type. With it on, the page is an article and gets the five article:* properties above; with it off, it is a website and gets none of them. It is the same switch that decides whether the page appears in your feeds and your AI files, and it cannot be split — a page cannot be an article while staying out of the feed.
Twitter / X cards
| Property | Value source | Emitted when | Fallback |
|---|---|---|---|
twitter:card | ogCard.cardType from site data | always | summary |
twitter:url | the page’s absolute URL | always | — |
twitter:site | the company Twitter ID | only when one is set | — |
twitter:image | feature image → ogCard.image → site logo | when any of the three resolves | — |
twitter:title | the page title | always | the company Social Title |
twitter:description | the page summary | only when the page has a summary | none — the tag is omitted |
twitter:creator | a visible author’s own Twitter ID | on feed-eligible pages, when the author has one | — |
twitter:label1 / twitter:data1 | the literal Est. reading time, and the page’s reading time in minutes | only when the page has a word count | — |
Two differences from Open Graph are easy to trip over. twitter:description has no fallback: where og:description drops back to the company Social Description, the Twitter tag is simply left out, so a page with no summary unfurls on X with a title and nothing else. And twitter:image has the extra logo fallback described above.
Reading time is computed from the page’s stored word count divided by your site’s reading speed — 200 words per minute unless you set a different Reading Speed, rounded down, with a floor of one minute.
- Open Graph
- JSON-LD
<meta property="og:locale" content="en-us">
<meta property="og:site_name" content="Leed" />
<meta property="og:site" content="https://leed.ai" />
<meta property="og:title" content="Publishing your first page" />
<meta property="og:description" content="A walk through the Deploy screen, from pending change to live URL." />
<meta property="og:image" content="https://leed.ai/cdn-cgi/imagedelivery/.../social" />
<meta property="og:image:width" content="800" />
<meta property="og:image:height" content="413" />
<meta property="og:url" content="https://leed.ai/blog/publishing-your-first-page/" />
<meta property="og:type" content="article" />
<meta property="article:published_time" content="2026-09-01T12:00:00.000Z" />
<meta property="article:modified_time" content="2026-09-04T09:15:00.000Z" />
<meta property="article:publisher" content="Leed" />
<meta property="article:author" content="Ada Lovelace" />
<meta property="article:tag" content="publishing,deployments,getting started" /><meta name="twitter:card" content="summary" />
<meta name="twitter:url" content="https://leed.ai/blog/publishing-your-first-page/" />
<meta name="twitter:site" content="Leed_AI" />
<meta name="twitter:image" content="https://leed.ai/cdn-cgi/imagedelivery/.../social" />
<meta name="twitter:title" content="Publishing your first page" />
<meta name="twitter:description" content="A walk through the Deploy screen, from pending change to live URL." />
<meta name="twitter:creator" content="ada_dev" />
<meta name="twitter:label1" content="Est. reading time" />
<meta name="twitter:data1" content="4 minutes" />{
"@context": "https://schema.org",
"@type": "BlogPosting",
"@id": "https://leed.ai/blog/publishing-your-first-page/",
"url": "https://leed.ai/blog/publishing-your-first-page/",
"headline": "Publishing your first page",
"datePublished": "2026-09-01T12:00:00.000Z",
"dateModified": "2026-09-04T09:15:00.000Z",
"publisher": {
"@type": "Organization",
"name": "Leed",
"logo": "https://leed.ai/static/images/logo-2026-07-11T15-12-07-709Z.png",
"description": "Leed brings together your marketing workflows, website, and customer journey into a seamless, accelerated experience informed by AI."
},
"image": "https://leed.ai/cdn-cgi/imagedelivery/.../original",
"keywords": "publishing,deployments,getting started",
"description": "A walk through the Deploy screen, from pending change to live URL.",
"wordCount": 980,
"author": [{ "@type": "Person", "givenName": "Ada", "familyName": "Lovelace" }]
}Which fields you control, and where
| What a reader sees on the card | Set in | Field | Documented at |
|---|---|---|---|
| The headline | the page editor | Title | Page Settings |
| The blurb under it | the page editor | Summary | Page Settings |
| The picture | the page editor | Feature Image | Uploading Images |
| The site name beside it | Settings → General | Site Title | Site Identity and Branding |
The @handle attribution | Settings → General | Twitter ID | Site Identity and Branding |
| The per-author attribution | the author’s own profile | Twitter ID on a visible profile | Your Profile |
Whether the card says article | Settings → Page Types | Show In Feeds | Configuring a Page Type |
| The blurb on a page with no summary | Settings → General | Social Description | Site Identity and Branding |
| The card shape and the fallback image | repository site data | ogCard.cardType, ogCard.image | Global Site Data |
The settings-level Social Title and Social Description are worth one more sentence, because their labels oversell them: the templates prefer the page’s own title and summary and use these only when the page has neither. On a real published page — which always has a title — Social Title never applies. Fill in the page’s own summary rather than reaching for the site-wide default.
An author only reaches article:author or twitter:creator if their profile is marked visible; an invisible profile is skipped silently in both.
Structured data
Where it does apply, a page-type slug of blog produces BlogPosting for an article and Blog for a paginated list page.
| Field | Source | Always present |
|---|---|---|
@context | the literal https://schema.org | yes |
@type | BlogPosting, or Blog on a paginated list page | yes |
@id | the page’s absolute URL | yes |
url | the page’s absolute URL | yes |
headline | the page title | yes |
datePublished | publishedAt | yes |
dateModified | modifiedAt | yes |
publisher.name | site title | yes |
publisher.logo | site logo, made absolute | yes |
publisher.description | site description | yes |
image | the feature image | only when the page has one |
keywords | the page’s keywords | only when the page has some |
description | the page summary | only when the page has one |
wordCount | the page’s stored word count | only when it is non-zero |
author[] | one Person per visible author, as givenName and familyName | only when at least one author is visible |
Note that authors are emitted as separate given and family names, with no combined name — a consumer expecting author.name will find nothing there.
If you need structured data on a page type Leed does not cover, write it yourself. A <script type="application/ld+json"> block in your own layout gives you per-type control, and one in header-includes.hbs reaches every page of the site including the documentation — see How Templates Work and Customizing the <head>. The built-in block comes from the JSON-LD helper, which is one of the content helpers and needs triple braces because it emits raw HTML.
Checking a card before you share it
Publish the page first, then paste its live URL into the platform’s own card inspector — every major platform runs one, and each caches results independently, so a card that looks stale in one place may be current in another.
What the common failures mean:
- No image. The page has no feature image and your site data sets no
ogCard.image. On an Open Graph consumer there is no image tag at all; on X you will see your site logo instead. - An empty or wrong description. The page has no summary. Open Graph fell back to your Social Description; the Twitter card omitted the tag entirely.
- The card says
websitewhere you expectedarticle. The page type’s Show In Feeds is off. - The card is a small square where you wanted a wide banner.
twitter:carddefaults tosummary; a wide preview needsogCard.cardTypeset tosummary_large_imagein your site data.
One reminder that catches people out: a preview site is noindex from top to bottom, and crawlers are told to stay away. Test the card against the live URL, not the preview one — Preview Site vs Live Site covers the other differences.