How Astro Integrates Markdown and MDX with Remark and Rehype Plugins

Astro processes Markdown and MDX through a unified pipeline built on remark and rehype, allowing custom plugins via astro.config.mjs while providing built-in syntax highlighting, image optimization, and frontmatter handling.

The withastro/astro repository treats both .md and .mdx files as first-class page formats, transforming them into renderable components through a shared processing layer. Understanding how Astro integrates Markdown and MDX with remark and rehype plugins helps you customize content transformation while leveraging Astro's asset pipeline and runtime features.

Core Markdown Processing Pipeline

When you import or route to a Markdown file, Astro's Vite plugin system kicks in to transform raw content into executable JavaScript.

The Vite Plugin Entry Point

The astro:markdown Vite plugin, located in packages/astro/src/vite-plugin-markdown/index.ts, handles file loading. It first splits frontmatter from content using safeParseFrontmatter, then lazily initializes a markdown processor via createMarkdownProcessor from @astrojs/markdown-remark (packages/markdown/remark/src/index.ts).

Plugin Assembly Order

The processor constructs a unified pipeline with the following sequence:

  1. Built-in remark plugins – GitHub Flavored Markdown (remark-gfm) and smartypants (remark-smartypants) unless disabled
  2. User remark plugins – loaded from markdown.remarkPlugins in your config via loadPlugins
  3. remarkCollectImages – internal plugin (packages/markdown/remark/src/remark-collect-images.ts) that records image paths for Astro's asset handling
  4. remark-rehype – converts the remark AST to rehype, applying any remarkRehype options you configure
  5. Syntax highlighting – either rehype-shiki or rehype-prism based on markdown.syntaxHighlight settings
  6. User rehype plugins – loaded from markdown.rehypePlugins
  7. Astro-specific rehype pluginsrehypeImages (injects Astro Image components) and rehypeHeadingIds (adds id attributes for anchor links)

The pipeline finalizes with rehype-stringify, allowing raw HTML through rehypeRaw. The result contains the HTML string and metadata including headings and collected image paths.

Virtual Module Generation

After processing, the Vite plugin emits a virtual module exporting:

  • frontmatter, file, and url metadata
  • Helper functions: rawContent(), compiledContent(), and getHeadings()
  • A Content component that renders the HTML (or a layout component if frontmatter.layout is specified)

MDX Integration Architecture

MDX extends the same unified ecosystem but swaps the parser for @mdx-js/mdx, enabling JSX component imports within Markdown.

The MDX Processor

The integration lives in packages/integrations/mdx/src/plugins.ts. The createMdxProcessor function calls createProcessor from @mdx-js/mdx with:

  • Remark plugins – default GFM and smartypants, plus mdxOptions.remarkPlugins and the built-in remarkCollectImages
  • Rehype plugins – default Astro plugins (rehypeMetaString, rehypeRaw) plus:
    • Syntax highlighters (rehypeShiki or rehypePrism) matching the Markdown pipeline
    • rehypeImageToComponent – transforms <img> tags into Astro's optimized <Image> component
    • rehypeInjectHeadingsExport – exposes headings to runtime via getHeadings()
    • rehypeApplyFrontmatterExport – makes frontmatter available as JavaScript exports
    • User plugins from mdxOptions.rehypePlugins
  • remarkRehypeOptions – passed directly to remark-rehype

Unlike standard Markdown processing, MDX returns a JSX/TSX AST that Vite bundles as a component, allowing seamless embedding of React, Vue, Svelte, or Solid components.

Configuring Remark and Rehype Plugins

Astro exposes the unified ecosystem through astro.config.mjs with a TypeScript-validated configuration shape defined in packages/astro/src/types/public/config.ts.

Basic Configuration Example

// astro.config.mjs
import { defineConfig } from 'astro/config';
import remarkToc from 'remark-toc';
import rehypeHighlight from 'rehype-highlight';
import remarkEmoji from 'remark-emoji';

