npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

tiptap-converter

v0.2.1

Published

TypeScript library for TipTap JSON ⇄ Markdown conversion with full GFM support

Downloads

12

Readme

TipTap MD

A production-grade TypeScript library for bidirectional conversion between TipTap JSON and Markdown with full GitHub Flavored Markdown (GFM) support.

Features

  • Bidirectional conversion: TipTap JSON ⇄ Markdown
  • Full GFM support: Tables, task lists, autolinks, strikethrough, fenced code blocks
  • Custom node/mark handling: Preserve unknown nodes and marks through HTML fallback
  • Safety features: URL validation, content filtering
  • TypeScript: Strict typing with full type safety
  • Zero dependencies: No framework dependencies, pure Node.js
  • Tree-shakeable: Optimized for bundle size
  • Round-trip stable: doc ≈ toTiptap(toMarkdown(doc))

Installation

npm install tiptap-converter

Quick Start

import { toMarkdown, toTiptap } from 'tiptap-converter';

// TipTap JSON to Markdown
const doc = {
  type: 'doc',
  content: [
    {
      type: 'heading',
      attrs: { level: 1 },
      content: [{ type: 'text', text: 'Hello World' }]
    },
    {
      type: 'paragraph',
      content: [
        { type: 'text', text: 'This is ' },
        { type: 'text', text: 'bold', marks: [{ type: 'bold' }] },
        { type: 'text', text: ' and ' },
        { type: 'text', text: 'italic', marks: [{ type: 'italic' }] },
        { type: 'text', text: ' text.' }
      ]
    }
  ]
};

const markdown = toMarkdown(doc);
console.log(markdown);
// Output:
// # Hello World
// 
// This is **bold** and *italic* text.

// Markdown to TipTap JSON
const back = toTiptap(markdown);
console.log(back);
// Output: TipTap JSON structure

API Reference

toMarkdown(doc: TiptapJSON, opts?: CoreOptions): string

Converts TipTap JSON to Markdown.

Parameters:

  • doc: TipTap document object
  • opts: Optional conversion options

Returns: Markdown string

toTiptap(md: string, opts?: CoreOptions): TiptapJSON

Converts Markdown to TipTap JSON.

Parameters:

  • md: Markdown string
  • opts: Optional conversion options

Returns: TipTap document object

CoreOptions

interface CoreOptions {
  /** Validate and filter URLs (javascript:, data:) — default true */
  sanitizeUrls?: boolean;
  /** How to serialize alignment/textStyle (e.g., 'html' | 'attr') — default 'html' */
  styleStrategy?: 'html' | 'attr';
  /** Custom mappers for unknown nodes/marks */
  custom?: {
    nodes?: Record<string, CustomNodeMapper>;
    marks?: Record<string, CustomMarkMapper>;
  };
}

Supported Nodes and Marks

Nodes

| TipTap Node | Markdown | Notes | |-------------|----------|-------| | doc | Document root | | | paragraph | Plain text | | | heading | # Heading | Levels 1-6, supports alignment | | blockquote | > Quote | | | codeBlock | ````language| Fenced code blocks with language | |bulletList|- Item| | |orderedList|1. Item| Supports custom start numbers | |taskList|- [ ] Task| GFM task lists | |table| GFM table | With alignment support | |tableRow| Table row | | |tableHeader| Table header | | |tableCell| Table cell | | |horizontalRule|---| | |hardBreak| \n| | |image|alt| | |s3Image|alt` | Custom node, preserves extra attrs |

Marks

