---
title: 'Page context'
description: 'The normalised per-page object baseline builds at cascade-time and exposes as _pageContext.'
slug: 'page-context'
type: 'article'
date: 2026-04-28T00:00:00.000Z
lang: 'en'
url: 'https://www.eleventy-baseline.dev/docs/core-reference/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 | 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`, `url`, `noindex`. Per-language `title`/`tagline` 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`. 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: `href`, `src`, `href`, `name`.                                                                   | `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.

{% raw %}

```nunjucks
{# 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>
```

{% endraw %}

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`, `type`, `lang`, `locale`, `date`, `url`) as the input for each graph node. See [[content-graph | Content graph]].

---

### 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.

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

---

## See also

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