tiptap-converter
v0.2.1
Published
TypeScript library for TipTap JSON ⇄ Markdown conversion with full GFM support
Downloads
12
Maintainers
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-converterQuick 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 structureAPI Reference
toMarkdown(doc: TiptapJSON, opts?: CoreOptions): string
Converts TipTap JSON to Markdown.
Parameters:
doc: TipTap document objectopts: Optional conversion options
Returns: Markdown string
toTiptap(md: string, opts?: CoreOptions): TiptapJSON
Converts Markdown to TipTap JSON.
Parameters:
md: Markdown stringopts: 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|| |
|s3Image|` | 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 validationTable 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 testRun with coverage:
npm run test:coverageBuilding
Build the library:
npm run buildDevelopment mode with watch:
npm run devTypeScript 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
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
Changelog
See CHANGELOG.md for version history.
