---
title: Frontmatter transformers
description: Define typed custom frontmatter and lifecycle hooks for Leadtype pipeline data.
status: updated
related:
  - title: Frontmatter
    href: /docs/authoring/frontmatter
    description: Review the default page metadata contract before adding custom fields.
  - title: createDocsSource
    href: /docs/reference/source
    description: Pass schemas and transformers into the framework-neutral source API.
---
Custom frontmatter is first-class in Leadtype. Define a schema when your docs need product-specific fields, then use transformers to derive or normalize those fields as the pipeline runs.

Transformer hook types live under `leadtype/transformers` when you want explicit annotations in shared config helpers.

Use transformers for release-channel behavior such as canary, RC, preview, or
stable builds. Those channels describe the build or deployment, not the page
source, so they should be derived from config, environment, package version, or
git ref instead of authored into page `status`.

```ts title="docs.config.ts"
import { defineDocsConfig } from "leadtype";
import * as v from "valibot";

const frontmatterSchema = v.object({
  title: v.string(),
  description: v.optional(v.string()),
  apiArea: v.optional(v.string()),
  sidebarLabel: v.optional(v.string()),
});

export default defineDocsConfig({
  product: {
    name: "Acme SDK",
    tagline: "SDK docs for Acme.",
  },
  frontmatterSchema,
  transformers: [
    {
      name: "api-area",
      afterFrontmatter(page, context) {
        return {
          ...page,
          data: {
            ...page.data,
            apiArea: context.relativePath?.startsWith("reference/")
              ? "reference"
              : "guides",
          },
        };
      },
      beforeSearchChunk(chunk) {
        if (!chunk.text.includes("apiArea:")) {
          return;
        }
        return {
          ...chunk,
          text: `${chunk.text}\n\n${chunk.headingPath.join(" ")} reference`,
        };
      },
    },
  ],
});
```

## Typed source data

Pass the same schema and transformers to lower-level APIs when you are not using `leadtype generate`:

```ts
import { createDocsSource } from "leadtype";
import docsConfig from "./docs.config";

const source = await createDocsSource({
  contentDir: "./docs",
  frontmatterSchema: docsConfig.frontmatterSchema,
  transformers: docsConfig.transformers,
});

const page = await source.loadPage("reference/auth");

page?.frontmatter.apiArea;
```

Without a schema, frontmatter remains `Record<string, unknown>`. With a schema, page and transformer payloads are typed from the schema output.

## Hook order

Transformers run in array order. A hook may return a replacement object or return nothing to keep the current value.

|Hook|Purpose|
|--|--|
|`beforeParse`|Edit raw source before frontmatter and MDX parsing.|
|`afterFrontmatter`|Add, validate, or normalize frontmatter-derived metadata.|
|`afterMdxAst`|Adjust the transformed mdast before markdown serialization.|
|`afterFlattenMarkdown`|Customize flattened markdown before it is written or indexed.|
|`beforeSearchIndex`|Edit the full search document list.|
|`beforeSearchChunk`|Edit individual search chunks.|
|`beforeLlmsTxt`|Customize root or docs-scoped `llms.txt`.|
|`beforeLlmsFull`|Customize `llms-full.txt`.|
|`beforeAgentsMd`|Customize package-bundled `AGENTS.md`.|

Hook errors include the transformer name, hook name, and source path when Leadtype has one.

`beforeSearchChunk` receives the current chunk text and metadata. If you change `text`, Leadtype recalculates the chunk length before indexing, so you do not need to update length manually.
