unplugin-vue-markdown makes Markdown a Vue component in either direction
Compile Markdown to Vue component
At a glance
- What is it?
- unplugin-vue-markdown is an MIT licensed unplugin from the unplugin organisation that compiles Markdown into Vue components and lets Vue components appear inside Markdown, using the same transformation as VitePress. It works across Vite, Webpack and Vue CLI, with markdown-exit doing the parsing underneath.
- Who is it for?
- unplugin-vue-markdown suits Vue projects that want docs pages, READMEs or content files to be real components, and that want to drop live Vue widgets into prose without giving up Markdown. It does not suit a project that needs per-file Markdown dialects or a full site generator, and it does not suit anyone still on a Node version below 22.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 60 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The plugin is one transform behind two Vite plugins
The mechanism is a build time transform, and the wiring is short. The Vue plugin is given an include pattern that also matches Markdown files, and the Markdown plugin is added alongside it, which is what lets a .md file be compiled into a component at all. The README states the same transformation as VitePress is used, and the package is powered by unplugin, which is why the same code serves Vite, Webpack, Vue CLI and more.
// vite.config.ts
import Vue from '@vitejs/plugin-vue'
import Markdown from 'unplugin-vue-markdown/vite'
export default defineConfig({
plugins: [
Vue({
include: [/\.vue$/, /\.md$/], // <-- allows Vue to compile Markdown files
}),
Markdown({ /* options */ }),
],
})Two details in that snippet carry the whole integration. The include array on the Vue plugin is what widens the file matching, and the Markdown plugin comes second. The comment in the source flags the include change explicitly, which tells you this is the step people miss. The package exports separate entry points per bundler, including ./vite, ./webpack, ./esbuild, ./rollup and ./rspack, and the peer dependency range on vite runs from version 2 up to 8, so the plugin is not tied to one bundler generation.
Markdown in, components out, and components back into Markdown
The two directions are the reason to use this rather than a documentation plugin. First, a Markdown file can be imported as a Vue component, so a README becomes something you can render inside a page.
<template>
<HelloWorld />
</template>
<script>
import HelloWorld from './README.md'
export default {
components: {
HelloWorld,
},
}
</script>Second, a Vue component can be used inside Markdown, written as a tag in the prose, and the documentation gives the example of a counter with an init prop. The component can be registered globally with app.component in the application entry point, or registered locally inside a script setup block, and the README also points at unplugin-vue-components for on demand registration. The circularity is the point. A content file can hold a live widget rather than a screenshot of one, and the widget keeps working because it is a component in the same app rather than an embedded frame.
Frontmatter lands in instance data and in props
Frontmatter is parsed and injected into the Vue instance data under a frontmatter field, and the same values are also passed to the wrapper component's props if the wrapperComponent option is set. A document with a name in its frontmatter can therefore be rendered with that value in the body.
---
name: My Cool App
---
# Hello World
This is {{frontmatter.name}}The rendered result is a heading and a paragraph containing the value. Two consequences follow from the double destination. Because the data is injected into the instance, the value is available to any expression in the document, which is how the interpolation in the example works. Because it also reaches the wrapper component as props, the wrapper can shape the whole page around the metadata, for example to render a title block. The implementation uses the @mdit-vue/plugin-frontmatter package for this, alongside @mdit-vue/plugin-component for the component side, both at version 3.0.2 or newer, and @mdit-vue/types carries the shared types.
Document head needs a separate package
Managing the document head and meta information is not built in. The README says you would need to install @unhead/vue and do some setup, and the options then include a headEnabled flag that turns the behaviour on.
npm i @unhead/vue// src/main.js
import { createHead } from '@unhead/vue/client' // <--
import { createApp } from 'vue'
const app = createApp(App)
const head = createHead() // <--
app.use(head) // <--Once that is in place, frontmatter can drive the head, with a document carrying a title and a meta list containing a description name and content. The README points at the @unhead/vue documentation for the full option set rather than duplicating it. This is a reasonable dependency split, since document head management is a solved problem with its own package, but it does mean the minimal install of the plugin gives you Markdown components and nothing about titles, descriptions or social metadata. If your use case is a documentation site, that is a second package and a second configuration to own.
markdown-exit is the parser, and markdown-it plugins still work
Under the hood the plugin uses markdown-exit, described as a TypeScript rewrite of markdown-it with built-in async rendering, and the README states that markdown-it plugins are compatible. The options object passes default options straight to markdown-exit, and a markdownSetup function hands you the instance so you can apply custom settings and plugins, with markdown-it-anchor and markdown-it-prism given as the examples. Wrapper classes for the generated div are set with wrapperClasses, and the README notes that the old option names markdownItOptions, markdownItUses and markdownItSetup are deprecated but still functional.
markdownSetup(md) {
// for example
md.use(MarkdownItAnchor)
md.use(MarkdownItPrism)
}The compatibility claim is the important part. A team that already has a markdown-it plugin set, an anchor scheme or a syntax highlighter configured can carry that configuration over rather than rebuilding it, and the async rendering built into the rewrite is what allows a component to load content on its own schedule. The deprecation of the older option names is a migration note, and the fact that they still work means an upgrade does not break an existing config on the day it lands.
Where the plugin does not reach
There are boundaries, and they are ordinary ones. This is a component compiler, not a documentation site generator, so there is no routing, no sidebar, no search and no theme. The routing integration exists only as an example of using it with vite-plugin-pages, where Markdown files under ./src/pages are given the .md extension in the Pages plugin and become routes, and that arrangement is one plugin working alongside another rather than a feature of this package. The Vue CLI configuration is also where you see the rough edges, because the documented setup sets parallel to false to disable thread-loader, with a comment saying that will cause errors and that the root cause is still being investigated. A plugin that requires a bundler feature to be switched off is a plugin that has not yet sorted out interaction with that bundler's threading model. On the tooling side the package requires Node 22 or newer, uses pnpm as its package manager at version 11.23.0, and the build is done with tsdown while the tests run under vitest.
Editorial conclusion
unplugin-vue-markdown suits Vue projects that want docs pages, READMEs or content files to be real components, and that want to drop live Vue widgets into prose without giving up Markdown. It does not suit a project that needs per-file Markdown dialects or a full site generator, and it does not suit anyone still on a Node version below 22. Before adopting it, add Markdown to your Vite plugins after the Vue plugin and widen the Vue include pattern to .md files, and check the frontmatter handling, since frontmatter values are injected into the instance data and also passed to the wrapper component's props.
Frequently asked questions
How do I install unplugin-vue-markdown?
Install it with npm i unplugin-vue-markdown. The package requires Node 22 or newer and is published as an ES module with per bundler entry points such as unplugin-vue-markdown/vite and unplugin-vue-markdown/webpack.
How do I use a Vue component inside a Markdown file?
Write the component as a tag in the Markdown, for example a counter with an init prop, then register it globally with app.component in the entry point or locally inside a script setup block, or let unplugin-vue-components register it on demand.
What happens to frontmatter in unplugin-vue-markdown?
Frontmatter is parsed and injected into the Vue instance data under a frontmatter field, and it is also passed to the wrapper component's props when the wrapperComponent option is set.
Does unplugin-vue-markdown support document head and meta?
Not on its own. The documentation says you need to install @unhead/vue, set up createHead in the application entry point, and enable the headEnabled option in the plugin.
Which Markdown parser does unplugin-vue-markdown use?
It uses markdown-exit, described as a TypeScript rewrite of markdown-it with built-in async rendering, and markdown-it plugins are compatible with it. The old option names markdownItOptions, markdownItUses and markdownItSetup are deprecated but still work.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/unplugin-unplugin-vue-markdown)Was this useful?