Table of Contents

Dates

Baseline resolves two dates for every page, datePublished and dateModified, from a list of sources you can change. One resolver answers for the JSON-LD graph, the Open Graph tags, the sitemap, the page context and the content graph, so no two surfaces can disagree about when a page was published.

It does not use Eleventy's page.date.


Why it exists

Eleventy gives every page a page.date, and when nothing in your front matter says otherwise it fills that in from the file's creation time on disk. On your laptop that is roughly when you made the file. On CI it is the moment the repository was cloned.

So a site that sets no dates gets, on every deploy, a datePublished that is identical across every page, to the millisecond, and that moves forward each time you build. It usually lands after the modified date. Search engines discount a sitemap that claims every URL changed on every deploy.

The second problem is quieter. page.date is always an instant, a precise moment. But 2026-04-28 is not a moment, it is a day. Turning it into one picks midnight in some time zone, and the date then shifts by a day for anyone formatting it in another. Nothing in Eleventy carries the difference.

Baseline keeps both facts: where a date came from, and whether it is a day or a moment.


Date or instant

Every resolved date is one of two kinds.

Kind Example Written as
Calendar date 2026-04-28 the day, unchanged
Instant 2026-04-28T14:30:00+02:00 converted to UTC, 2026-04-28T12:30:00Z

A calendar date is never widened into a timestamp for an output that accepts a day. Both the sitemap's <lastmod> and schema.org's datePublished take a date on its own, so that is what they get.

Writing a date with no quotes in YAML front matter gives you a calendar date:

---
datePublished: 2026-04-28
---

Add a time and you have an instant. If that time carries no offset, it is read in the site's time zone:

---
datePublished: 2026-04-28T14:30
---

The source lists

Each field has an ordered list of sources. Baseline tries them in order and the first one that yields a usable date wins.

Field Default sources
datePublished datePublished, date, :filename
dateModified dateModified, modified, updated, :git-modified, :datePublished

A name with no colon is a front-matter key, read from the page's data. A name with a colon is derived.

Source Where it comes from
:filename A YYYY-MM-DD anywhere in the file name, as a calendar date
:git-modified The last commit to touch the file, as an instant
:git-created The first commit to touch the file, as an instant
:datePublished The published date this page just resolved to
:default Expands to the built-in list for that field

Nothing is invented

If no source answers, the field stays empty. The page gets no datePublished, the sitemap entry gets no <lastmod>, and the JSON-LD omits the property. An absent date is correct; a wrong one is not, and datePublished is optional on a WebPage anyway.

That is why :git-created is not in the published default. A file's first commit is when it entered the repository, which is not when it was published. A page that sat behind draft: true for three weeks would be dated three weeks before anyone could read it.

Changing them

options.dates takes either field. :default expands to the built-in list in place, so you can add a key without restating the chain:

eleventyConfig.addPlugin(baseline(settings, {
	dates: {
		datePublished: ['pubDate', ':default']
	}
}));

That tries your own pubDate key first, then falls back to everything Baseline would have tried.

File modification time is not a source, and Baseline will refuse it by name if you ask. On CI it is the clone time rather than the date the content changed, which is the problem this page opens with.


Time zone

settings.timeZone is optional and defaults to UTC.

export default {
	timeZone: 'Europe/Amsterdam'
};

It only affects one thing: a date-time written without an offset. A value that carries its own offset is converted from that, and a calendar date is not a moment, so neither is touched by it.

Any IANA time zone name works, along with the aliases and casings your platform accepts, because Baseline validates by handing the value to Intl and seeing whether it is taken. UTC, utc, GMT and Etc/UTC all resolve to UTC. Node ships the time zone database, so nothing needs installing.

A bare offset like +02:00 is refused. It has no daylight saving, so it would be wrong for half the year, which makes it a value rather than a zone. Use the name of the place.


Git as a source

:git-modified and :git-created come from one git log walk, run once per build and cached, rather than one call per file.

They read the author date, not the committer date. Rebasing rewrites committer dates on commits whose content never changed, which would move a page's modified date for a reason that has nothing to do with the page.

Git is skipped entirely in two cases. Resolution falls through to the next source, and Baseline says one line about it at the start of the build:

  • No repository. Nothing to read.
  • A shallow clone. GitHub Actions checks out at depth 1 by default, which makes every file report the same commit. A uniform wrong answer is worse than none, so Baseline treats the git sources as empty. Fetch the full history if you want them: fetch-depth: 0 in actions/checkout.

Neither line appears unless a source list actually names :git-created or :git-modified, so a site that does not use them never hears about git.


Where the resolved dates appear

Surface What it carries
_pageContext.page.datePublished / .dateModified The resolved strings, for templates
JSON-LD datePublished / dateModified On the WebPage and Article nodes
article:published_time / article:modified_time Open Graph, on article-type pages
Sitemap <lastmod> The resolved modified date, or your sitemap.lastmod if you set one
_navigator.nodes[url].datePublished / .dateModified On every content graph node

Each is handed a finished string. No template formats a date, so there is no second place one can be reinterpreted.

Baseline does not format dates for display. There is no date filter and no dateStyle setting yet. For a human-readable date, reach for Intl.DateTimeFormat with the zone and locale Baseline already resolved for the page.


Scheduling is not handled

There is no publishDate or expiryDate, and no clamp forcing a modified date to fall after a published one, because a scheduled post can legitimately be edited before it goes out. To keep a page out of a build, use draft: true, which the drafts preprocessor skips in production.


See also

  • Site settings for settings.timeZone alongside the rest of the settings argument.
  • Config export for options.dates alongside the other options.
  • Page context for the object the resolved dates land on.
  • SEO graph for the JSON-LD and Open Graph surfaces that read them.