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 viaeleventyComputed). - 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 -
_pageContextand_snapshot.pageContext. - Internals - the registry primitive that backs the cache.
- Head module - the consumer side; reads
_pageContextat transform-time. - Site settings - the inputs
siteandheadare built from. - Content graph - reads
_pageContextat build time for node identity. - SEO graph - the sibling per-page substrate; same cascade-build, transform-read lifecycle.