withastro/astro · error · MarkdocError

Unexpected problem adding heading IDs to Markdoc file. Did y

Error message

Unexpected problem adding heading IDs to Markdoc file. Did you modify the `ctx.headingSlugger` property in your Markdoc config?

What it means

The Markdoc `heading` schema transform needs a slugger from `config.ctx.headingSlugger` to generate stable heading IDs. If that property is absent at transform time (the `?` chain returns undefined), the integration throws `MarkdocError` suggesting the user altered `ctx.headingSlugger` in their Markdoc config.

Source

Thrown at packages/integrations/markdoc/src/heading-ids.ts:42

}

/*
	Expose standalone node for users to import in their config.
	Allows users to apply a custom `render: AstroComponent`
	and spread our default heading attributes.
*/
export const heading: Schema = {
	children: ['inline'],
	attributes: {
		id: { type: String },
		level: { type: Number, required: true, default: 1 },
	},
	transform(node, config: HeadingIdConfig) {
		const { level, ...attributes } = node.transformAttributes(config);
		const children = node.transformChildren(config);

		if (!config.ctx?.headingSlugger) {
			throw new MarkdocError({
				message:
					'Unexpected problem adding heading IDs to Markdoc file. Did you modify the `ctx.headingSlugger` property in your Markdoc config?',
			});
		}
		const slug = getSlug(attributes, children, config.ctx.headingSlugger);

		const render = config.nodes?.heading?.render ?? `h${level}`;

		const tagProps =
			// For components, pass down `level` as a prop,
			// alongside `__collectHeading` for our `headings` collector.
			// Avoid accidentally rendering `level` as an HTML attribute otherwise!
			typeof render === 'string'
				? { ...attributes, id: slug }
				: { ...attributes, id: slug, __collectHeading: true, level };

		return new Markdoc.Tag(render, tagProps, children);
	},

View on GitHub (pinned to d081033d5f)

Solutions

  1. Avoid overriding or replacing `config.ctx` in your Markdoc config; if you must extend it, preserve the existing `headingSlugger`.
  2. If you supply a custom ctx, ensure `ctx.headingSlugger` is set to a function `(text: string) => string`.
  3. Remove any extension that strips ctx and re-run the build.
Defensive patterns

Strategy: type-guard

Validate before calling

function hasHeadingSlugger(config: any): boolean {
  return !!(config?.ctx?.headingSlugger) && typeof config.ctx.headingSlugger === 'function';
}

Type guard

function hasSlugger(config: unknown): config is { ctx: { headingSlugger: (s: string) => string } } {
  return !!config && typeof (config as any)?.ctx?.headingSlugger === 'function';
}

Prevention

When it happens

Trigger: A user `markdocConfig` that overrides `ctx` and removes/renames `headingSlugger`. Calling the heading transform outside the content pipeline where ctx isn't seeded. A custom extension that replaces the `ctx` object passed to Markdoc transforms.

Common situations: Customizing Markdoc ctx for heading behavior and accidentally dropping the slugger. Plugin ordering that mutates `config.ctx`. Using a Markdoc version where the integration's ctx contract changed.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/6668753bd0f97aaf. Report an issue: GitHub.