Installation
Install the package and register Payload Markdown in Payload config.
Installation
Install the package in the Payload app:
1pnpm add payload-markdown
If you use Payload MCP or let AI agents such as Claude Code or Codex work in your repo, install the skill that ships with the package. It teaches agents every directive, theme and the MCP content workflow.
1npx payload-markdown skill installRun it again after upgrading the package. See Agent Skill for drift checks and options.
Package Names
The package is published under two names from the same build, always at the same version:
| Package | Install |
|---|---|
payload-markdown | pnpm add payload-markdown |
@valkyrianlabs/payload-markdown | pnpm add @valkyrianlabs/payload-markdown |
Both are fully supported and behave identically. Import from the name you installed: these docs use payload-markdown, so with the scoped package write @valkyrianlabs/payload-markdown/server instead of payload-markdown/server, and so on. The Payload admin components follow whichever name is installed, so the import map needs no extra configuration.
Switching an existing app from one name to the other: replace the dependency and the imports, update the Tailwind @source path, then run payload generate:importmap so the admin import map points at the new name.
Installing both names loads two copies of the plugin. Pick one per app.
Register The Plugin
Add payloadMarkdown() to the Payload plugins array.
1import { payloadMarkdown } from 'payload-markdown'2import type { Config } from 'payload'3 4const config: Config = {5 plugins: [6 payloadMarkdown({7 collections: {8 posts: true,9 },10 }),11 ],12}13 14export default config
When a collection is enabled with true, the plugin infers the useful install path:
- collections without a
blocksfield receive a Markdown text field namedcontent - collections with one or more
blocksfields receive thevlMdBlockMarkdown block inside those fields
If a collection already has a field with the target name, the plugin does not add another one. If the root Payload config or a blocks field already includes vlMdBlock, it is not added twice.
Configure A Collection Explicitly
Use an object when you want to choose the field name, field options, or install behavior.
1payloadMarkdown({2 collections: {3 posts: {4 fieldName: 'body',5 field: {6 admin: {7 description: 'Author-facing Markdown body',8 },9 defaultValue: '# Draft',10 label: 'Body Markdown',11 localized: true,12 required: true,13 },14 installField: true,15 installIntoBlocks: false,16 },17 },18})
Preferred Config Shape
Keep each concern in its own namespace:
1import { DEFAULT_CODE_LANGS, payloadMarkdown } from 'payload-markdown'2 3payloadMarkdown({4 code: {5 enhanced: true,6 fullBleed: false,7 langs: [...DEFAULT_CODE_LANGS, 'latex', 'r'],8 lineNumbers: true,9 shikiTheme: 'github-dark',10 },11 config: {12 size: 'lg',13 variant: 'blog',14 },15 themes: {16 card: {17 items: [18 {19 name: 'feature',20 classes: 'rounded-2xl border border-cyan-400/40 bg-cyan-950/30 p-5',21 },22 ],23 },24 },25 collections: {26 posts: {27 code: {28 lineNumbers: false,29 },30 config: {31 variant: 'docs',32 },33 themes: {34 callout: {35 items: [36 {37 name: 'editorial',38 classes: 'rounded-xl border border-white/10 bg-white/5 px-4 py-3',39 },40 ],41 },42 },43 },44 },45})
Use code, not config.options, for Shiki and code fence behavior. Use themes, not config.themes, for directive theme registries.
Tailwind Setup
The renderer uses Tailwind-friendly class strings. For the default typography layer, install and enable Tailwind Typography:
1pnpm add @tailwindcss/typography
1@import "tailwindcss";2@plugin "@tailwindcss/typography";3 4@source "../node_modules/payload-markdown/dist";
Installed the scoped package? Point @source at ../node_modules/@valkyrianlabs/payload-markdown/dist instead.
Keep custom directive theme class strings in source-controlled config so Tailwind can discover them. Markdown authors should select named themes with theme="..." rather than writing arbitrary Tailwind classes in CMS content.