withastro/astro · error · Error
`markdown.remarkPlugins`, `markdown.rehypePlugins`, and…
Error message
`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: npm install @astrojs/markdown-remark
What it means
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.
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.
Example fix
# before — throws: @astrojs/markdown-remark is no longer a default dependency
export default defineConfig({
markdown: { remarkPlugins: [remarkToc] },
});
# after
npm install @astrojs/markdown-remark remark-toc
export default defineConfig({
markdown: { remarkPlugins: [remarkToc] },
}); Defensive patterns
Strategy: validation
Validate before calling
// CI gate: legacy markdown plugins require @astrojs/markdown-remark
try {
await import('@astrojs/markdown-remark');
} catch {
console.error('markdown.remarkPlugins/rehypePlugins need: npm install @astrojs/markdown-remark');
process.exit(1);
} Type guard
// True when the config uses unified-era markdown options
function usesLegacyMarkdownPlugins(config: {
markdown?: { remarkPlugins?: unknown[]; rehypePlugins?: unknown[]; remarkRehype?: object };
}): boolean {
const md = config.markdown ?? {};
return (
(md.remarkPlugins?.length ?? 0) > 0 ||
(md.rehypePlugins?.length ?? 0) > 0 ||
Object.keys(md.remarkRehype ?? {}).length > 0
);
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.
Related errors
- AdapterSupportOutputMismatch
- [astro] `markdown.remarkPlugins`, `markdown.rehypePlugins`…
- [astro]…
- [astro] deprecated. Move onto your processor instead (e.g…
- Be sure to follow the
AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18).
Data as JSON: /api/errors/714b485d74ab81fa.
Report an issue: GitHub.
Appendix: source
Thrown at packages/astro/src/core/config/validate.ts:82
if (!md) return;
const remarkPlugins = md.remarkPlugins ?? [];
const rehypePlugins = md.rehypePlugins ?? [];
const remarkRehype = md.remarkRehype ?? {};
if (
remarkPlugins.length === 0 &&
rehypePlugins.length === 0 &&
Object.keys(remarkRehype).length === 0
) {
return;
}
let unified: typeof import('@astrojs/markdown-remark').unified;
let isUnifiedProcessor: typeof import('@astrojs/markdown-remark').isUnifiedProcessor;
try {
({ unified, isUnifiedProcessor } = await import('@astrojs/markdown-remark'));
} catch {
throw new Error(
'`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',
);
}
const current = md.processor;
if (!current || isUnifiedProcessor(current)) {
// `createRenderer` reads `.options.*` at render time, so in-place mutation propagates.
const target = (current ?? (md.processor = unified())) as ReturnType<typeof unified>;
const counts = migratedLegacyPluginCounts.get(target.options) ?? { remark: 0, rehype: 0 };
// Only push entries past what we've already folded; mergeConfig appends, so they sit at the tail.
if (remarkPlugins.length > counts.remark) {
target.options.remarkPlugins.push(...remarkPlugins.slice(counts.remark));
}
if (rehypePlugins.length > counts.rehype) {
target.options.rehypePlugins.push(...rehypePlugins.slice(counts.rehype));
}
// `remarkRehype` is an object, so Object.assign is idempotent for unchanged keys
// and absorbs any new keys integrations add.View on GitHub (pinned to 52e6c34790)