{"record":{"id":"f13a8208782538f2","repo":"withastro/astro","slug":"mdx-a-s-tteri-plugin-attempted-to-inject-invalid","errorCode":null,"errorMessage":"[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`.","messagePattern":"\\[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`\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/integrations/mdx/src/satteri/index.ts","lineNumber":150,"sourceCode":"\t\t\t\t\t\t? {}\n\t\t\t\t\t\t: { smartPunctuation: mdxOptions.smartypants !== false }),\n\t\t\t\t},\n\t\t\t\tfileURL: pathToFileURL(filePath),\n\t\t\t\tjsxImportSource: 'astro',\n\t\t\t\telementAttributeNameCase: 'html',\n\t\t\t\tdata: { astro: astroData },\n\t\t\t});\n\t\t\tlet compiled = mdxResult.code;\n\n\t\t\t// Read the returned bag, not the seeded reference, so a plugin that replaces it is honored.\n\t\t\tconst astro = mdxResult.data.astro;\n\t\t\tconst headings = astro?.headings ?? [];\n\t\t\tconst astroMetadata = mdxResult.data.__astroMetadata ?? createDefaultAstroMetadata();\n\n\t\t\t// Plugins may have mutated frontmatter; emit the final value.\n\t\t\tconst resolvedFrontmatter = astro?.frontmatter;\n\t\t\tif (!resolvedFrontmatter || !isFrontmatterValid(resolvedFrontmatter)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'[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`.',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tcompiled = compiled.replace(/^export default MDXContent;\\s*$/m, '');\n\n\t\t\tif (imageImportInfo.hasImages) {\n\t\t\t\t// `vite-plugin-mdx-postprocess` wraps Content to map\n\t\t\t\t// `astro-image` → `components.img ?? __AstroImage__`, so a user's\n\t\t\t\t// `export const components = { img: ... }` override is honored.\n\t\t\t\tcompiled += `\\nimport { Image as ${ASTRO_IMAGE_IMPORT} } from \"astro:assets\";`;\n\t\t\t\tfor (const [src, importName] of imageImportInfo.importedImages) {\n\t\t\t\t\tcompiled += `\\nimport ${importName} from ${JSON.stringify(src)};`;\n\t\t\t\t}\n\t\t\t\tcompiled += `\\nexport const ${USES_ASTRO_IMAGE_FLAG} = true;`;\n\t\t\t}\n\n\t\t\tcompiled += `\\nexport const frontmatter = ${JSON.stringify(resolvedFrontmatter)};`;","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/withastro/astro/blob/3578d45d34226d63cff3d261c971c221de6794d2/packages/integrations/mdx/src/satteri/index.ts#L132-L168","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (inside a rehype/recma plugin)\nctx.data.astro.frontmatter = null;\n// after\nObject.assign(ctx.data.astro.frontmatter, { title: 'Hello' });","handlingStrategy":"type-guard","validationCode":"// At the end of your plugin's visitor / transformer:\nimport { isFrontmatterValid } from '@astrojs/internal-helpers/frontmatter';\nconst astro = (ctx.data.astro ??= {});\nif (!isFrontmatterValid(astro.frontmatter)) {\n  astro.frontmatter = {}; // restore a valid bag before the pipeline reads it back\n}","typeGuard":"import { isFrontmatterValid } from '@astrojs/internal-helpers/frontmatter';\nfunction hasValidFrontmatter(data: any): data is { astro: { frontmatter: Record<string, unknown> } } {\n  return isFrontmatterValid(data?.astro?.frontmatter);\n}","tryCatchPattern":"try {\n  await compileMdx(source, { rehypePlugins: [myPlugin] });\n} catch (err) {\n  if (err instanceof Error && err.message.includes('invalid frontmatter')) {\n    throw new Error(`myPlugin produced invalid frontmatter: ${err.message}`);\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["mdx","rehype-plugin","frontmatter","saetteri"],"backgroundTag":"plugin-hook-invalid-data","analyzedSha":"3578d45d34226d63cff3d261c971c221de6794d2","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}