datocms-structured-text-to-markdown
v5.1.7
Published
Convert DatoCMS Structured Text field to Markdown
Downloads
59
Maintainers
Readme
datocms-structured-text-to-markdown
Markdown renderer for the DatoCMS Structured Text field type.
Installation
Using npm:
npm install datocms-structured-text-to-markdownUsing yarn:
yarn add datocms-structured-text-to-markdownUsage
import { render } from 'datocms-structured-text-to-markdown';
render({
schema: 'dast',
document: {
type: 'root',
children: [
{
type: 'heading',
level: 1,
children: [
{
type: 'span',
value: 'Hello world!',
},
],
},
{
type: 'paragraph',
children: [
{
type: 'span',
value: 'This is a paragraph.',
},
],
},
],
},
});
// -> # Hello world!
//
// This is a paragraph.Supported Markdown Features
The renderer supports all DatoCMS Structured Text nodes and converts them to CommonMark-compatible Markdown:
Block Nodes
- Headings:
# H1through###### H6 - Paragraphs: Plain text with double newlines
- Lists: Both bulleted (
-) and numbered (1.) lists with nested support - Blockquotes: Lines prefixed with
> - Code blocks: Fenced code blocks with language support
- Thematic breaks: Horizontal rules (
---)
Inline Formatting
- Strong:
**bold** - Emphasis:
*italic* - Code:
`code` - Strikethrough:
~~text~~ - Highlight:
==text==(extended Markdown) - Underline:
<u>text</u>(HTML fallback, no native Markdown)
Links
- Regular links:
[text](url) - Record links: Custom rendering via
renderLinkToRecord
Behavior Notes
- Escaping strategy:
renderTextescapes\`*_{}[]()#+|<>to avoid accidental formatting or unintended HTML. For bespoke sanitization, supply a customrenderTextimplementation. - Ordered list markers: Every numbered list item is rendered as
1.. CommonMark parsers expand these into the correct numeric sequence automatically and this keeps the output stable even when items are reordered. - Blockquote attribution: When a blockquote contains an
attributionfield, the renderer appends a final line formatted as— Author. This mirrors the DOM renderer's output but is not part of the Markdown core spec.
Error Handling
The renderer surfaces meaningful RenderError instances when required data is missing:
inlineItemnodes throw if you providerenderInlineRecordbut the requested record is not present in.links. Without the handler, the node is skipped.itemLinknodes behave the same way: supplyingrenderLinkToRecordwithout the matching record raises, while omitting the handler falls back to the plain link text.blockandinlineBlocknodes require both a renderer and a matching record. Missing renderers make the node render as empty; missing records raise.
Handle these errors upstream by passing the complete GraphQL response or adjusting your custom render callbacks.
Advanced Usage
Custom Rendering
You can pass custom renderers for nodes and text:
import { render, renderNodeRule } from 'datocms-structured-text-to-markdown';
import { isHeading } from 'datocms-structured-text-utils';
const options = {
renderText: (text) => text.toUpperCase(),
customNodeRules: [
renderNodeRule(
isHeading,
({ node, children, adapter: { renderFragment } }) => {
// Custom heading with decoration
return renderFragment([
`${'='.repeat(node.level)} `,
...(children || []),
'\n\n',
]);
},
),
],
};
render(document, options);Rendering DatoCMS records and blocks
You can pass custom renderers for itemLink, inlineItem, block, and inlineBlock nodes:
import { render } from 'datocms-structured-text-to-markdown';
const graphqlResponse = {
value: {
schema: 'dast',
document: {
type: 'root',
children: [
{
type: 'paragraph',
children: [
{
type: 'span',
value: 'Check out ',
},
{
type: 'itemLink',
item: '123',
children: [
{
type: 'span',
value: 'this article',
},
],
},
{
type: 'span',
value: ' and ',
},
{
type: 'inlineItem',
item: '123',
},
{
type: 'span',
value: '!',
},
],
},
{
type: 'block',
item: '456',
},
],
},
},
blocks: [
{
id: '456',
__typename: 'CalloutRecord',
style: 'positive',
title: '🛠️ Block and Structured Text utilities',
content:
'We provide many utility functions to help you work with blocks and structured text nodes effectively.',
},
],
links: [
{
id: '123',
__typename: 'BlogPostRecord',
title: 'My First Post',
slug: 'my-first-post',
},
],
};
const options = {
renderInlineRecord: ({ record }) => {
switch (record.__typename) {
case 'BlogPostRecord':
return `[${record.title}](/blog/${record.slug})`;
default:
return null;
}
},
renderLinkToRecord: ({ record, children }) => {
switch (record.__typename) {
case 'BlogPostRecord':
return `[${children}](/blog/${record.slug})`;
default:
return null;
}
},
renderBlock: ({ record }) => {
switch (record.__typename) {
case 'CalloutRecord': {
// GitHub-flavored Markdown supports callout syntax
const calloutType = record.style.toUpperCase();
return `> [!${calloutType}] ${record.title}\n> ${record.content}\n\n`;
}
default:
return null;
}
},
};
render(graphqlResponse, options);
// -> Check out [this article](/blog/my-first-post) and [My First Post](/blog/my-first-post)!
//
// > [!POSITIVE] 🛠️ Block and Structured Text utilities
// > We provide many utility functions to help you work with blocks and structured text nodes effectively.API
render(structuredText, options?)
Converts a Structured Text document to a Markdown string.
Parameters
structuredText: The Structured Text document (can be a full GraphQL response or a plain document)options(optional): Rendering optionscustomNodeRules: Array of custom node rendering rulescustomMarkRules: Array of custom mark rendering rulesrenderInlineRecord: Function to renderinlineItemnodesrenderLinkToRecord: Function to renderitemLinknodesrenderBlock: Function to renderblocknodesrenderInlineBlock: Function to renderinlineBlocknodesrenderText: Function to customize text renderingrenderNode: Function to customize node renderingrenderFragment: Function to customize fragment rendering
Returns
A Markdown string, or null if the input is empty.
