--- id: plugins description: Using MDX plugins to expand Docusaurus Markdown functionalities slug: /markdown-features/plugins --- # MDX Plugins Sometimes, you may want to extend or tweak your Markdown syntax. For example: - How do I embed youtube videos using the image syntax (`![](https://youtu.be/yKNxeF4KMsY)`)? - How do I style links that are on their own lines differently, e.g., as a social card? - How do I make every page start with a copyright notice? And the answer is: create an MDX plugin! MDX has a built-in [plugin system](https://mdxjs.com/advanced/plugins/) that can be used to customize how the Markdown files will be parsed and transformed to JSX. There are three typical use-cases of MDX plugins: - Using existing [remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins) or [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins); - Creating remark/rehype plugins to transform the elements generated by existing MDX syntax; - Creating remark/rehype plugins to introduce new syntaxes to MDX. If you play with the [MDX playground](https://mdxjs.com/playground/), you would notice that the MDX transpilation has two intermediate steps: Markdown AST (MDAST), and Hypertext AST (HAST), before arriving at the final JSX output. MDX plugins also come in two forms: - **[Remark](https://github.com/remarkjs/remark/)**: processes the Markdown AST. - **[Rehype](https://github.com/rehypejs/rehype/)**: processes the Hypertext AST. :::tip Use plugins to introduce shorter syntax for the most commonly used JSX elements in your project. The [admonition](./markdown-features-admonitions.mdx) syntax that we offer is also generated by a Remark plugin, and you could do the same for your own use case. ::: ## Default plugins {#default-plugins} Docusaurus injects [some default Remark plugins](https://github.com/facebook/docusaurus/tree/main/packages/docusaurus-mdx-loader/src/remark) during Markdown processing. These plugins would: - Generate the table of contents; - Add anchor links to each heading; - Transform images and links to `require()` calls. - … These are all typical use-cases of Remark plugins, which can also be a source of inspiration if you want to implement your own plugin. ## Installing plugins {#installing-plugins} An MDX plugin is usually an npm package, so you install them like other npm packages using npm. Take the [math plugins](./markdown-features-math-equations.mdx) as an example. ```bash npm2yarn npm install --save remark-math@5 rehype-katex@6 ```
How are remark-math and rehype-katex different? In case you are wondering how Remark and Rehype are different, here is a good example. `remark-math` operates on the Markdown AST, where it sees text like `$...$`, and all it does is transform that to the JSX `...` without doing too much with the content. This decouples the extraction of math formulae from their rendering, which means you can swap $\KaTeX$ out with other math renderers, like MathJax (with [`rehype-mathjax`](https://github.com/remarkjs/remark-math/tree/main/packages/rehype-mathjax)), just by replacing the Rehype plugin. Next, the `rehype-katex` operates on the Hypertext AST where everything has been converted to HTML-like tags already. It traverses all the elements with `math` class and uses $\KaTeX$ to parse and render the content to actual HTML.
:::warning Many official Remark/Rehype plugins are [**ES Modules only**](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c), a JavaScript module system, which Docusaurus supports. We recommend using an [**ES Modules**](https://flaviocopes.com/es-modules/) config file to make it easier to import such packages. ::: Next, import your plugins and add them to the plugin options through plugin or preset config in `docusaurus.config.js`: ```js title="docusaurus.config.js" // highlight-start import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; // highlight-end // highlight-start export default { presets: [ [ '@docusaurus/preset-classic', { docs: { path: 'docs', // highlight-start remarkPlugins: [remarkMath], rehypePlugins: [rehypeKatex], // highlight-end }, }, ], ], }; ```
Using a [**CommonJS**](https://nodejs.org/api/modules.html#modules-commonjs-modules) config file? If you decide to use a CommonJS config file, it is possible to load those ES module plugins thanks to dynamic imports and an async config creator function: ```js title="docusaurus.config.js" // highlight-start module.exports = async function createConfigAsync() { // highlight-end return { presets: [ [ '@docusaurus/preset-classic', { docs: { path: 'docs', // highlight-start remarkPlugins: [(await import('remark-math')).default], rehypePlugins: [(await import('rehype-katex')).default], // highlight-end }, }, ], ], }; }; ```
## Configuring plugins {#configuring-plugins} Some plugins can be configured and accept their own options. In that case, use the `[plugin, pluginOptions]` syntax, like this: ```js title="docusaurus.config.js" import rehypeKatex from 'rehype-katex'; export default { presets: [ [ '@docusaurus/preset-classic', { docs: { rehypePlugins: [ // highlight-start [rehypeKatex, {strict: false}], // highlight-end ], }, }, ], ], }; ``` You should check your plugin's documentation for the options it supports. ## Creating new rehype/remark plugins {#creating-new-rehyperemark-plugins} If there isn't an existing package that satisfies your customization need, you can create your own MDX plugin. :::note The writeup below is **not** meant to be a comprehensive guide to creating a plugin, but just an illustration of how to make it work with Docusaurus. Visit the [Remark](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#create-plugins) or [Rehype](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#create-plugins) documentation for a more in-depth explanation of how they work. ::: For example, let's make a plugin that visits every `h2` heading and adds a `Section X. ` prefix. First, create your plugin source file anywhere—you can even publish it as a separate npm package and install it like explained above. We would put ours at `src/remark/section-prefix.js`. A remark/rehype plugin is just a function that receives the `options` and returns a `transformer` that operates on the AST. ```js "src/remark/section-prefix.js" import {visit} from 'unist-util-visit'; const plugin = (options) => { const transformer = async (ast) => { let number = 1; visit(ast, 'heading', (node) => { if (node.depth === 2 && node.children.length > 0) { node.children.unshift({ type: 'text', value: `Section ${number}. `, }); number++; } }); }; return transformer; }; export default plugin; ``` You can now import your plugin in `docusaurus.config.js` and use it just like an installed plugin! ```js title="docusaurus.config.js" // highlight-next-line import sectionPrefix from './src/remark/section-prefix'; export default { presets: [ [ '@docusaurus/preset-classic', { docs: { // highlight-next-line remarkPlugins: [sectionPrefix], }, }, ], ], }; ``` :::tip The `transformer` has a second parameter [`vfile`](https://github.com/vfile/vfile) which is useful if you need to access the current Markdown file's path. ```js const plugin = (options) => { const transformer = async (ast, vfile) => { ast.children.unshift({ type: 'text', value: `The current file path is ${vfile.path}`, }); }; return transformer; }; ``` Our `transformImage` plugin uses this parameter, for example, to transform relative image references to `require()` calls. ::: :::note The default plugins of Docusaurus would operate before the custom remark plugins, and that means the images or links have been converted to JSX with `require()` calls already. For example, in the example above, the table of contents generated is still the same even when all `h2` headings are now prefixed by `Section X.`, because the TOC-generating plugin is called before our custom plugin. If you need to process the MDAST before the default plugins do, use the `beforeDefaultRemarkPlugins` and `beforeDefaultRehypePlugins`. ```js title="docusaurus.config.js" export default { presets: [ [ '@docusaurus/preset-classic', { docs: { // highlight-next-line beforeDefaultRemarkPlugins: [sectionPrefix], }, }, ], ], }; ``` This would make the table of contents generated contain the `Section X.` prefix as well. :::