withastro/astro · error · Error
`markdown.remarkPlugins`, `markdown.rehypePlugins`, and `mar
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
When Sätteri is the default Markdown processor (so `@astrojs/markdown-remark` is not bundled), Astro lazily imports that package only if the user configured `markdown.remarkPlugins`, `markdown.rehypePlugins`, or `markdown.remarkRehype`. If the dynamic `import('@astrojs/markdown-remark')` rejects, Astro throws a plain `Error` instructing the user to install it. This keeps the default install lean while still supporting the unified-based plugin pipeline on demand.
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 d081033d5f)
Solutions
- Install the package: `npm install @astrojs/markdown-remark`.
- If you don't need unified plugins, remove the three `markdown.*` config keys to stay on the default Sätteri processor.
- After installing, ensure your remark/rehype plugins are compatible with the installed `@astrojs/markdown-remark` major version.
Example fix
// before — astro.config.mjs (package missing)
import { defineConfig } from 'astro/config';
export default defineConfig({
markdown: { remarkPlugins: [remarkToc] },
});
// after
npm install @astrojs/markdown-remark Defensive patterns
Strategy: validation
Validate before calling
const has = (a) => Array.isArray(a) ? a.length > 0 : a && Object.keys(a).length > 0;
if (has(md.remarkPlugins) || has(md.rehypePlugins) || Object.keys(md.remarkRehype ?? {}).length) {
require.resolve('@astrojs/markdown-remark'); // throws if missing
} Type guard
function needsMarkdownRemark(md) {
return Boolean((md.remarkPlugins?.length) || (md.rehypePlugins?.length) || (md.remarkRehype && Object.keys(md.remarkRehype).length));
} Prevention
- When adopting remark/rehype plugins, install @astrojs/markdown-remark explicitly.
- Pin the package version compatible with your Astro major.
- Document the dependency in the project README.
When it happens
Trigger: Configuring any of `markdown.remarkPlugins`, `markdown.rehypePlugins`, or `markdown.remarkRehype` in `astro.config` without having `@astrojs/markdown-remark` installed. The `validate.ts` check triggers the dynamic import, which fails.
Common situations: Migrating from a previous Astro version where `@astrojs/markdown-remark` was a default dependency; copying remark/rehype plugin config from a tutorial into a fresh project on the new default processor; a pnpm setup where the package isn't hoisted.
Related errors
- CacheProviderNotFound
- The markdown processor "${processor.name}" does not provide
- CannotOptimizeSvg
- RemoteImageNotAllowed
- Unknown error parsing tsconfig.json or jsconfig.json. Could
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/714b485d74ab81fa.
Report an issue: GitHub.