| TipTap Mark | Markdown | Notes | |-------------|----------|-------| | bold | **text** | | | italic | *text* | | | strike | ~~text~~ | GFM strikethrough | | code | `text` | Inline code | | link | [text](url "title") | URL validation | | underline | <u>text</u> | HTML fallback | | highlight | <mark>text</mark> | HTML fallback | | subscript | <sub>text</sub> | HTML fallback | | superscript | <sup>text</sup> | HTML fallback | | textStyle | <span style="...">text</span> | HTML fallback | | mention | [@label](mention://id) | Custom mark |

Custom Node and Mark Handling

Unknown Nodes

Unknown nodes are preserved using HTML fallback:

// TipTap JSON
{
  type: 'customWidget',
  attrs: { id: 'widget123', config: { theme: 'dark' } },
  content: [{ type: 'text', text: 'Widget content' }]
}

// Converts to Markdown
'<div data-tiptap-type="customWidget" data-tiptap-attrs="{\"id\":\"widget123\",\"config\":{\"theme\":\"dark\"}}">Widget content</div>'

// Round-trips back to TipTap JSON
{
  type: 'customWidget',
  attrs: { id: 'widget123', config: { theme: 'dark' } },
  content: [{ type: 'text', text: 'Widget content' }]
}

Custom Handlers

You can provide custom handlers for specific node/mark types:

import { toMarkdown, toTiptap, CoreOptions } from 'tiptap-converter';

const options: CoreOptions = {
  custom: {
    nodes: {
      customWidget: {
        toMarkdown: (node, ctx) => {
          const id = node.attrs?.id || '';
          return `[WIDGET:${id}]${node.content?.[0]?.text || ''}[/WIDGET]`;
        }
      }
    },
    marks: {
      highlight: {
        toMarkdown: (mark, text, ctx) => {
          const color = mark.attrs?.color || 'yellow';
          return `<mark style="background-color: ${color}">${text}</mark>`;
        }
      }
    }
  }
};

const markdown = toMarkdown(doc, options);
const back = toTiptap(markdown, options);

Safety Features

URL Validation

By default, potentially harmful URLs are filtered:

// Potentially harmful URLs are blocked
const doc = {
  type: 'doc',
  content: [{
    type: 'paragraph',
    content: [{
      type: 'text',
      text: 'Click here',
      marks: [{ type: 'link', attrs: { href: 'javascript:alert("xss")' } }]
    }]
  }]
};

const markdown = toMarkdown(doc);
// Result: 'Click here](#)'

// Disable validation if needed
const markdown = toMarkdown(doc, { sanitizeUrls: false });
// Result: 'Click here](javascript:alert("xss"))'

Allowed Protocols

  • http:, https:
  • mailto:, tel:
  • ftp:, ftps:, sftp:
  • mention: (for custom mentions)
  • Relative URLs

Blocked Protocols

  • javascript:
  • data:
  • vbscript:
  • file:
  • about:

Style Strategy

Control how alignment and text styles are serialized:

HTML Strategy (default)

const doc = {
  type: 'heading',
  attrs: { level: 1, textAlign: 'center' },
  content: [{ type: 'text', text: 'Centered Heading' }]
};

const markdown = toMarkdown(doc, { styleStrategy: 'html' });
// Result: '<div align="center"># Centered Heading</div>'

Attribute Strategy

const markdown = toMarkdown(doc, { styleStrategy: 'attr' });
// Result: '# Centered Heading<!--align:center-->'

Examples

Complex Document

const complexDoc = {
  type: 'doc',
  content: [
    {
      type: 'heading',
      attrs: { level: 1 },
      content: [{ type: 'text', text: 'Project Report' }]
    },
    {
      type: 'paragraph',
      content: [
        { type: 'text', text: 'An ' },
        { type: 'text', text: 'File', marks: [{ type: 'bold' }] },
        { type: 'text', text: ' was found in the ' },
        { type: 'text', text: 'API', marks: [{ type: 'code' }] },
        { type: 'text', text: '.' }
      ]
    },
    {
      type: 'taskList',
      content: [
        {
          type: 'taskItem',
          attrs: { checked: false },
          content: [{
            type: 'paragraph',
            content: [{ type: 'text', text: 'Fix authorization checks' }]
          }]
        },
        {
          type: 'taskItem',
          attrs: { checked: true },
          content: [{
            type: 'paragraph',
            content: [{ type: 'text', text: 'Add input validation' }]
          }]
        }
      ]
    }
  ]
};

const markdown = toMarkdown(complexDoc);
console.log(markdown);

Output:

# Project Report

An **issue** was found in the `API`.

- [ ] Fix authorization checks

- [x] Add input validation

Table with Alignment

const tableDoc = {
  type: 'doc',
  content: [{
    type: 'table',
    content: [
      {
        type: 'tableRow',
        content: [
          {
            type: 'tableHeader',
            attrs: { textAlign: 'left' },
            content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Name' }] }]
          },
          {
            type: 'tableHeader',
            attrs: { textAlign: 'center' },
            content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Status' }] }]
          },
          {
            type: 'tableHeader',
            attrs: { textAlign: 'right' },
            content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Score' }] }]
          }
        ]
      }
    ]
  }]
};

const markdown = toMarkdown(tableDoc);
console.log(markdown);

Output:

| Name | Status | Score |
| :--- | :---: | ---: |

Performance

The library is optimized for performance:

  • Large documents: Handles 1000+ nodes efficiently
  • Deep nesting: Supports 20+ levels of list nesting
  • Many marks: Efficiently processes 1000+ text nodes with overlapping marks
  • Large tables: Handles 100x50 tables (5000+ cells) efficiently

Testing

Run the test suite:

npm test

Run with coverage:

npm run test:coverage

Building

Build the library:

npm run build

Development mode with watch:

npm run dev

TypeScript Support

Full TypeScript support with strict typing:

import { TiptapJSON, CoreOptions, CustomNodeMapper } from 'tiptap-converter';

const doc: TiptapJSON = {
  type: 'doc',
  content: []
};

const options: CoreOptions = {
  sanitizeUrls: true,
  styleStrategy: 'html',
  custom: {
    nodes: {},
    marks: {}
  }
};

License

MIT

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Submit a pull request

Changelog

See CHANGELOG.md for version history.