export default defineConfig({
  markdown: {
    // Toggle built-ins
    gfm: true,
    smartypants: false,
    
    // Remark plugins accept functions or [plugin, options] tuples
    remarkPlugins: [
      remarkEmoji, // Transforms :smile: to emoji
      [remarkToc, { heading: 'contents', maxDepth: 3 }],
    ],
    
    // Configure remark-to-rehype bridge
    remarkRehype: {
      allowDangerousHtml: true,
      footnoteLabel: 'Notes',
    },
    
    // Rehype plugins transform HTML AST
    rehypePlugins: [
      rehypeHighlight,
    ],
    
    syntaxHighlight: 'shiki', // or 'prism'
  },
});

MDX-Specific Options

When using the MDX integration (automatically added or manually imported as @astrojs/mdx), options follow the same structure under mdxOptions:

// astro.config.mjs
export default defineConfig({
  integrations: [
    // MDX integration inherits markdown defaults but can override
  ],
});

Plugin Loading and Security Mechanisms

Both pipelines use loadPlugins from packages/markdown/remark/src/load-plugins.ts to resolve plugin references. This utility handles:

  • String names – dynamically imports packages relative to your project root
  • Function references – uses plugin functions directly
  • Async resolution – runs Promise.all to support async imports while preserving plugin order

User plugins always execute after built-in defaults, following the "user overrides defaults" principle.

Frontmatter Injection Security

After remark or rehype plugins mutate frontmatter, Astro validates the result in vite-plugin-markdown/index.ts (lines 95-98). The isFrontmatterValid check ensures plugins inject only plain objects. If a plugin attempts to inject invalid frontmatter, Astro throws an InvalidFrontmatterInjectionError with a descriptive message, preventing serialization attacks or malformed metadata propagation.

Summary

  • Astro processes .md files through packages/astro/src/vite-plugin-markdown/index.ts and MDX through packages/integrations/mdx/src/plugins.ts, both leveraging the unified remark/rehype ecosystem.
  • The default pipeline includes GFM, smartypants, syntax highlighting (Shiki or Prism), and Astro-specific plugins for image optimization and heading ID injection.
  • Configure custom plugins via astro.config.mjs using markdown.remarkPlugins and markdown.rehypePlugins, which accept both function references and [plugin, options] tuples.
  • MDX supports the same plugin architecture while additionally exposing getHeadings() and frontmatter exports through specialized rehype plugins.
  • The loadPlugins utility resolves string plugin names dynamically, and Astro validates frontmatter mutations to prevent injection attacks.

Frequently Asked Questions

How do I add a table of contents to Markdown files in Astro?

Use the remark-toc plugin in your astro.config.mjs. Add it to the remarkPlugins array with optional configuration for heading depth and label. Since remark plugins run before HTML generation, they modify the Markdown AST to inject the TOC structure before rehype converts it to HTML.

Can I use the same remark plugins for both Markdown and MDX?

Yes. The MDX integration inherits the default remark plugins (GFM, smartypants) and the remarkCollectImages plugin from the core Markdown configuration. You can extend MDX-specific options via mdxOptions.remarkPlugins and mdxOptions.rehypePlugins in your integration settings, or rely on the shared defaults.

Why are my rehype plugins not affecting MDX files?

MDX uses a separate processor pipeline defined in packages/integrations/mdx/src/plugins.ts. While it includes many default Astro rehype plugins automatically, you must explicitly add custom rehype plugins to mdxOptions.rehypePlugins in your configuration. The MDX pipeline also includes additional Astro-specific plugins like rehypeImageToComponent that transform HTML elements into Astro components.

How does Astro handle images referenced in Markdown content?

The built-in remarkCollectImages plugin (in packages/markdown/remark/src/remark-collect-images.ts) traverses the remark AST to identify image paths during processing. For MDX, rehypeImageToComponent further transforms standard <img> tags into Astro's optimized <Image> component during the rehype phase. This allows Astro's asset pipeline to hash, optimize, and bundle referenced images automatically.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →