withastro/astro · error · Error

[MDX] A Sätteri plugin attempted to inject invalid frontmatt

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

After compiling MDX through the Sätteri pipeline, the integration reads `mdxResult.data.astro.frontmatter` (the final, post-plugin value) and re-validates it. If the resolved frontmatter is missing or not a valid JSON object, it throws a plain `Error` attributing the failure to a Sätteri plugin. This catches plugins that replaced or nulled the frontmatter bag during compilation.

Source

Thrown at packages/integrations/mdx/src/satteri/index.ts:151

						? {}
						: { 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 d081033d5f)

Solutions

  1. Make any plugin that touches frontmatter always leave `ctx.data.astro.frontmatter` as a plain JSON object.
  2. If you replace `data.astro`, copy `frontmatter` over: `{ ...newAstro, frontmatter: existing.frontmatter ?? {} }`.
  3. Verify plugin order so a later plugin doesn't null frontmatter after earlier setup.
  4. Default frontmatter to `{}` rather than allowing it to become undefined.

Example fix

// before
file.data.astro = { ...newData }; // drops frontmatter

// after
file.data.astro = { ...newData, frontmatter: file.data.astro?.frontmatter ?? {} };
Defensive patterns

Strategy: validation

Validate before calling

function isValidFrontmatter(fm: unknown): boolean {
  return fm !== null && fm !== undefined && typeof fm === 'object' && !Array.isArray(fm);
}

Type guard

function isPlainFrontmatter(v: unknown): v is Record<string, unknown> {
  return v !== null && typeof v === 'object' && !Array.isArray(v);
}

Prevention

When it happens

Trigger: A remark/rehype/Sätteri plugin assigns `ctx.data.astro.frontmatter` to null/undefined or a non-serializable value during compile. A plugin that swaps out the whole `data.astro` object without preserving `frontmatter`. Async frontmatter mutation that resolves after the read.

Common situations: Custom Sätteri/remark plugin overriding frontmatter. Plugin compatibility issue across MDX versions. Frontmatter intentionally cleared in a transform.

Related errors


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