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
- Avoid overriding or replacing `config.ctx` in your Markdoc config; if you must extend it, preserve the existing `headingSlugger`.
- If you supply a custom ctx, ensure `ctx.headingSlugger` is set to a function `(text: string) => string`.
- 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
- Do not override or replace `config.ctx` in Markdoc config.
- If extending ctx, spread the existing object to preserve `headingSlugger`.
- Test heading rendering after any Markdoc config change.
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
- **${relativePartialPath}** contains invalid content: Could n
- **${rootRelativePath}** contains invalid content: ${validati
- Could not resolve image ${JSON.stringify(node.attributes.src
- The markdown processor "${processor.name}" does not provide
- [RSS] You can only glob entries within 'src/pages/' when pas
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/6668753bd0f97aaf.
Report an issue: GitHub.