remark-prosemirror

April 24, 2026 · View on GitHub

A set of plugins and utils for integrating remark, mdast, and ProseMirror. Can be used to convert ProseMirror documents to Markdown, and vice versa!

Installation

npm i @handlewithcare/remark-prosemirror

Usage

This library exports tools for converting back and forth between Markdown and ProseMirror documents. It’s part of the unified ecosystem.

Parsing a Markdown document as ProseMirror

To parse a Markdown document into ProseMirror, first we parse the document into mdast (Markdown abstract syntax tree), and then use the remark plugin to compile that to ProseMirror.

import { unified } from "unified";
import { remarkParse } from "remark-parse";
import {
  remarkProseMirror,
  toPmNode,
  toPmMark,
  type RemarkProseMirrorOptions,
} from "@handlewithcare/remark-prosemirror";
import type { Node } from "prosemirror-model";

import { mySchema } from "./mySchema.js";

async function markdownToProseMirror(markdown: string): Node {
  const doc = await unified()
    // Use remarkParse to parse the markdown string
    .use(remarkParse)
    // Convert to ProseMirror with the remarkProseMirror plugin.
    // It takes the schema and a set of handlers, each of which
    // maps an mdast node type to a ProseMirror node (or nodes)
    .use(remarkProseMirror, {
      schema: mySchema,
      handlers: {
        // For simple nodes, you can use the built-in toPmNode
        // util
        paragraph: toPmNode(mySchema.nodes.paragraph),
        listItem: toPmNode(mySchema.nodes.list_item),
        // If you need to take over control, you can write your
        // own handler, which gets passed the mdast node, its
        // parent, and the plugin state, which has helper methods
        // for converting nodes from mdast to ProseMirror.
        list(node, _, state) {
          const children = state.all(node);
          const nodeType = node.ordered
            ? mySchema.nodes.ordered_list
            : mySchema.nodes.bullet_list;
          return nodeType.createAndFill({}, children);
        },

        // You can also treat mdast nodes as ProseMirror marks
        emphasis: toPmMark(mySchema.marks.em),
        strong: toPmMark(mySchema.marks.strong),
        // And you can set attrs on nodes or marks based on
        // the mdast data
        link: toPmMark(mySchema.marks.link, (node) => ({
          href: node.url,
          title: node.title,
        })),
      },
    } satisfies RemarkProseMirrorOptions)
    .process(markdown);

  return doc;
}

Serializing a ProseMirror document to Markdown

To serialize a ProseMirror document to Markdown, first we convert the ProseMirror document to mdast. Then we can use remark to serialize the mdast to a string.

import { unified } from "unified";
import { remarkStringify } from "remark-stringify";
import {
  fromProseMirror,
  fromPmNode,
  fromPmMark,
} from "@handlewithcare/remark-prosemirror";
import type { Node } from "prosemirror-model";

import { mySchema } from "./mySchema.js";

function proseMirrorToMarkdown(doc: Node) {
  // Convert to mdast with the fromProseMirror util.
  // It takes a schema, a set of node handlers, and a
  // set of mark handlers, each of which converts a
  // ProseMirror node or mark to an mdast node.
  const mdast = fromProseMirror(doc, {
    schema: mySchema,
    nodeHandlers: {
      // Simple nodes can be converted with the fromPmNode
      // util.
      paragraph: fromPmNode("paragraph"),
      list_item: fromPmNode("listItem"),
      // You can set mdast node properties from the
      // ProseMirror node or its attrs
      ordered_list: fromPmNode("list", () => ({
        ordered: true,
      })),
      bullet_list: fromPmNode("list", () => ({
        ordered: false,
      })),
    },
    markHandlers: {
      // Simple marks can be converted with the fromPmMark
      // util.
      em: fromPmMark("emphasis"),
      strong: fromPmMark("strong"),
      // Again, mdast node properties can be set from the
      // ProseMirror mark attrs
      link: fromPmMark("link", (mark) => ({
        url: mark.attrs["href"],
        title: mark.attrs["title"],
      })),
    },
  });

  return unified().use(remarkStringify).stringify(mdast);
}

API Docs

Interfaces

FromProseMirrorOptions<PmNodes, PmMarks>

Type Parameters

Type Parameter
PmNodes extends string
PmMarks extends string

Properties

markHandlers

markHandlers: Partial<Record<PmMarks, PmMarkHandler>>

Defined in

lib/mdast-util-from-prosemirror.ts:125

nodeHandlers

nodeHandlers: Partial<Record<PmNodes, PmNodeHandler>>

Defined in

lib/mdast-util-from-prosemirror.ts:124

schema

schema: Schema<PmNodes, PmMarks>

Defined in

lib/mdast-util-from-prosemirror.ts:123


RemarkProseMirrorOptions

Properties

handlers

handlers: MdastHandlers

Defined in

lib/mdast-util-to-prosemirror.ts:367

htmlHandlers?

optional htmlHandlers: HastHandlers

Defined in

lib/mdast-util-to-prosemirror.ts:368

schema

schema: Schema<any, any>

Defined in

lib/mdast-util-to-prosemirror.ts:366

Functions

fromPmMark()

fromPmMark<Type>(type, getAttrs?): PmMarkHandler

Type Parameters

Type Parameter
Type extends "blockquote" | "break" | "code" | "definition" | "delete" | "emphasis" | "footnoteDefinition" | "footnoteReference" | "heading" | "html" | "image" | "imageReference" | "inlineCode" | "link" | "linkReference" | "list" | "listItem" | "paragraph" | "strong" | "table" | "tableCell" | "tableRow" | "text" | "thematicBreak" | "yaml" | "root"

