SEO and meta.
You do not have to hand-write the meta tags for a page. After your template renders, the platform reads the site and the page and injects the head metadata: the title and description, Open Graph and Twitter Card tags, and JSON-LD structured data. This page covers what it injects and how to influence it.
The injection happens once, at the end of the render, and only when the output looks like a real HTML document. The metadata is inserted just before , and the platform does it only when the rendered body has both a and a . A CSS, JavaScript or JSON template, which has no , is left untouched.
What the platform injects
In one pass, the platform adds to the head:
- the page title, description and keywords, as
tags; - Open Graph tags (
og:title,og:image,og:url,og:descriptionand more) for link previews; - a Twitter summary card (
twitter:title,twitter:image,twitter:description); - app and icon tags (theme colour, the manifest link and the favicon matrix), covered on the PWA and push page;
- JSON-LD structured data.
The URLs in those tags (og:url, twitter:url, and the JSON-LD url) are built from your site's canonical host, so they stay absolute and correct even when a site answers on more than one domain.
The JSON-LD always includes a WebSite block and an Organization block. A blog post (type 2) also gets an Article block, and a product (type 3) a Product block. The post type decides which one is added.
Influencing the output
The lightest lever is a post's own meta. A post carries a meta.title and a meta.description, and in Standard mode the platform uses them for the page's title and description, and for the Open Graph and Twitter equivalents. Compat ignores them for posts, so on a compat site they have no effect. On a standard site, setting a post's meta title and description is the simplest way to tune how it appears in search results and link previews.
The post's meta is also in the render context as page.meta, if a template wants to read it directly:
name="description" content="{{ page.meta.description | default: page.teaser | strip_tags }}">Full control with meta_resource
For complete control of the head metadata, use the {% meta_resource name %} tag. It renders one of your templates and injects the result into the head, and it produces no inline output where you call it. The important part: once a page uses {% meta_resource %}, the platform injects only your fragment and skips its own automatic tags. You own the head, so put everything the page needs into that template.
{% meta_resource custom-seo %}The body of the custom-seo template is rendered with the current context, so it can read the page and the site. Because the automatic tags are skipped, this is also where you would add a real canonical link if you want one:
name="description" content="{{ page.meta.description | default: page.teaser | strip_tags }}">
property="og:title" content="{{ page.title }}">
property="og:image" content="{{ page.primary_media.url | default: site.logo }}">
rel="canonical" href="{{ env.root_url }}{{ page.permalink }}">The tag is covered alongside the other custom tags on the Tags page.
Suppressing injection
To stop the platform injecting anything into the head, put the marker <%= no_render %> in the template body. When it is present the automatic head metadata is suppressed entirely, and the marker itself is stripped from the output. Placed at the very start of a body it also turns off Liquid rendering for that template, so reach for it when a template already emits a complete, final document that should be served as is.
Mode differences
A handful of the SEO details differ by render mode. In Standard mode the platform uses the correct og:image fallback for a post with no cover image (the site logo), takes the Twitter description from the post teaser, and writes JSON-LD with the right dates, keys and author, plus the site's own mobile number as the organisation's contact number. Compat keeps the older behaviour for byte-for-byte parity with the previous engine. The Render modes page has the full table.