FeaturesTemplates & Design

One call writes the whole head tag

Title, description, canonical, robots, share cards, icons and structured data for every page and every object, resolved when the page renders. You add one line to your layout; the values come from the record, from its collection, and from one site-wide record.

Standard Included in Standard and Pro

twig
{# layouts/base.twig — covers every page on the site #}
<head>
	<meta charset="utf-8">
	{% block seo %}{{ cms.seo.head(page|default(null)) }}{% endblock %}
</head>

{# pages/blog.twig — describe the post, not the page record that routes to it #}
{% set post = cms.collection.object('blog', params.id) %}
{% block seo %}{{ cms.seo.head(post, {collection: 'blog'}) }}{% endblock %}

{# Hand it your own schema.org nodes and they join the same @graph #}
{{ cms.seo.head(post, {collection: 'blog', jsonld: [faq]}) }}
The layout call handles every page. A template rendering a collection object overrides the block and names the collection — without that name Total CMS cannot read the collection's mapping, resolve the object's URL or build its image. No |raw is needed anywhere: head() escapes every value it prints.

Total CMS writes the head of the document for you. One call in your layout — cms.seo.head(page) — emits the title, meta description, canonical link, robots directives, the site's icons, Open Graph and Twitter card tags, your own meta tags and a single JSON-LD @graph of Organization, WebSite, WebPage, BreadcrumbList and Article nodes. There is no build step: values resolve when the page renders. Delete the title and description tags your layout carries, or the page ships two of each.

Values come from three places and the first non-empty one wins. The SEO card on the record holds the per-record overrides: title, social title, description, social image, canonical URL, no index, no follow, structured data type. The collection's field mapping names the properties that already hold the headline, the summary and the picture, so you do not copy them onto a card for every object. The Site SEO record supplies the rest — site name, base URL, title template, default description, default share image, organization details.

A blog needs no configuration: any collection on the blog, blog-legacy or feed schema is mapped to Article already, with title, summary and image pointed at. A record with No Index on drops out of the sitemap it would otherwise appear in, and loses its canonical tag too, because asking a crawler not to index a page while naming the address to index is two instructions at once. The card's Title accepts ${property} placeholders, so one value composes a title out of the record — ${name} — ${city}, with ${site} for the site name. A title whose placeholders all come back empty falls through rather than emitting the punctuation with nothing around it.

The Site SEO record also carries the pieces you would otherwise bolt on. Upload one square PNG as the Icon and every size a browser, phone or search result asks for is cut from it by ImageWorks, with /favicon.ico served from the same image. Paste the verification tags Google, Bing and Pinterest hand you into Meta Tags, and they print as written on every page. A generator tag names Total CMS, never its version, and switches off for a white-labeled site. With IndexNow on, every publish, edit and delete of a URL the sitemap lists is queued and sent to Bing, Yandex, Seznam and Naver, decided by the sitemap's own rules. Google does not take part in IndexNow, so the sitemap still matters.

Site SEO is a reserved single-object collection rather than a settings panel, so the default share image, the icon and the organization logo are real uploads, and the record reads in Twig like any object. You cannot save over the reserved schema, but on Pro a custom schema with inheritFrom set to seo-site puts site-wide fields of your own — a tagline, a phone number — on the same record.

This page is the worked case. totalcms.co runs on Total CMS: builder/layouts/base.twig carries the single call, and the features template overrides that block to pass the feature record and its collection, handing its own FAQPage node to the same call. This page ships one JSON-LD script with the core nodes and the FAQ in one graph.

At a glance

What you get

One line in the layout
cms.seo.head() in the head of your layout covers every page on the site. Call it with no arguments on a 404 or a search page and you still get the site title, default description and the WebSite and Organization nodes.
Three layers, first one wins
The SEO card on the record, then the collection's field mapping, then the Site SEO defaults. Leave the card empty and nothing is lost. Blog, blog-legacy and feed collections are mapped to Article already.
Every icon from one upload
One square PNG of 512px or more becomes the favicon, Android and Apple touch icon sizes, plus /favicon.ico. An SVG icon, a separate touch icon and a theme color are optional.
IndexNow tells engines what changed
Switch it on and publishes, edits and deletes are queued and sent to Bing and the other IndexNow engines by the sitemap's own rules. No account, no API key: the site hosts its own key file.
Your nodes join the core graph
Pass a jsonld option and your FAQPage, Product or Event node lands in the same @graph as the core nodes, referencing the Organization and WebSite core already wrote instead of describing them again.

SEO built in

Common questions

Does it replace the title tag my layout already has?

It emits one, so delete yours. head() writes both the title and the meta description, and a layout that keeps its own ships two of each. The four bundled starter templates already carry {% block seo %}{{ cms.seo.head(page|default(null)) }}{% endblock %} in place of the old title and description blocks, so a page template can override the whole block when it renders something other than the page record.

Where does the share image come from?

The Social Image on the record's SEO card first, then whatever image property the collection maps, then the Default Social Image on the Site SEO record. Whichever wins is served through ImageWorks at 1200×630 as an absolute URL, and if it carries alt text that becomes og:image:alt and twitter:image:alt. The Twitter card type follows: summary_large_image when an image resolved, summary when none did.

How do I keep one page out of search results?

Switch on No Index on that record's SEO card. The robots meta tag is emitted only when noindex or nofollow is on — no tag means index, follow, and it is quieter. The record also leaves the sitemap and loses its canonical tag. If you added the SEO card to a schema of your own, put "seo" in that schema's index array as well: the sitemap builders read the collection index rather than the object files, so without it a noindexed object still shows up in the sitemap.

Does it work on a Stacks site?

Yes. Keep one page record per Stacks page with the same route and an empty Page Template — the router never serves it, because Apache answers first — and put {{ cms.seo.head(cms.builder.page()) }} in the layout. The SEO card, title placeholders and No Index work as on any page. A post page needs no record: when a request matches a collection's URL, cms.builder.page() returns the object itself and the head is built from the collection's mapping.

Try it on a site of your own.

45 days, every Pro feature, no credit card. Build it, hand it to a client, and see what they can and can’t touch.