Create one file — src/_includes/header-includes.hbs — and whatever it contains is emitted at the top of every page’s <head>, ahead of Leed’s stylesheet link, its metadata partials and all of its scripts. There is no setting to turn on, no field in the CMS, and no plan gate. The file existing is the entire mechanism.
How it works
The first three lines of leed/head.hbs are the whole hook:
{{#if (templateExists "header-includes.hbs") }}
{{> header-includes }}
{{/if}}
<!-- Start headers generated by https://leed.ai -->Three things follow from that, and each one answers a question people ask about this file.
It is a file-existence check and nothing else. templateExists joins the name onto Eleventy’s configured includes directory and calls fs.existsSync. It reads no company setting, consults no entitlement, and has no customTemplates key behind it — unlike docs-header.hbs and docs-footer.hbs, which are probed once at build start and then gated on hasTier "starter". That difference is why this slot has no plan requirement to state: there is nowhere in the chain for a gate to sit. The other five slots, and how each of them is detected, are laid out in Overriding Leed Templates.
Your tags come first. The probe is line 1 of the partial, so your output precedes the Tailwind preload and stylesheet links, the three metadata partials, the structured-data shortcode, the inline page-identity script and every <script> tag Leed adds. The layout still owns whatever it wrote before calling {{> leed/head }} — in the scaffolded site-template.hbs and in Leed’s documentation shell that is the <title> and the viewport meta:
<head>
<title>Your page | Your site</title>
<meta name="viewport" content="width=device-width">
<!-- everything from header-includes.hbs lands here -->
<!-- Start headers generated by https://leed.ai -->
<link href="/static/css/site.css?v=…" rel="preload" as="style">
…It is an ordinary partial. Nothing registers it specially: it is picked up by the same glob that turns every .hbs file under _includes into a partial named by its path, exactly as described in Writing Your Own Partials. So it can call helpers, read the page context, and be edited while leed site build --serve is running — a .hbs change under _includes resets the Eleventy config and re-reads every partial.
What it applies to
Every page rendered by a template that calls {{> leed/head }}. In a stock repository that is:
| Page | Calls leed/head from | Your includes reach it? |
|---|---|---|
| Every documentation and API page | leed/docs/shell.hbs | Yes |
Every page using the scaffolded site-template.hbs | your own master template | Yes |
/stubs/unsubscribe.html, /stubs/unsubscribed.html | leed-unsubscribe.hbs, leed-unsubscribed.hbs | Yes — unless you have ejected them |
A layout of yours that builds its own <head> | — | No |
Two consequences worth stating out loud. Ejecting unsubscribe.hbs or unsubscribed.hbs replaces the whole document, Leed’s <head> included, so those two pages stop receiving your includes the moment you take ownership of them — put the tags in your own copy if you want them there. And if you write a layout that hand-rolls its <head> rather than calling leed/head, you get neither Leed’s head nor your own file; that page also loses the stylesheet, the metadata tags and the scripts that make code highlighting, math, diagrams and tabs work at all.
What to put in it
Anything that must be true of every page and that Leed does not already emit. The four cases below cover almost all real use.
- Verification meta
- Preconnect
- Analytics snippet
- Page-type scoped tag
A search console or domain-verification token, pasted as given:
<meta name="google-site-verification" content="8oGVn0…">
<meta name="msvalidate.01" content="B2C4…">A warm-up hint for a host the page will call, such as a font provider or an API you fetch from client-side. Keep it to hosts every page actually uses — a preconnect to a host that is not contacted is wasted:
<link rel="preconnect" href="https://api.example.com" crossorigin>
<link rel="dns-prefetch" href="https://api.example.com">A third-party analytics or consent script. Load it async or defer unless the vendor requires otherwise, because this file is included before everything else and a blocking script here blocks the whole head:
<script async src="https://cdn.example-analytics.com/t.js" data-site="abc123"></script>The file is a partial, so it renders in the page context and you can scope a tag to one page type — the one pattern here that goes beyond pasting static markup:
{{#if (eq pageTypeId "u05nxr") }}
<meta name="docsearch:version" content="latest">
{{/if}}Site-wide JSON-LD belongs here too, with one caveat worth knowing before you paste. leed/head already calls the JSON-LD shortcode, but that shortcode only emits a <script type="application/ld+json"> for pages whose page type slug is blog; every other page type gets an HTML comment instead — <!-- JSON+LD not generated for docs type of page --> and so on. So on most page types there is nothing to collide with, and on a blog page type there is. View source on the page type you care about before adding structured data, and read Social Cards and Structured Data for what is already emitted in that family.
What not to put in it
| What you want to add | header-includes.hbs? | Where instead |
|---|---|---|
| Search-console or domain verification meta tag | Yes | — |
preconnect / dns-prefetch for a host you call | Yes | — |
| Third-party analytics or consent snippet | Yes | Leed’s own tracking, and what it does and does not collect, is at How Leed Tracks Visitors |
| An extra favicon or touch-icon variant | Yes | The site’s primary icon set is configured, not hand-written — Site Identity and Branding |
| Site-wide JSON-LD | Yes, except on a blog page type, where Leed already emits some | Social Cards and Structured Data |
| Your Tailwind sheet or custom CSS | No | Adding Your Own CSS and JS — Leed emits the stylesheet itself and its URL carries a content hash that changes on every build |
| A tag for one page only | No | The page’s front matter plus a conditional in your layout — Front Matter Reference |
| Open Graph or Twitter card tags | No | Already emitted — Head and Component Partials |
A <title> | No | Your layout owns it; the docs shell and the scaffolded site template both write it before calling leed/head |
One more caution about weight. Because the file is included before the stylesheet, a synchronous third-party script here delays every other head resource on every page of the site. Prefer async or defer, and prefer one vendor snippet over three.
What the site’s headers do and do not control
Your published site sets a small, fixed set of response headers, including Content-Security-Policy: frame-ancestors 'none' on /*. That directive governs who may embed your pages in a frame — it does not restrict scripts, stylesheets or iframes that your own page loads, so a normal analytics or widget tag pasted here is not blocked by it. What it does break is anything that needs to load your pages inside someone else’s frame: preview panes, some session-replay and co-browsing products, and embed widgets that render your page in an iframe on a third-party domain. If a vendor’s install instructions mention framing your site, check the policy before assuming a paste is enough. The full header set is at Headers, Caching and Security.
Verifying it
The loop is short, and it is worth running once so you know what a working file looks like:
# 1. create the file
touch src/_includes/header-includes.hbs
# 2. put a tag in it, then start the local server
leed site build --serveOpen any page, view source, and look between the viewport meta and the <!-- Start headers generated by https://leed.ai --> comment. Your tags are the block in that gap. If the gap is empty, the file was not found — go back to the filename and its location before suspecting anything else, because that is the only failure mode this hook has. The watch loop, the local URL and what a .hbs edit triggers are covered in Local Development.
Check a documentation page as well as a marketing page. They are rendered by different templates — the docs shell and your master template — and confirming both proves the include reaches the whole site rather than one half of it.
Why there is nothing to eject
leed site eject copies a Leed-owned template into your repository so you can take ownership of it, and every entry in its registry has a Leed default to copy. header-includes.hbs has none: Leed ships no version of this file, and the {{#if}} around it renders nothing when it is absent. It is the only one of the six override slots that adds to Leed’s markup rather than replacing it, which is why it is not listed by the eject command and why creating it by hand is the correct and only way to use it. The five slots that do replace Leed markup, and what each one costs in plan terms, are in Overriding Leed Templates.