Table of Contents

Page context

A normalised per-page object: one cached snapshot of the values Baseline's modules need, built once during the data cascade and read again at transform-time without re-deriving from raw cascade data.

You access it as _pageContext in templates. Internally, the head module reads it through a registry lookup so the same object is available after the cascade has closed.


Why it exists

Eleventy's HTML transformer hook (where head injection runs) only sees page metadata. The data cascade is not available there. The page-context registry caches the cascade-time view so transform-time consumers can read it back by page.url.

That trick is the difference between Baseline's head module being able to compose <title> from cascade data and it having to re-walk the cascade per request.


Where it surfaces

  • In templates: as _pageContext (computed via eleventyComputed).
  • In _snapshot: every built page-context lives under _snapshot.pageContext, keyed by URL. See Globals.
  • At transform-time: read by the head module via the registry's getByKey(page.url) lookup.

The Page Context shape

Seven top-level keys. Each one is built by a small pure function from cascade data; the field itself is what the consumer sees.

Key What it carries Built from
site title, tagline, description, url, noindex. Per-language title, tagline and description if the page has a lang and settings.languages.<lang> overrides. settings, data.settings, page.lang
page Eleventy's page snapshot, scoped to the keys baseline reads (url, inputPath, fileSlug, lang, locale, date, …). data.page
entry Front-matter values: title, description, excerpt, slug, section (path array), type, head, plus the derived breadcrumbs trail. Page-level overrides for site defaults. section coerces a string to a single-element array with a dev warning. data.title, data.description, data.excerpt, data.slug, data.section, data.type, data.head
query Cheap derived flags. Today: isHome (true when page.url === '/'). entry, page
meta Composed <title>, default description, canonical, robots, noindex. Title composition uses head.titleSeparator. data, site, page, query
render Render-environment values. Today: generator (Eleventy's version string, when present). data.eleventy.generator
head Merged + deduplicated link, script, style, meta arrays from settings.head and page front matter. Dedupe keys: links on rel plus href (canonicals normalised first), scripts on src, styles on href, metas on whichever of charset, name, property or http-equiv identifies them, plus media where present, so two theme-color tags scoped to different colour schemes both survive. settings.head, data.head

Reading it in a template

_pageContext is a regular cascade key. You can read it the same way you read page or any other piece of cascade data.

{# The composed <title>, separator-joined and homepage-aware #}
<title>{{ _pageContext.meta.title }}</title>

{# The resolved canonical, suitable for <link rel="canonical"> #}
<link rel="canonical" href="{{ _pageContext.meta.canonical }}">

{# The site-level title for the active language #}
<header>{{ _pageContext.site.title }}</header>

{# Front-matter values, normalised through entry #}
<p class="excerpt">{{ _pageContext.entry.description }}</p>

You can read raw front matter directly from the cascade if that is all you need. Reach for _pageContext when you want the normalised view: composed title (instead of recomposing the separator logic), resolved canonical (instead of re-walking the fallback chain), per-language site fields (instead of re-reading the settings.languages.<lang> override yourself).

The head module reads the same object you would; you are not paying a second build for it.

The content graph also reads _pageContext at build time, using its identity fields (title, slug, description, section, breadcrumbs, type, lang, locale, translationKey, isDefaultLang, date, url) as the input for each graph node. See Content graph.


Fields on page that Baseline adds

_pageContext.page is a scoped copy of Eleventy's own page object, so most of what is on it is Eleventy's. Two fields are Baseline's, and both are read from page directly rather than through _pageContext.

Field Available when What it carries
page.lang multilingual mode is on The page's language code, resolved from its data rather than its path: lang, the language alias, or derived from locale, falling back to the site default. page.locale, page.isDefaultLang and page.translationKey come with it.
page.translations multilingual mode is on This page's sibling translations, the current page excluded, sorted by language code. Each entry is { url, lang, label, title, description, isDefaultLang }.

page.translations is grouped from the content graph, so the graph's membership rules decide which siblings show up in it. The multilang module has the full treatment: what that excludes, a switcher example, and the filters that predate the field.


Opt out: _internal: true

Templates that set _internal: true in their data are skipped by the registry. Pages with a non-HTML output extension are also skipped automatically.

This is for synthetic templates that should not get a page context: sitemap XML, the navigator debug page, any virtual template you write yourself that does not represent a real page. Set _internal: true in the template's data and the registry leaves it alone.

// In a virtual template's data
eleventyConfig.addTemplate('feed.njk', virtualTemplateContent, {
	// ... other template settings,
	permalink: '/feed.xml',
	_internal: true
});

See also

  • Globals - _pageContext and _snapshot.pageContext.
  • Internals - the registry primitive that backs the cache.
  • Head module - the consumer side; reads _pageContext at transform-time.
  • Site settings - the inputs site and head are built from.
  • Content graph - reads _pageContext at build time for node identity.
  • SEO graph - the sibling per-page substrate; same cascade-build, transform-read lifecycle.