Parameters

ParameterType
typeType
getAttrs?(pmNode) => Omit<Extract<Root, { type: Type; }> | Extract<Blockquote, { type: Type; }> | Extract<Break, { type: Type; }> | Extract<Code, { type: Type; }> | Extract<Definition, { type: Type; }> | Extract<Delete, { type: Type; }> | Extract<Emphasis, { type: Type; }> | Extract<FootnoteDefinition, { type: Type; }> | Extract<FootnoteReference, { type: Type; }> | Extract<Heading, { type: Type; }> | Extract<Html, { type: Type; }> | Extract<Image, { type: Type; }> | Extract<ImageReference, { type: Type; }> | Extract<InlineCode, { type: Type; }> | Extract<Link, { type: Type; }> | Extract<LinkReference, { type: Type; }> | Extract<List, { type: Type; }> | Extract<ListItem, { type: Type; }> | Extract<Paragraph, { type: Type; }> | Extract<Strong, { type: Type; }> | Extract<Table, { type: Type; }> | Extract<TableCell, { type: Type; }> | Extract<TableRow, { type: Type; }> | Extract<Text, { type: Type; }> | Extract<ThematicBreak, { type: Type; }> | Extract<Yaml, { type: Type; }>, "type" | "children">

Returns

PmMarkHandler

Defined in

lib/mdast-util-from-prosemirror.ts:178


fromPmNode()

fromPmNode<Type>(type, getAttrs?): PmNodeHandler

Type Parameters

Type Parameter
Type extends "blockquote" | "break" | "code" | "definition" | "delete" | "emphasis" | "footnoteDefinition" | "footnoteReference" | "heading" | "html" | "image" | "imageReference" | "inlineCode" | "link" | "linkReference" | "list" | "listItem" | "paragraph" | "strong" | "table" | "tableCell" | "tableRow" | "text" | "thematicBreak" | "yaml" | "root"

Parameters

ParameterType
typeType
getAttrs?(pmNode) => Omit<Extract<Root, { type: Type; }> | Extract<Blockquote, { type: Type; }> | Extract<Break, { type: Type; }> | Extract<Code, { type: Type; }> | Extract<Definition, { type: Type; }> | Extract<Delete, { type: Type; }> | Extract<Emphasis, { type: Type; }> | Extract<FootnoteDefinition, { type: Type; }> | Extract<FootnoteReference, { type: Type; }> | Extract<Heading, { type: Type; }> | Extract<Html, { type: Type; }> | Extract<Image, { type: Type; }> | Extract<ImageReference, { type: Type; }> | Extract<InlineCode, { type: Type; }> | Extract<Link, { type: Type; }> | Extract<LinkReference, { type: Type; }> | Extract<List, { type: Type; }> | Extract<ListItem, { type: Type; }> | Extract<Paragraph, { type: Type; }> | Extract<Strong, { type: Type; }> | Extract<Table, { type: Type; }> | Extract<TableCell, { type: Type; }> | Extract<TableRow, { type: Type; }> | Extract<Text, { type: Type; }> | Extract<ThematicBreak, { type: Type; }> | Extract<Yaml, { type: Type; }>, "type" | "children">

Returns

PmNodeHandler

Defined in

lib/mdast-util-from-prosemirror.ts:157


fromProseMirror()

fromProseMirror<PmNodes, PmMarks>(pmNode, options): MdastRoot

Type Parameters

Type Parameter
PmNodes extends string
PmMarks extends string

Parameters

ParameterType
pmNodeNode
optionsFromProseMirrorOptions<PmNodes, PmMarks>

Returns

MdastRoot

Defined in

lib/mdast-util-from-prosemirror.ts:128


remarkProseMirror()

remarkProseMirror(this, ...parameters): undefined | void

Parameters

ParameterType
thisProcessor<undefined, undefined, undefined, undefined, undefined>
...parameters[RemarkProseMirrorOptions]

Returns

undefined | void

Defined in

lib/remark-prosemirror.ts:10


toPmMark()

toPmMark<MdastNode>(markType, getAttrs?): (node, _, state) => Node[]

Type Parameters

Type Parameter
MdastNode extends Nodes

Parameters

ParameterType
markTypeMarkType
getAttrs?(mdastNode) => null | Record<string, unknown>

Returns

Function

Parameters
ParameterType
nodeMdastNode
_Parent
stateState
Returns

Node[]

Defined in

lib/mdast-util-to-prosemirror.ts:335


toPmNode()

toPmNode<MdastNode>(nodeType, getAttrs?): (node, _, state) => null | Node

Type Parameters

Type Parameter
MdastNode extends Nodes

Parameters

ParameterType
nodeTypeNodeType
getAttrs?(mdastNode) => null | Record<string, unknown>

Returns

Function

Parameters
ParameterType
nodeMdastNode
_Parent
stateState
Returns

null | Node

Defined in

lib/mdast-util-to-prosemirror.ts:324

Looking for someone to collaborate with?

Reach out to Handle with Care! We're a product development collective with years of experience bringing excellent ideas to life. We love Markdown and ProseMirror, and we're always looking for new folks to work with!

Sponsors

The following companies, organizations, and individuals support Pitter Patter's ongoing development.

Sponsors receive regular updates about development progress, including previews of upcoming features, priority getting issues addressed as we release features, and a direct line of communication to us for support. Funders above a certain threshold join our steering committee — a lightweight governance model where significant contributors help shape our roadmap priorities.

Become a Sponsor

Sponsors

Moment
Moment
Lingco
Lingco
dskrpt
dskrpt
Fastrepl
Fastrepl