---
title: Frontmatter
description: Every frontmatter field a page accepts, all optional — title, description, sidebar, SEO, search, and the rest, with what each one controls.
---

Every page accepts the following frontmatter. All fields are optional.

<TypeTable
  type={{
    title: { type: "string", description: "Page title." },
    description: { type: "string", description: "Page summary." },
    type: {
      type: "string",
      default: "doc",
      description: "Content type. blog/changelog drive feeds.",
    },
    date: {
      type: "string",
      description: "Publish date for blog/changelog feeds (ISO or YAML date).",
    },
    authors: {
      type: "string | string[] | object[]",
      description:
        "Post author(s) for blog/changelog content — a name, or objects with a name plus optional avatar/url and any extra fields. Preserved as-is.",
    },
    slug: { type: "string", description: "Override the generated slug." },
    draft: {
      type: "boolean",
      default: "false",
      description: "Exclude from production builds.",
    },
    lastModified: {
      type: "string",
      description:
        'Pin the page\'s "last updated" date (ISO or YAML date); overrides the git-derived date.',
    },
  }}
/>

## Sidebar

```yaml lineNumbers
sidebar:
  label: Install
  order: 2
  icon: download
  badge: New
  hidden: false
  display: page
```

`display` sets the render mode of the page's folder group ([per-group overrides](/docs/content/navigation#per-group-overrides)) and is only meaningful on a folder's `index` page under the generated sidebar — anywhere else (a non-index page, the content root's own `index` page, or any page under an explicit `navigation.sidebar`) it has no group to configure, and Blume warns with `BLUME_SIDEBAR_DISPLAY_IGNORED`.

## SEO

```yaml lineNumbers
seo:
  title: Install Blume
  description: Install Blume and scaffold your first project.
  image: /og/install.png
  canonical: https://acme.com/install
  noindex: false
```

## Search

```yaml lineNumbers
search:
  exclude: false
  tags: [api]
```

## Changelog

Changelog entries (`type: changelog`) accept an optional `changelog` object for richer feed and display metadata:

```yaml lineNumbers
type: changelog
changelog:
  version: 1.2.0
  date: 2026-06-20
  category: Features
```

`date` may live here or at the top level — both feed the [changelog RSS feed](/docs/content#feeds). See [Changelog](/docs/advanced/changelog) for the generated timeline page and feed.

## Custom keys

Any key outside this reference fails the build, so typos are caught early. Projects that carry their own metadata can opt extra keys in via [`frontmatter.extend`](/docs/configuration#frontmatter) in `blume.config.ts`, each validated by a schema the project supplies:

```ts blume.config.ts lineNumbers
import { defineConfig } from "blume";
import { z } from "zod";

export default defineConfig({
  frontmatter: {
    extend: {
      owner: z.string(),
      reviewedAt: z.coerce.date().optional(),
    },
  },
});
```

```yaml page.mdx
---
title: Install
owner: "@sam"
reviewedAt: 2026-06-20
---
```

Schemas are accepted through the [Standard Schema](https://standardschema.dev) interface, so Zod (whichever version your project installs), Valibot, and ArkType all work. Every declared key is validated on every page — absent ones included — so a required schema enforces the key site-wide; mark it `.optional()` to validate only where present. All other keys stay strictly validated, and built-in fields can't be redeclared.

### Per-type keys

To require keys only on one content type — an RFC's `status`, an incident report's `severity` — declare them under [`content.types`](/docs/configuration#content) instead, keyed by the frontmatter `type` they apply to:

```ts blume.config.ts lineNumbers
import { defineConfig } from "blume";
import { z } from "zod";

export default defineConfig({
  content: {
    types: {
      rfc: {
        frontmatter: {
          domain: z.string(),
          status: z.enum(["draft", "review", "enforced"]),
        },
      },
    },
  },
});
```

```yaml rfcs/openapi-request-schemas.mdx
---
title: OpenAPI request schemas
type: rfc
domain: architecture
status: enforced
---
```

Per-type keys follow the same validation rules as `extend`, scoped to pages whose resolved `type` matches — including pages that set no `type`, when the declaration is for [`content.defaultType`](/docs/configuration#content). A key belongs to one declaration, site-wide or per-type, not both. And a key declared only for another type stays unknown elsewhere, so a stray `status` on a plain doc page still fails the build.

A page that fails validation fails `blume build` with a diagnostic naming the file and key. With [`--no-strict`](/docs/reference/cli#common-flags), the build succeeds anyway and the failing pages are dropped from the output — the build summary reports how many.

Schemas are exported from `blume/schema` for editor and migration tooling.
