{"record":{"id":"714b485d74ab81fa","repo":"withastro/astro","slug":"markdown-remarkplugins-markdown-rehypeplugins","errorCode":null,"errorMessage":"`markdown.remarkPlugins`, `markdown.rehypePlugins`, and `markdown.remarkRehype` run on the `unified` processor from `@astrojs/markdown-remark`, which is no longer installed by default now that Sätteri is the default Markdown processor. Install it with:\n  npm install @astrojs/markdown-remark","messagePattern":"`markdown\\.remarkPlugins`, `markdown\\.rehypePlugins`, and `markdown\\.remarkRehype` run on the `unified` processor from `@astrojs/markdown-remark`, which is no longer installed by default now that Sätteri is the default Markdown processor\\. Install it with:\n  npm install @astrojs/markdown-remark","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/astro/src/core/config/validate.ts","lineNumber":82,"sourceCode":"\tif (!md) return;\n\n\tconst remarkPlugins = md.remarkPlugins ?? [];\n\tconst rehypePlugins = md.rehypePlugins ?? [];\n\tconst remarkRehype = md.remarkRehype ?? {};\n\tif (\n\t\tremarkPlugins.length === 0 &&\n\t\trehypePlugins.length === 0 &&\n\t\tObject.keys(remarkRehype).length === 0\n\t) {\n\t\treturn;\n\t}\n\n\tlet unified: typeof import('@astrojs/markdown-remark').unified;\n\tlet isUnifiedProcessor: typeof import('@astrojs/markdown-remark').isUnifiedProcessor;\n\ttry {\n\t\t({ unified, isUnifiedProcessor } = await import('@astrojs/markdown-remark'));\n\t} catch {\n\t\tthrow new Error(\n\t\t\t'`markdown.remarkPlugins`, `markdown.rehypePlugins`, and `markdown.remarkRehype` run on the `unified` processor from `@astrojs/markdown-remark`, which is no longer installed by default now that Sätteri is the default Markdown processor. Install it with:\\n  npm install @astrojs/markdown-remark',\n\t\t);\n\t}\n\n\tconst current = md.processor;\n\tif (!current || isUnifiedProcessor(current)) {\n\t\t// `createRenderer` reads `.options.*` at render time, so in-place mutation propagates.\n\t\tconst target = (current ?? (md.processor = unified())) as ReturnType<typeof unified>;\n\t\tconst counts = migratedLegacyPluginCounts.get(target.options) ?? { remark: 0, rehype: 0 };\n\t\t// Only push entries past what we've already folded; mergeConfig appends, so they sit at the tail.\n\t\tif (remarkPlugins.length > counts.remark) {\n\t\t\ttarget.options.remarkPlugins.push(...remarkPlugins.slice(counts.remark));\n\t\t}\n\t\tif (rehypePlugins.length > counts.rehype) {\n\t\t\ttarget.options.rehypePlugins.push(...rehypePlugins.slice(counts.rehype));\n\t\t}\n\t\t// `remarkRehype` is an object, so Object.assign is idempotent for unchanged keys\n\t\t// and absorbs any new keys integrations add.","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/withastro/astro/blob/52e6c34790cc8ac4e69e6135ace06049867e5c4a/packages/astro/src/core/config/validate.ts#L64-L100","documentation":"In Astro versions where Sätteri is the default Markdown processor, `@astrojs/markdown-remark` is no longer installed as a default dependency. If your config sets `markdown.remarkPlugins`, `markdown.rehypePlugins`, or `markdown.remarkRehype`, config validation dynamically imports `@astrojs/markdown-remark` to run those plugins on the `unified` processor; when the import fails, this plain Error is thrown with the exact install command.","triggerScenarios":"Any astro.config containing a non-empty `remarkPlugins`, `rehypePlugins`, or `remarkRehype` under `markdown`, on a project where `@astrojs/markdown-remark` is not an installed dependency (removed during upgrade, or never directly depended on).","commonSituations":"Upgrading an older project that used remark/rehype plugins (TOC, reading-time, syntax highlighters) to a Sätteri-default Astro; monorepo cleanup dropping the previously transitive package.","solutions":["Install the package: `npm install @astrojs/markdown-remark` (message prints this command).","Verify it is in the project's own `dependencies`, not merely hoisted transitively.","If you no longer need unified plugins, remove the legacy `markdown.*` keys and use Sätteri's equivalents instead."],"exampleFix":"# before — throws: @astrojs/markdown-remark is no longer a default dependency\nexport default defineConfig({\n  markdown: { remarkPlugins: [remarkToc] },\n});\n\n# after\nnpm install @astrojs/markdown-remark remark-toc\n\nexport default defineConfig({\n  markdown: { remarkPlugins: [remarkToc] },\n});","handlingStrategy":"validation","validationCode":"// CI gate: legacy markdown plugins require @astrojs/markdown-remark\ntry {\n  await import('@astrojs/markdown-remark');\n} catch {\n  console.error('markdown.remarkPlugins/rehypePlugins need: npm install @astrojs/markdown-remark');\n  process.exit(1);\n}","typeGuard":"// True when the config uses unified-era markdown options\nfunction usesLegacyMarkdownPlugins(config: {\n  markdown?: { remarkPlugins?: unknown[]; rehypePlugins?: unknown[]; remarkRehype?: object };\n}): boolean {\n  const md = config.markdown ?? {};\n  return (\n    (md.remarkPlugins?.length ?? 0) > 0 ||\n    (md.rehypePlugins?.length ?? 0) > 0 ||\n    Object.keys(md.remarkRehype ?? {}).length > 0\n  );\n}","tryCatchPattern":null,"preventionTips":["Keep `@astrojs/markdown-remark` in dependencies for as long as the config references unified plugins.","When upgrading Astro majors, run the app once in CI to catch removed-default-dependency errors.","Plan migration of remark/rehype plugins to the Sätteri processor to drop the dependency."],"tags":["markdown","dependencies","upgrade","configuration"],"backgroundTag":"missing-dependency","analyzedSha":"52e6c34790cc8ac4e69e6135ace06049867e5c4a","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":"2026-08-18T18:48:03.901Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}