Filters
Eleventy filters Baseline registers. Some are always available; the rest come on with the module that registers them.
At a glance
| Filter | Module | Registered when |
|---|---|---|
markdownify, relatedPosts, isString, t |
core | always |
inlineESbuild, inlinePostCSS |
assets |
always (assets is unconditional) |
translationsFor, translationIn, defaultTranslation |
multilang |
when multilingual is active |
_inspect, _json, _keys |
navigator |
always (the option gates the virtual page, not the module) |
Core filters
Registered by the entry point on every build, unless you got there first. If your project already defines a filter under one of these names, Baseline leaves yours alone and says which ones it skipped. The same goes for the image shortcode. Nothing to configure: define it and it is yours.
markdownify
Render an inline markdown string to HTML. No wrapping <p> tag (uses markdown-it's renderInline). Raw HTML in the input is escaped rather than passed through: the filter runs a stock markdown-it, and stock markdown-it has html: false.
{{ "Read the **manual**." | markdownify }}
Use it for short bits of markdown that live in front matter or data files (taglines, descriptions). Block-level markdown belongs in .md files, not in markdownify.
It runs its own markdown-it instance rather than the one Baseline amends, so none of the substrate reaches it: [[slug]] comes out as literal text, {#id .class} stays as written, and heading IDs never come up because renderInline skips the block parser. Bold, italic, code spans and links are what it is for. If a string needs the wikilinks or the attribute syntax, it belongs in a .md body, where the page's own markdown pass gives it both.
relatedPosts
Filter the current page out of a collection. The page is read from this.ctx.page at render time.
{% asyncEach item in collections.docs | relatedPosts %}
{% include "partials/related-card.njk" %}
{% endeach %}
If the page has no url (rare), the filter returns the collection unchanged.
isString
A type guard: true when the value is a string, false otherwise. Useful in templates that accept either a scalar or an object.
{% if value | isString %}
<p>{{ value }}</p>
{% else %}
<p>{{ value.text }}</p>
{% endif %}
t
Look up a UI string by key. This is the string half of translation, separate from the page relationships the multilang module handles, and it is registered whether or not multilingual mode is on. A single-language site still benefits from one place for its labels.
Strings live in _data/translations/, one file per language code. Eleventy auto-loads the directory, so there is nothing to register.
// src/_data/translations/en.js
export default {
nav: { home: 'Home', about: 'About' },
greeting: 'Hello, {name}',
items: { one: '{count} item', other: '{count} items' }
};
{{ "nav.home" | t }}
Keys are dot-paths into that table, so nav.home reads the nested key. The language comes from the page's own page.lang, falling back to your defaultLanguage. Pass lang to override a single call.
Two things the table can carry beyond plain strings. A {name} placeholder is filled from the params you pass, and an unmatched placeholder stays visible rather than rendering blank:
{{ "greeting" | t({ name: "Cris" }) }}
And an entry written as an object of plural forms is selected by count, through Intl.PluralRules. That means CLDR categories, so a language with three forms gets three:
{{ "items" | t({ count: 1 }) }}
{{ "items" | t({ count: 7 }) }}
A key that is missing in the page's language falls back to the default language. A key missing in both renders as the key itself and logs a warning, so the gap shows up on the page instead of leaving a blank you have to notice.
The filter reads the cascade through Nunjucks, which is what Baseline sets for both HTML and markdown. If you add another template engine, t returns the key there rather than the string.
Assets filters
Registered by the assets module, which is always active.
Both inline filters return raw <script> or <style> markup as a string. Nunjucks escapes strings by default, so pipe through | safe (or use {{- value | safe -}} to also strip surrounding whitespace) when you want the markup to render as HTML rather than as literal angle brackets in the page.
inlineESbuild
Async filter. Bundle a JS file through esbuild and return it wrapped in <script> tags, ready to drop into a template.
{% set jsPath = _baseline.paths.assets ~ "js/critical.js" %}
{{ jsPath | inlineESbuild({ minify: true }) | safe }}
Accepts the same options as the assets.esbuild config, but does not inherit it: what you pass is merged over the processor's defaults (minify: true, target: 'es2020', bundle: true), whatever assets.esbuild says.
attributes is the exception, and goes on the tag rather than to esbuild:
{{ jsPath | inlineESbuild({ attributes: { type: "module", defer: true, nonce: nonce } }) | safe }}
true renders a bare attribute (defer); false, null and undefined are omitted, so you can toggle one with a value instead of building the object conditionally.
Use it rather than unwrapping the tag. Nunjucks' striptags is the obvious tool for that and it is the wrong one: it removes anything tag-shaped, and minified JS is full of < followed by a letter, so it cuts expressions mid-statement. The build stays green, the HTML stays well-formed, and the error exists only in the browser. nonce is the case that cannot be worked around at all, since a hash-based CSP needs the digest taken over the bundle this call just produced, and the filter is the only thing holding both halves at once.
A failure fails the build. There is no useful site on the other side of a bundle that did not compile, and the error names the file and carries the original as its cause. The dev server is the exception: under --serve the error is logged and a <script>/* Error processing JS */</script> placeholder is returned, because a dead watch loop is worse than a broken reload.
inlinePostCSS
Async filter. Process a CSS file through PostCSS and return it wrapped in <style> tags. It uses the same config the compiled entry points use: your postcss.config.js if you have one, else Baseline's fallback (where cssnano only joins in when ELEVENTY_ENV=production).
{% set cssPath = _baseline.paths.assets ~ "css/critical.css" %}
{{ cssPath | inlinePostCSS | safe }}
Takes an attributes object on the same terms as inlineESbuild, and nothing else: PostCSS has no per-call options, so attributes is the whole bag.
{{ cssPath | inlinePostCSS({ attributes: { media: "print", nonce: nonce } }) | safe }}
Same error semantics as inlineESbuild: a failure fails the build, and only the dev server degrades to a <style>/* Error processing CSS */</style> placeholder.
Multilang filters
Registered by the multilang module, available only when multilingual mode is active. All three take a page and read the translation map Baseline already holds, so there is no collection to pass.
Each entry they return is a record rather than a page: { url, lang, label, title, description, isDefaultLang }. label is the language's languageName; title and description are that translation's own. If you need more of the page than that, look it up by url.
translationsFor
Every language variant of the page you pass, itself included, sorted by language code. Use it when you need the variants of some other page; for the page being rendered, page.translations is shorter and leaves the current language out.
{% set translations = page | translationsFor %}
{% for variant in translations %}
<a href="{{ variant.url }}" hreflang="{{ variant.lang }}">{{ variant.lang }}</a>
{% endfor %}
translationIn
One named language variant, or null when it does not exist.
{% set fr = page | translationIn("fr") %}
{% if fr %}
<a href="{{ fr.url }}">Lire en français</a>
{% endif %}
defaultTranslation
The default-language variant, or null when none exists. Handy for canonical resolution and fallbacks.
{% set canonical = page | defaultTranslation %}
Navigator filters
Registered by the navigator module, which loads on every build. The navigator option gates its virtual page, not these filters, so all three are available in production too. They are debugging helpers; treat them as dev-only by habit rather than by configuration.
_inspect
Pretty-print a value using Node's util.inspect. Defaults: depth: 4, maxArrayLength: 10, breakLength: 80, compact: true. Override per-call by passing an options object.
<pre>{{ page | _inspect({ depth: null }) }}</pre>
The navigator template (/navigator-core.html) uses _inspect with depth set from inspectorDepth (default 4; configurable via options.navigator.inspectorDepth).
_json
Serialise a value with JSON.stringify. Second argument is the indentation, default 0 (compact).
<pre>{{ page | _json(2) }}</pre>
_keys
Return an object's own keys, sorted alphabetically.
{{ page | _keys | join(", ") }}
See also
- Plugin entrypoint - registration order.
- Assets module - what the inline filters share with the build pipeline.
- Multilang module - how
translationKeyand the translation map are populated. - Navigator module - the
_runtimeand_ctxNunjucks globals that pair with these filters.