withastro/astro · error · Error
[MDX] A Sätteri plugin attempted to inject invalid…
Error message
[MDX] A Sätteri plugin attempted to inject invalid frontmatter. Ensure `ctx.data.astro.frontmatter` is a valid object that is not `null` or `undefined`.
What it means
The MDX integration's Saetteri compile pipeline seeds ctx.data.astro.frontmatter before running your remark/rehype/recma plugins, then reads the final value back from the MDX result (mdxResult.data.astro.frontmatter) so plugins that replace the bag are honored. If that final value is null, undefined, or fails isFrontmatterValid (not a valid plain object), compilation of the .mdx file aborts with this error.
Solutions
- In your plugin, mutate the existing bag instead of replacing it: Object.assign(ctx.data.astro.frontmatter, { title })
- If you must replace it, assign a plain non-null object: ctx.data.astro.frontmatter = { title: 'Hello' }
- Verify the plugin only runs for MDX compilation, not the plain markdown pipeline
- Update @astrojs/mdx and @astrojs/internal-helpers together so isFrontmatterValid matches the version that seeded the data
Example fix
// before (inside a rehype/recma plugin)
ctx.data.astro.frontmatter = null;
// after
Object.assign(ctx.data.astro.frontmatter, { title: 'Hello' }); Defensive patterns
Strategy: type-guard
Validate before calling
// At the end of your plugin's visitor / transformer:
import { isFrontmatterValid } from '@astrojs/internal-helpers/frontmatter';
const astro = (ctx.data.astro ??= {});
if (!isFrontmatterValid(astro.frontmatter)) {
astro.frontmatter = {}; // restore a valid bag before the pipeline reads it back
} Type guard
import { isFrontmatterValid } from '@astrojs/internal-helpers/frontmatter';
function hasValidFrontmatter(data: any): data is { astro: { frontmatter: Record<string, unknown> } } {
return isFrontmatterValid(data?.astro?.frontmatter);
} Try / catch
try {
await compileMdx(source, { rehypePlugins: [myPlugin] });
} catch (err) {
if (err instanceof Error && err.message.includes('invalid frontmatter')) {
throw new Error(`myPlugin produced invalid frontmatter: ${err.message}`);
}
throw err;
} Prevention
- Prefer Object.assign on the seeded frontmatter over replacing the reference
- If replacing ctx.data.astro, always spread the old bag first
- Add a fixture .mdx file to your plugin's tests and compile it through the real pipeline
When it happens
Trigger: A plugin in the MDX pipeline assigns ctx.data.astro.frontmatter = null/undefined, assigns a primitive or array, replaces ctx.data.astro wholesale without carrying frontmatter over, or deletes the property after it was seeded.
Common situations: Porting a remark plugin written for plain .md files (where data.astro is not seeded) into the MDX pipeline; plugins that sanitize or rebuild file.data and drop the astro key; plugins written against older @astrojs/mdx versions that always mutated the seeded object instead of replacing it.
Related errors
- [ ] Astro now supports MDX! Support for components in ".md"…
- [MDX] A remark or rehype plugin attempted to inject invalid…
- [@astrojs/mdx] Importing `getContainerRenderer` from…
- not applied.
- not applied.
AI-assisted analysis of withastro/astro@3578d45d34 (2026-08-18).
Data as JSON: /api/errors/f13a8208782538f2.
Report an issue: GitHub.
Appendix: source
Thrown at packages/integrations/mdx/src/satteri/index.ts:150
? {}
: { smartPunctuation: mdxOptions.smartypants !== false }),
},
fileURL: pathToFileURL(filePath),
jsxImportSource: 'astro',
elementAttributeNameCase: 'html',
data: { astro: astroData },
});
let compiled = mdxResult.code;
// Read the returned bag, not the seeded reference, so a plugin that replaces it is honored.
const astro = mdxResult.data.astro;
const headings = astro?.headings ?? [];
const astroMetadata = mdxResult.data.__astroMetadata ?? createDefaultAstroMetadata();
// Plugins may have mutated frontmatter; emit the final value.
const resolvedFrontmatter = astro?.frontmatter;
if (!resolvedFrontmatter || !isFrontmatterValid(resolvedFrontmatter)) {
throw new Error(
'[MDX] A Sätteri plugin attempted to inject invalid frontmatter. Ensure `ctx.data.astro.frontmatter` is a valid object that is not `null` or `undefined`.',
);
}
compiled = compiled.replace(/^export default MDXContent;\s*$/m, '');
if (imageImportInfo.hasImages) {
// `vite-plugin-mdx-postprocess` wraps Content to map
// `astro-image` → `components.img ?? __AstroImage__`, so a user's
// `export const components = { img: ... }` override is honored.
compiled += `\nimport { Image as ${ASTRO_IMAGE_IMPORT} } from "astro:assets";`;
for (const [src, importName] of imageImportInfo.importedImages) {
compiled += `\nimport ${importName} from ${JSON.stringify(src)};`;
}
compiled += `\nexport const ${USES_ASTRO_IMAGE_FLAG} = true;`;
}
compiled += `\nexport const frontmatter = ${JSON.stringify(resolvedFrontmatter)};`;View on GitHub (pinned to 3578d45d34)