Skip to content

Examples

All examples write generated Markdown under src/generated/.... Add that generated directory to .gitignore; generated Markdown files are not meant to be committed. Run vitepress build during deployment so the plugin recreates them before the site is built.

txt
docs/src/generated/

Import package README files only

This setup turns each packages/*/README.md into one generated VitePress page.

ts
import { defineConfig } from 'vitepress'
import { externalMarkdown, getExternalMarkdownSidebar } from 'vitepress-plugin-external-markdown'

const externalMarkdownOptions = {
  root: new URL('..', import.meta.url).pathname,
  srcDir: 'src',
  sources: [
    {
      baseDir: '../packages',
      pattern: '*/README.md',
    },
  ],
  outDir: 'generated/packages',
  routeBase: '/generated/packages/',
  resolveMarkdown(ctx) {
    const packageName = ctx.relativePath.replace(/\/README\.md$/u, '')

    return {
      slug: packageName,
      title: ctx.title,
      text: packageName,
      order: packageName,
      sidebar: true,
    }
  },
}

export default defineConfig({
  srcDir: 'src',
  vite: {
    plugins: [externalMarkdown(externalMarkdownOptions)],
  },
  themeConfig: {
    sidebar: {
      '/generated/packages/': getExternalMarkdownSidebar(externalMarkdownOptions),
    },
  },
})

Include README, CHANGELOG, and docs

This setup imports README.md, CHANGELOG.md, and files under package docs/ directories.

ts
const externalMarkdownOptions = {
  root: new URL('..', import.meta.url).pathname,
  srcDir: 'src',
  sources: [
    {
      name: 'package-docs',
      baseDir: '../packages',
      pattern: '{*/README.md,*/CHANGELOG.md,*/docs/**/*.md}',
    },
  ],
  outDir: 'generated/packages',
  routeBase: '/generated/packages/',
  resolveMarkdown(ctx) {
    const slug = ctx.relativePath
      .replace(/\/README\.md$/u, '')
      .replace(/\.md$/u, '')
      .toLowerCase()

    return {
      slug,
      title: ctx.title,
      text: ctx.fileName === 'README.md' ? ctx.relativePath.split('/')[0] : ctx.title,
      order: ctx.relativePath,
      sidebar: true,
    }
  },
}

Copy colocated assets

Use copyAssets when imported Markdown refers to local images or static files. Assets are copied independently from Markdown generation, and Markdown links are not rewritten.

ts
const externalMarkdownOptions = {
  root: new URL('..', import.meta.url).pathname,
  srcDir: 'src',
  sources: [
    {
      baseDir: '../docs-content',
      pattern: '**/*.md',
    },
  ],
  outDir: 'generated/docs',
  routeBase: '/generated/docs/',
  copyAssets: [
    {
      baseDir: '../docs-content',
      pattern: 'images/**/*',
      outDir: 'generated/docs',
    },
    {
      baseDir: '../docs-content',
      pattern: 'public/**/*',
      outDir: '.',
    },
  ],
}

With this configuration, files are materialized like this.

txt
docs/src/generated/docs/images/example.png
docs/src/public/logo.png

This page is generated from docs-content/en/examples.md, and the image below is copied from docs-content/en/assets/ by the same plugin configuration.

Copied asset flow

Add top-level nav items

Return nav: true for files that should also appear in top-level navigation, then use getExternalMarkdownNav().

ts
import {
  externalMarkdown,
  getExternalMarkdownNav,
  getExternalMarkdownSidebar,
} from 'vitepress-plugin-external-markdown'

const externalMarkdownOptions = {
  root: new URL('..', import.meta.url).pathname,
  srcDir: 'src',
  sources: [
    {
      baseDir: '../packages',
      pattern: '*/README.md',
    },
  ],
  outDir: 'generated/packages',
  routeBase: '/generated/packages/',
  resolveMarkdown(ctx) {
    const packageName = ctx.relativePath.replace(/\/README\.md$/u, '')

    return {
      slug: packageName,
      title: ctx.title,
      text: ctx.title,
      order: packageName,
      sidebar: true,
      nav: packageName === 'core',
    }
  },
}

export default defineConfig({
  srcDir: 'src',
  vite: {
    plugins: [externalMarkdown(externalMarkdownOptions)],
  },
  themeConfig: {
    nav: getExternalMarkdownNav(externalMarkdownOptions),
    sidebar: {
      '/generated/packages/': getExternalMarkdownSidebar(externalMarkdownOptions),
    },
  },
})

Skip private or draft docs

Return false from the resolver to skip a source file.

ts
resolveMarkdown(ctx) {
  if (ctx.relativePath.includes('/drafts/')) {
    return false
  }

  return {
    slug: ctx.relativePath.replace(/\.md$/u, ''),
    title: ctx.title,
    text: ctx.title,
    order: ctx.relativePath,
  }
}

Inject generated frontmatter

Resolver frontmatter wins over source frontmatter when both define the same field.

ts
resolveMarkdown(ctx) {
  return {
    slug: ctx.relativePath.replace(/\.md$/u, ''),
    title: ctx.title,
    text: ctx.title,
    order: ctx.relativePath,
    frontmatter: {
      outline: 'deep',
      editLink: false,
    },
  }
}

Generated Markdown receives one frontmatter block at the top of the file.