# How Astro Integrates Markdown and MDX with Remark and Rehype Plugins

> Unlock Markdown and MDX power in Astro. Learn how Astro uses remark and rehype plugins for custom content processing, syntax highlighting, and image optimization. Integrate seamlessly.

- Repository: [Astro/astro](https://github.com/withastro/astro)
- Tags: internals
- Published: 2026-03-06

---

**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`](https://github.com/withastro/astro/blob/main/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`](https://github.com/withastro/astro/blob/main/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`](https://github.com/withastro/astro/blob/main/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 plugins** – `rehypeImages` (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`](https://github.com/withastro/astro/blob/main/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`](https://github.com/withastro/astro/blob/main/packages/astro/src/types/public/config.ts).

### Basic Configuration Example

```javascript
// 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`:

```javascript
// 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`](https://github.com/withastro/astro/blob/main/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`](https://github.com/withastro/astro/blob/main/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`](https://github.com/withastro/astro/blob/main/packages/astro/src/vite-plugin-markdown/index.ts) and MDX through [`packages/integrations/mdx/src/plugins.ts`](https://github.com/withastro/astro/blob/main/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`](https://github.com/withastro/astro/blob/main/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`](https://github.com/withastro/astro/blob/main/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.