Plugin entrypoint
The baseline() factory is the single entry point. You call it once in your Eleventy config callback with two arguments: site settings and module options. Everything below is what that call sets up.
For the directory contract Baseline ships alongside the factory, see Config export. For the settings argument, see Site settings. This page covers the second argument, options, which controls module activation and behaviour.
Default usage
import baseline, { config as baselineConfig } from '@apleasantview/eleventy-plugin-baseline';
import settings from './src/_data/settings.js';
/** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */
export default async function (eleventyConfig) {
await eleventyConfig.addPlugin(baseline(settings));
}
export const config = baselineConfig;
baseline() itself is synchronous, but it returns an async closure (Eleventy's documented async-plugin pattern). The await on addPlugin is the correct shape.
Options
await eleventyConfig.addPlugin(
baseline(settings, {
verbose: false,
multilingual: false,
sitemap: true,
navigator: true,
head: { titleSeparator: ' – ', showGenerator: false },
assets: { esbuild: {} },
media: { image: { widths: [320, 640, 960, 1280, 1920], formats: ['avif', 'webp', 'jpeg'] } }
})
);
| Option | Type | Default | What it does |
|---|---|---|---|
verbose |
boolean |
false |
Emit Baseline's full build narrative, the info-level lines from the plugin and every module. Three states rather than two; see below. |
multilingual |
boolean |
false |
Turn on the multilang module. Activation is explicit: this, a default language (settings.defaultLanguage or settings.defaultLocale), and a non-empty settings.languages are all required. Nothing is inferred, and either default-language key works. |
sitemap |
boolean |
true |
Generate XML sitemaps. Set false if you produce sitemaps elsewhere. |
navigator |
boolean or object |
true in development |
Register debug globals and the optional /navigator-core.html template. The boolean toggles both. Object form { template, inspectorDepth } tunes them independently: template controls whether the page renders, and inspectorDepth (default 4) sets how deep that page inspects what it dumps. It does not change the _inspect filter, which carries its own default of 4. See the navigator module. |
head.titleSeparator |
string |
' – ' |
String placed between the page title and the site title in <title>. |
head.titleTemplate |
string |
none | Template for the whole <title>, with tokens %s (page title), %siteTitle% and %tagline%. Overrides the separator composition. A page can set its own in front matter, or null for a bare title. |
head.showGenerator |
boolean |
false |
Emit a <meta name="generator"> tag in the head. |
assets.esbuild |
object |
{} |
An options bag forwarded to esbuild whole. See the esbuild documentation for keys. Baseline defaults minify, target and bundle, and sets entryPoints and write itself. |
media.image.widths |
number[] |
[320, 640, 960, 1280, 1920] |
Default widths for the image shortcode, set once here instead of on every call. Add 'auto' if you want the full-size original re-encoded too. |
media.image.formats |
string[] |
['avif', 'webp', 'jpeg'] |
Default output formats. Order decides <source> order; the <img> fallback picks by compatibility regardless. jpeg is last so nothing reaches it that could use something better, and it costs a third encode per width. Drop it and the fallback is a webp, which the client that fell back to it may also refuse. |
media.image.sizes |
string |
'(max-width: 768px) 100vw, 768px' |
Set one and it is used verbatim. Leave it and Baseline puts auto in front on lazy images, so the browser uses the real layout width and this string is only the fallback for browsers that cannot. See Image shortcode. |
Option keys are checked structurally at startup and mismatches are warned about, not thrown. media is checked strictly, so a misspelled key there is named rather than ignored: options: media.image, Unrecognized key: "width".
The three states of verbose
Saying nothing and saying false are different requests, so the option has three settings rather than two.
Leave it unset and you get the boxed startup banner, a "Baseline vX, running Eleventy vY" line, and one line naming the active modules. Set it to true for the full narrative on top of that. Set it to false and Baseline says nothing at all.
Eleventy's own --quiet has the same effect as false, so a quiet build is quiet throughout. warn and error emit in every state: a silent build still surfaces problems.
What Baseline registers
In the order the entry point runs:
| # | Registers | What it does |
|---|---|---|
| 1 | Reserved data keys | Initialised to {} so module-namespaced data can merge cleanly. The list is on Internals; the populated half is on Globals. |
| 2 | _baseline top level and env |
name and version at the top level. env carries mode, read from process.env.ELEVENTY_ENV, and package. Mirrors the shape of Eleventy's own eleventy global. |
| 3 | _baseline.options |
Snapshot of the resolved options object (state.options). |
| 4 | Date global | A date object exposed to templates, carrying toUTCISO(value). |
| 5 | HtmlBasePlugin | Sets baseHref from settings.url, falling back to pathPrefix when no site URL is set. |
| 6 | _baseline.features |
Flags reflecting the resolved options. |
| 7 | Virtual directories | Synthesises the assets and public keys on eleventyConfig.directories. See Config export for the asymmetry with the on-disk static/ folder. |
| 8 | Passthrough copy | Mounts the public virtual directory at the site root /. |
| 9 | _baseline.paths |
Resolved input, output, includes, data, assets and public directories. |
| 10 | Drafts preprocessor | Skips pages with draft: true when ELEVENTY_RUN_MODE === 'build'. Guarded: if you have already registered a drafts preprocessor, Baseline leaves it alone. |
| 11 | Runtime stores | The content map, the translation map, the graph-built translation index and the slug index. Read by the page-context registry, the head module and wikilinks. |
| 12 | Page-context registry | Per-page object built at cascade-time, exposed as _pageContext. See Page context. |
| 13 | Modules | In order: multilang when active, sitemap when active, then navigator, head and assets. The last three are unconditional; the navigator option gates its virtual page, not the module. |
| 14 | Media cache | In a build, image renditions are written to .cache/media/ and copied into the output on eleventy.after. See Image shortcode. |
| 15 | Filters | markdownify, relatedPosts, isString, t. Module-specific filters are on Filters. |
| 16 | Shortcode: image |
Responsive <picture> via @11ty/eleventy-img. See Image shortcode. |
| 17 | Image dev server | On-demand image generation during --serve. |
| 18 | Markdown substrate | Three plugins applied through amendLibrary('md', ...), each guarded by a safeUse helper that skips re-registering a plugin you already loaded: markdown-it-attrs, an auto-heading-ids plugin (stable ids with WordPress-style dedup, manual ids win), and the wikilinks plugin. See Content helpers for the author-facing syntax. |
| 19 | Content-graph pre-pass | Sentinel-guarded inner build that runs on every eleventy.before event, walks every rendered page, writes .cache/_baseline/content-graph.json and hooks the result into the cascade. See Content graph for what it builds and the keys it binds. |
Steps 10, 15 and 16 are the guarded ones: a preprocessor, filter or shortcode name you have already defined stays yours, and the build prints one line naming what it skipped. See Who owns a name for why that works wherever you wrote it.
The head module's render strategy is selected by a code-level driver seam, not a user option. One driver ships today. See Internals.
Who owns a name
Baseline yields: a filter, shortcode or preprocessor you have already defined stays yours, and the build prints one line naming what it skipped. Two Eleventy behaviours sit underneath that and are worth knowing when it does not go the way you expect.
Your config finishes before any plugin body runs. addFilter and addShortcode take effect where you write them; addPlugin queues, and the queue does not start until your config callback has returned. So a name you register directly is already in place when Baseline executes, which is exactly why Baseline can see it and stand down, whether you registered it above or below the addPlugin call. Order within your own file does not matter here.
Registering from inside another plugin is the case that does behave by order, because plugin bodies run in the order they were added. If you need your registration to land after Baseline's, add it as a plugin of your own, after Baseline.
Overwriting is invisible by default. Eleventy does warn when a filter or shortcode is redefined, but through debug, so nothing appears on a normal build. To see it:
DEBUG=Eleventy:UserConfig npx @11ty/eleventy
Collections behave the other way and throw outright on a duplicate name, so the two adjacent APIs fail in opposite directions.
Plugin metadata
baseline() returns a function whose name property is set to @apleasantview/eleventy-plugin-baseline, so eleventyConfig.hasPlugin('@apleasantview/eleventy-plugin-baseline') returns true once the plugin is added.
Not included
@11ty/eleventy-img's HTML transform (eleventyImageTransformPlugin) is not bundled. Add it yourself if you want content-level <img> rewrites. The image shortcode works either way; the difference is whether <img> tags written into Markdown also get transformed.
See also
- Site settings - the
settingsargument. - Config export - the directory contract.
- Globals -
_baseline,_pageContext,_snapshot, and the reserved keys. - Page context - what the registry exposes.
- Internals - registry, stores, virtual dirs, driver seam.