PBS 186 of X: Web Metadata — the Search and OpenGraph HTML Headers
Before we switch our focus back to building statically generated websites with Jekyll, we need to take one more short diversion. Before we took a break from the topic, we described the mechanisms for developing the look of a Jekyll website, and after our break, we’ll shift our focus to how Jekyll allows you to structure your content. The reason you structure your content is to give the information on your site a sensible shape so people can find and understand it. While structuring your content, you’ll also need to make it interact well with the rest of the internet. In other words, you need to help search engines understand your sites’s content, and you need to help social media services embed rich previews of your content into posts.
Before we look at how we do this in Jekyll, we need to step back and understand how it’s done in general, which is what this instalment is all about.
There are two important types of so-called metadata websites need to embed in their headers to support search engines and social media sites, the venerable old so-called SEO Headers, and the much newer OpenGraph protocol.
Matching Podcast Episode
You can also Download the MP3
Read an unedited, auto-generated transcript with chapter marks: PBS_2026_06_27
The Big Picture — It’s All About Metadata
Whether we’re thinking about the older headers for search engines or the newer header for social media, we’re dealing with the same fundamental concept — embedding information about a page’s content into the page’s header.
This is data about data, or, to use the jargon, metadata.
The official HTML tag for adding metadata to a page is the <meta name="" content=""> tag, which must appear within the <head> tag. The official specification lists a number of standard metadata names and defines their meanings, and then leaves it up to communities of internet users to define conventions or standards for additional names.
The official standard names are very much search-engine focused, so they’re still important, and you should still add them to your content management system themes, but their importance is waning in our modern social media and AI-led world.
In fact, they were once so important that an entire industry developed around optimising their use — Search Engine Optimisation, or SEO!
For convenience, we’ll do what most people do, and refer to the official headers as SEO Headers in this instalment.
With the rise of social media, this formal search-engine-focused specification just wasn’t flexible enough, so Facebook set about designing a more appropriate standard. They could have chosen to build their standard on top of the official standard by defining additional values for the <meta name=""> attribute, but they didn’t do that; they chose a very different route instead.
Facebook chose to intentionally step outside the formal HTML specification and to design their own custom extension to HTML itself. They added a whole new attribute to the <meta> tag — <meta property="">. This is, intentionally, invalid HTML!
The HTML standard defines the list of all allowed attributes as:
- Any of the global attributes. This is quite a short list, and while it does include the
itempropattribute, which is mentioned later in the<meta>tag spec, it doesn’t include apropertyattribute! name— for defining page metadatahttp-equiv— for defining so-called Pragma directives (client-side versions of HTTP response headers)content— for the relevant value for the tagcharset— for declaring a page’s character sets (e.g.,<meta charset="utf-8">)media— for specifying some kind of relevant media content
So, the property attribute really is officially invalid!
But wait, there’s more. The HTML standard also states that:
“Exactly one of the
name,http-equiv,charset, anditempropattributes must be specified.”
So, all OpenGraph headers really are invalid HTML!
Why?
I’d forgive you for assuming arrogance on Facebook’s part (I may have done the same), but that’s not the case here. It’s actually something quite different — a strangely ingenious approach!
The whole point of OpenGraph metadata is to allow a page to give information to social media services without affecting the look of the page in any way.
By intentionally using invalid HTML, Facebook ensure all browsers completely ignore the OpenGraph headers!
The Official ‘SEO’ Headers
Taking things chronologically, let’s start with the so-called SEO Headers.
There aren’t many of these headers in the entire official specification, and not all of them are relevant to the kinds of websites we can build with content management systems like WordPress and Jekyll. Here’s a list of the most relevant ones:
application-name— this is relevant for web apps, and it should be a single name for every page that makes up the app. For example, Allison could add the following to her Time Adder app:<meta name="application-name" content="Time Adder">.author— this is relevant to regular web pages that publish content, like articles and blog posts, and simply lists the author of the page’s content. This might be the same on every page on your site, or it might not.description— a short summary of the page’s content. For blog posts, this would usually be the post’s preview snippet.generator— the software that built the page. If you want to let the world know you’re using Jekyll, you could have your theme include the header<meta name="generator" content="Jekyll">.keywords— a comma-separated list of keywords.
The official spec envisaged community organisations defining additional conventions or 3rd-party standards, but that never quite took off. There is one notable 3rd-party standard (WHATWG) that adds two additional commonly used SEO headers:
creator— the organisation or institution responsible for the page. For example, the Let’s Talk site publishes the header<meta name="creator" content="Bartificer Creations Ltd.">.publisher— this is relevant for aggregator sites like scientific journals that publish articles from many authors at many academic institutions.
If you think about the kind of rich link previews we see on social media, only one of these headers is potentially useful: the description. But even then, you may well want to give search engines a much more detailed overview than you would want to show as a one-sentence attention grabber in a social media preview!
The OpenGraph Protocol
The reason the OpenGraph protocol exists is because plain text links get lost in social media posts dominated by gifs, emoji, images, and video clips. For links to get noticed, they need to catch the eye, hence the invention of those little link preview cards that get automatically generated when include URLs in social media apps.
If the problem to be solved is making engaging link previews, then the existing SEO metadata headers are of very little help. Something more was needed, and everyone’s somethings eventually coalesced around on OpenGraph, turning it into a defacto-standard.
Firstly, because OpenGraph only tries to solve a single clearly defined problem, it’s not a complicated specification. The official documentation is really quite short, and pleasingly readable!
Because OpenGraph makes use of HTML <meta> tags, it’s your content management system’s responsibility to insert the headers into your pages. When you use WordPress, it’s the joint responsibility of your theme and/or some additional plugins, and when you use Jekyll, you need to include the tags in your layouts.
The basic structure of OpenGraph tags is simply:
<meta property="PREFIX:SOME_NAME" content="SOME VALUE">
For example:
<meta property="og:title" content="About Programming by Stealth" />
That’s the actual OpenGraph title tag from this series’ front page.
I chose this example very intentionally, because it highlights something some people find confusing — the tags are intentionally duplicative.
We know that HTML pages define window/tab titles with the <title> tag, so why does OpenGraph not just use that information rather than adding a <meta property="og:title"> tag? Simple — it’s often fine to use the same text for both browser window titles and social media info card titles, but not always. This decouples those two things. How my page is titled in the browser, and how my page’s social media card is titled on social media info cards, can be the same or different, as suits me!
In reality, content management systems often use the same piece of information the user typed once in many places. Page titles are a great example of that. When you write a post in WordPress, you have two primary text boxes — one where you enter the post’s title, and one where you enter the post’s content. You typed that title once, but it can appear many times in the post’s generated HTML:
- It will appear in the
<title>tag, perhaps with an automatically added postfix or prefix. - It will appear in an
<h1>tag above the content, almost certainly without any kind of postfix or prefix this time. - It will appear in
<meta property="og:title">tag, perhaps with a prefix or postfix added, but if not, not necessarily the same one as used in the<title>tag.
I use the same approach when developing my Jekyll layouts — page.title is repeatedly referenced in my layouts.
The OpenGraph Tags
OpenGraph is designed for any kind of content that could be linked to, now, or in the future. We’re going to focus our attention on just the tags appropriate to traditional web pages. Or, to express that in OpenGraph syntax, the tags that are appropriate for:
<meta property="og:type" content="website">
After that, the most important tags you really should include every single time are:
og:title— the title for your post/page within generated information cards.og:description— a short and pithy description of your post/page. While you might use the same value for the SEO<meta name="description">and<meta property="og:description">tags, you may well choose to give search engines longer, more detailed descriptions, and social media sites much shorter one-sentence descriptions.og:image— the URL to an image to represent your page in the info card. Content management systems like WordPress often default to using the first image included in a post’s content, but they generally allow you to specify a custom image using a field named something like ‘Featured Image’ (the name WordPress uses in it’s UI).og:url— the canonical URL for the page. It’s very common for the same content to be accessible via multiple URLs, for example, every blog post onbartb.iecan be accessed over bothhttpandhttpson the domainsbartb.ie,www.bartb.ie,bartbusschots.ie&www.bartbusschots.ie, so every post has six URLs! I want social media to send people tohttps://bartb.ie/..., so when I migrate the site to Jekyll, that’s the base URL I’ll be using in my<meta property="og:url">tags.og:locale— the content’s language inlanguage_TERRITORYformat, where the languages are lower-cased two-letter codes defined in ISO 639-1, and the territory codes are upper-cased two-letter codes defined in ISO 3166-1. That sounds complicated, but since I post in British English, I use<meta property="og:locale" content="en_GB">, while Allison posts in American English, so the correct tag for her is<meta property="og:locale" content="en_US">.og:site_name— the name of the site the content belongs to, e.g.,<meta property="og:site_name" content="Let's Talk Podcasts">.
For websites, the appropriate generic content type is og:type, so a good starting point is:
<meta property="og:type" content="website">
A Note on og:type
However, if you’re building a blog, there are two additional types you might consider:
- On blog posts, a more appropriate
og:typeis<meta property="og:type" content="article">. (Used on this very page!) - On author profiles, a more appropriate
og:typeis<meta property="og:type" content="profile">. When you use this type, you should also include a few additional type-specific OpenGraph headers:profile:first_name&profile:last_nameto specify the person’s name.- You might also consider
profile:usernameif that’s appropriate for your site.
A Note for Podcasters
If some of your blog posts are actually show notes for podcast episodes, you might consider adding an og:audio tag to those posts, linking to the episode’s audio file. For example, episode 153 of Let’s Talk Photo advertises:
<meta property="og:audio" content="https://media.lets-talk.ie/lta/lta153.mp3">
At the moment, I’m not seeing any of the popular social media sites making use of this tag, but by adding it, you’re making your theme/layout just that little bit more future-proof.
A Real-World Example — The Let’s Talk Website
In January, I migrated the Let’s Talk website from WordPress to Jekyll using a theme I built from the ground up using what we have been learning in our Jekyll mini-series. With the new colour scheme, new layouts, and updated typography, there were plenty of very visible changes, but under the hood, there was another big change — I focused heavily on getting my metadata right. I put a lot of effort into learning about current best practices, and then built support for what I think are the perfect <meta> headers into my custom theme.
The Jekyll/Liquid code might be of some interest to others, so I will include it at the end of this section, but the key points I want to share are my choices in terms of the generated headers.
The Let’s Talk Metadata Headers
The optimum headers depend on the page’s content, so rather than having one set of headers for all the pages on the site, I customised the headers on a layout-by-layout basis. The three most notable layouts are those for generic pages, like the front page, the one for episode notes, and the one for contributor profiles.
Let’s start with my default set of headers, used on all pages that don’t fall into another more specific category.
Here are the SEO headers from the front page:
<meta name="author" content="Bart Busschots">
<meta name="creator" content="Bartificer Creations Ltd.">
<meta name="description" content="Let's Talk Podcasts, from Bartificer Creations.">
<meta name="keywords" content="Apple, news, iOS, macOS, iPhone, iPad, AppleWatch, Mac, photography, art, craft, podcast">
Other than the fact that I am using both the <meta name="author"> and <meta name="creator"> headers, there’s nothing particularly noteworthy here. In fact, the paucity of the metadata I can express illustrates the problem OpenGraph was designed to solve!
So, here are the OpenGraph headers from the front page:
<meta property="og:type" content="website">
<meta property="og:url" content="https://www.lets-talk.ie/">
<meta property="og:image" content="https://www.lets-talk.ie/assets/graphics/LetsTalk-Podcasts-OpenGraph.png">
<meta property="og:site_name" content="Let's Talk Podcasts">
<meta property="og:locale" content="en_GB">
<meta property="og:description" content="Let's Talk Podcasts, from Bartificer Creations.">
<meta property="og:title" content="Let's Talk Podcasts" />
Notice I’m specifying the generic og:type website on this page. That won’t be true on the special purposes pages. Also, notice I’m adding the canonical URL and providing a URL to a nice PNG icon for the site. I’ve also given the site, as a whole, a name with og:site_name, and I’ve explicitly set my locale to British English with <meta property="og:locale" content="en_GB">.
Finally, I want to draw your attention to the fact that some of these headers are duplicative:
- The
og:descriptionis identical to the description in the<meta name="description">SEO header. - While it’s not shown in the snippet, the content of the
og:titleis identical to the window/tab title used in the<title>tag.
My episode show notes are content-heavy, so they can be considered articles; hence, their metadata is a little different.
I’ll use the headers from the show notes for Let’s Talk Apple 153.
Again, the SEO headers are not all that expressive:
<meta name="author" content="Bart Busschots">
<meta name="creator" content="Bartificer Creations Ltd.">
<meta name="description" content="Unsurprisingly, the month leading up to WWDC has been relatively quiet, but there's still plenty to discuss. The show starts with the usual few follow-up, some legal and regulatory updates, some HR & acquisition news, and some updates to Apple services & original content. There are just three main stories — an unexpected turn in the Apple -v- Epic case, a reported deal between Apple & Intel, and Apple's annual accessibility announcements. The show finishes with a rundown of some smaller Apple-related stories that made the news in May.">
<meta name="keywords" content="Apple, news, iOS, macOS, iPhone, iPad, AppleWatch, Mac, podcast">
Other than the fact that description and keywords have changed to reflect the content, there’s nothing noteworthy here.
Moving on to the OpenGraph headers, things get a little more interesting:
<meta property="og:type" content="article">
<meta property="og:url" content="https://www.lets-talk.ie/lta153">
<meta property="og:image" content="https://www.lets-talk.ie/assets/graphics/LetsTalk-Apple-OpenGraph.png">
<meta property="og:site_name" content="Let's Talk Podcasts">
<meta property="og:locale" content="en_GB">
<meta property="og:description" content="Unsurprisingly, the month leading up to WWDC has been relatively quiet, but there's still plenty to discuss. The show starts with the usual few follow-up, some legal and regulatory updates, some HR & acquisition news, and some updates to Apple services & original content. There are just three main stories — an unexpected turn in the Apple -v- Epic case, a reported deal between Apple & Intel, and Apple's annual accessibility announcements. The show finishes with a rundown of some smaller Apple-related stories that made the news in May.">
<meta property="og:audio" content="https://media.lets-talk.ie/lta/lta153.mp3">
<meta property="og:title" content="Let's Talk Apple episode 153 — May 2026">
I want to draw your attention to two obvious changes:
- The
og:typeis nowarticle. - There is now an
<meta property="og:audio">tag linking to the episode’s audio file.
But there are also two more subtle features of note.
Firstly, both the <meta name="description"> and og:description are the same, and what’s more, that same text appears in the body of the page as the episode’s summary. In fact, if you check the RSS feed for the podcast, you’ll see the same text reused a fourth time as the summary.
And secondly, this page makes use of the fact that it is possible to use different titles in different contexts.
- The window/tab title in the
<title>tag is ‘LTA 153: May 2026’ — nice and compact for those of us who always have too many tabs 😉 - The visible title on the page (from the regular HTML tags in the page
<body>) is ‘LTA 153 Show Notes’, which is a little more expressive - The
og:titleis ‘Let’s Talk Apple episode 153 — May 2026’ is even more expressive, because the social media share cards need to stand entirely alone, being outside of the context of the website.
Finally, let’s look at the metadata for Allison’s contributor profile.
Again, there is very little of note in the SEO headers:
<meta name="author" content="Bart Busschots">
<meta name="description" content="The contributor profile for Allison Sheridan on the Let's Talk podcasts.">
<meta name="creator" content="Bartificer Creations Ltd.">
<meta name="keywords" content="podcast">
Because OpenGraph explicitly supports online profiles, the OpenGraph headers are, yet again, more expressive:
<meta property="og:type" content="profile">
<meta property="og:url" content="https://www.lets-talk.ie/contributor/allison_sheridan">
<meta property="og:image" content="https://www.lets-talk.ie/assets/graphics/LetsTalk-Podcasts-OpenGraph.png">
<meta property="og:description" content="The contributor profile for Allison Sheridan on the Let's Talk podcasts.">
<meta property="og:site_name" content="Let's Talk Podcasts">
<meta property="og:locale" content="en_GB">
<meta property="profile:first_name" content="Allison">
<meta property="profile:last_name" content="Sheridan">
<meta property="og:title" content="Allison Sheridan (Let's Talk Podcasts)" >
The most important change is that the og:type is now profile, and there are profile:first_name and profile:last_name headers.
And yet again, there are multiple different titles for different contexts:
- The Window/tab title is
Allison Sheridan | Let's Talk Podcasts, the pipe character is very common in tab headings, and, nice and space efficient, so it felt the most appropriate. - The visible title on the page is simply ‘Allison Sheridan’ — the page’s context is doing the rest of the work.
- The
og:titleis again more expressive, adding some needed context: ‘Allison Sheridan (Let’s Talk Podcasts)’
The Underlying Code
Before showing the relevant code, I want to take a moment to explain my aims when developing it:
- Collect as much of the Jekyll/Liquid code into a single file as possible to help keep it together for easy maintenance over time
- Make the metadata as flexible as possible by favouring site configuration settings and YAML front matter whenever possible.
- Avoid needless duplication in front matter.
Taking this approach led to collecting almost all the code into a single include file used by all my layouts (./docs/_includes/html_head_common.html), and supporting front matter definitions, includes, layouts, and pages.
I didn’t quite succeed in pulling all the Jekyll/Liquid code into a single file, but I came very close. There are just two exceptions:
- Each of my top-level layouts is responsible for adding the
og:titlemetadata tag into the<head>tag. - I had to add support for falling back to an
include_relativedirective to load SEO and OpenGraph descriptions for contributor profiles fromdocs/_contributors/_description.txt.
All in all, not too bad!
The following snippet from ./docs/_includes/html_head_common.html captures almost the entire SEO header implementation:
{%- comment %}
Define the traditional metadata/SEO headers:
--------------------------------------------
Spec at https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name
REQUIRED additional headers: ADD TO EVERY LAYOUT!
- NONE YET
Customisable headers:
- author: OPTIONAL - Only included if one of the following is defined (tried in the given order):
1. include.meta_author
2. page.meta.author
3. site.meta.author
- creator: OPTIONAL - the organisastion responsible for the content. Only included if one of the following is defined (tried in the given order):
1. include.meta_creator
2. page.meta.creator
3. site.meta.creator
- description: OPTIONAL - Only included if one of the following is defined (tried in the given order):
1. include.meta_description
2. page.meta.description
3. page.description
4. a snippet in a file named ./_description.txt (relative to the markdown file), but only if layout.enable_description_snippet is true (default false)
- keywords: OPTIONAL — Only included if at least one of the following is defined — if multiple are defined, the superset of keywords will be used:
1. include.meta_keywords
2. layout.meta.keywords
2. page.meta.keywords
3. site.meta.keywords
{%- endcomment %}
{%- capture meta_author %}{{ include.meta_author | default: page.meta.author | default: site.meta.author | default: '' | strip | normalize_whitespace }}{% endcapture %}
{%- if meta_author != '' %}
<meta name="author" content="{{ meta_author }}">
{%- endif %}
{%- capture meta_creator %}{{ include.meta_creator | default: page.meta.creator | default: site.meta.creator | default: '' | strip | normalize_whitespace }}{% endcapture %}
{%- if meta_creator != '' %}
<meta name="creator" content="{{ meta_creator }}">
{%- endif %}
{%- capture meta_description_include %}{% if layout.enable_description_snippet == true %}{% include_relative _description.txt %}{% endif %}{% endcapture %}
{%- capture meta_description %}{{ include.meta_description | default: page.meta.description | default: page.description | default: meta_description_include | default: '' | strip | normalize_whitespace }}{% endcapture %}
{%- if meta_description != '' %}
<meta name="description" content="{{ meta_description }}">
{%- endif %}
{%- comment %}For keywords, we want to merge the various sources if multiple are defined, so we need to do a bit more work to ensure we end up with a unique set of keywords{% endcomment %}
{%- assign _empty_array = '' | split: ',' %} {% comment %} create an empty array to use as a default value for the various sources {% endcomment %}
{%- assign include_keywords = include.meta_keywords | default: _empty_array %}
{%- assign layout_keywords = layout.meta.keywords | default: _empty_array %}
{%- assign page_keywords = page.meta.keywords | default: _empty_array %}
{%- assign site_keywords = site.meta.keywords | default: _empty_array %}
{%- assign keywords = include_keywords | concat: page_keywords | concat: layout_keywords | concat: site_keywords | uniq %}
{%- if keywords %}
<meta name="keywords" content="{{ keywords | join: ', ' }}">
{%- endif %}
Notice that the vast majority of the code is a very detailed Liquid comment describing all the possible sources for each <meta> tag, and the order in which they will be tried.
For the most part the code relies on a combination of {% capture %} blocks for assembling variables, and chained default: filters for applying the documented hierarchy of possible sources, and if blocks to avoid outputting optional <meta> tags without defined values.
One notable exception is the <meta name="keywords"> tag. The content for this tag is built by concatenating arrays from many possible sources, rather than having values from multiple possible sources overriding each other in a defined order.
There are a few little tricks going on here — firstly, the vision of Liquid that’s used by GitHub Pages doesn’t support directly declaring new arrays in Liquid. Sure, it can make use of arrays defined in YAML front matter, and some if it’s functions return arrays, but you can’t just create a new variable holding an array of your choice. The common workaround for this is to create an empty array by splitting an empty string using the Liquid split: filter, which is what is going on in this line:
{%- assign _empty_array = '' | split: ',' %}
Having an empty array makes it possible to build separate, possibly empty, arrays for each supported keyword source:
{%- assign include_keywords = include.meta_keywords | default: _empty_array %}
…
{%- assign site_keywords = site.meta.keywords | default: _empty_array %}
Once we have our four arrays for the keywords coming from the site, page, layout, and include parameters, they get merged into a single array, joined into a comma-separated list, and if the list is not empty, rendered to the page:
{%- assign keywords = include_keywords | concat: page_keywords | concat: layout_keywords | concat: site_keywords | uniq %}
{%- if keywords %}
<meta name="keywords" content="{{ keywords | join: ', ' }}">
{%- endif %}
You can see this merging of keywords from multiple sources in action on the podcast episode pages. For example, these are the keywords for Let’s Talk Photo episode 153:
<meta name="keywords" content="photography, art, craft, podcast">
The podcast keywords are coming from the site config file (_config.yml):
# Site Settings
…
meta:
…
keywords: # universal keywords for the site, does not override page-specfific keywords, but merged with them if defined
- podcast
…
The other three keywords come from the front matter in the page for the Let’s Talk Photo podcast as a whole (./docs/_podcasts/ltp.md):
---
…
meta:
keywords:
- photography
- art
- craft
…
---
Since the page for the show itself is ./docs/_ltp/ltp153.md, how does the front matter from a completely different page get into the mix? The answer is that the Podcast Episode layout (docs/_layouts/podcast_episode.html) fetches those keywords and passes them directly to the html_head_common layout using its meta_keywords parameter:
{%- assign podcast_slug = page.collection -%}
{%- assign podcast = site.podcasts | where: "slug", podcast_slug | first -%}
…
<!DOCTYPE HTML>
<html>
<head>
{%- comment %}Include the snippet with the standard content for the head tag{% endcomment %}
{%- include html_head_common.html og_type="article" og_image=podcast.opengraph.image og_description=page.blurb meta_description=page.blurb meta_keywords=podcast.meta.keywords %}
…
</head>
…
</html>
The following snippet from ./docs/_includes/html_head_common.html captures almost the entire OpenGraph header implementation:
{%- comment %}
Define the Common Open Graph Headers:
-------------------------------------
Spec at https://ogp.me
REQUIRED additional headers: ADD TO EVERY LAYOUT!
- og:title
Customisable headers:
- og:type: A Required header, so defaults to "website", can be
overridden by the following (in order):
1. include.og_type
2. layout.opengraph.type
3. page.opengraph.type
- og:image: A Required header, so defaults to the podcast network's
banner, can be overridden by the following (in order):
1. include.og_image
2. page.opengraph.image
Note - images sized according to this advice: https://www.ogimage.gallery/libary/the-ultimate-guide-to-og-image-dimensions-2024-update
- og:description: OPTIONAL - only included if one of the following is defined (tried in the given order):
1. include.og_description
2. page.opengraph.description
3. page.description
4. a snippet in a file named ./_description.txt (relative to the markdown file), but only if layout.enable_description_snippet is true (default false)
- profile:first_name: OPTIONAL - only included if og:type is "profile", and one of the following is defined (tried in the given order):
1. include.og_profile_first_name
2. page.opengraph.profile_first_name
3. page.first_name
- profile:last_name: OPTIONAL - only included if og:type is "profile", and one of the following is defined (tried in the given order):
1. include.og_profile_last_name
2. page.opengraph.profile_last_name
3. page.surname
Additional Optional headers: add to appropriate layouts
- og:audio: The URL to an audio file — vital for the podcast episode layout!
{%- endcomment %}
{%- comment %}REQUIRED OpenGraph headers{% endcomment %}
{%- capture og_type %}{{ include.og_type | default: layout.opengraph.type | default: page.opengraph.type | default: 'website' }}{% endcapture %}
<meta property="og:type" content="{{ og_type }}">
<meta property="og:url" content="{{ page.url | absolute_url }}">
<meta property="og:image" content="{{ include.og_image | default: page.opengraph.image | default: '/assets/graphics/LetsTalk-Podcasts-OpenGraph.png' | absolute_url }}">
{%- comment %}OPTIONAL OpenGraph headers{% endcomment %}
<meta property="og:site_name" content="{{ site.title }}">
<meta property="og:locale" content="en_GB">
{%- capture og_description_include %}{% if layout.enable_description_snippet == true %}{% include_relative _description.txt %}{% endif %}{% endcapture %}
{%- capture og_description %}{{ include.og_description | default: page.opengraph.description | default: page.description | default: og_description_include | default: '' | strip | normalize_whitespace }}{% endcapture %}
{%- if og_description != '' %}
<meta property="og:description" content="{{ og_description }}">
{%- endif %}
{%- capture og_profile_first_name %}{{ include.og_profile_first_name | default: page.opengraph.profile_first_name | default: page.first_name | default: '' | strip | normalize_whitespace }}{% endcapture %}
{%- if og_type == 'profile' and og_profile_first_name != '' %}
<meta property="profile:first_name" content="{{ og_profile_first_name }}">
{%- endif %}
{%- capture og_profile_last_name %}{{ include.og_profile_last_name | default: page.opengraph.profile_last_name | default: page.surname | default: '' | strip | normalize_whitespace }}{% endcapture %}
{%- if og_profile_last_name != '' %}
<meta property="profile:last_name" content="{{ og_profile_last_name }}">
{%- endif %}
The approach is basically the same, though there’s more code because there are more headers!
What About AI Web Crawlers?
As I write this post in June 2026, we’re in the early stages of what we expect to be a major AI revolution. Search engines are likely to decline ever more quickly, and AI agents are likely to rise. For content creators, there’s obvious value in helping AI agents find and properly interpret your content, so some sort of AI-specific alternative to SEO and OpenGraph tags will undoubtedly emerge soon.
For now, AI crawlers are consuming the existing SEO & OpenGraph headers, but there are also some emerging possible future standards to watch:
- The JSON-LD specification that was also developed for search engines is being consumed by some AI crawlers.
- Rather than using HTML metadata, site owners may be required to add separate Model Context Protocol (MCP) URLs to their sites. (I hope not!)
- There is a proposal to standardise additional
<meta name="">tags for AI crawlers, e.g.,<meta name="ai:summary">& even<meta name="ai:cite">for defining a preferred linking style in AI responses! These proposals are still in their very earliest stages; there aren’t even draft IETF or W3C specifications other link to yet!
Final Thoughts
While visitors to your sites never see your metadata, at least not directly, without it, you’ll have much fewer visitors to your site! Your pages won’t show up well in search results, and links to your pages won’t look inviting in social media posts, so, inevitably, your site is less attractive.
Now that we know how to embed search engine metadata, and how to enrich social media previews with OpenGraph, we can use that knowledge to fine-tune any content management system, but of course we’ll be implementing them in the Jekyll static site generator.
Reminder — PBS 181 Challenge
We’ll finally blow the dust off the challenge set many months ago at the end of the instalment 181 next time. That makes now the perfect time for the challenge to help get you back into the Jekyll Mindset.