Convert DatoCMS Structured Text field to Markdown
npm install datocms-structured-text-to-markdownMarkdown renderer for the DatoCMS Structured Text field type.
Using npm:
``sh`
npm install datocms-structured-text-to-markdown
Using yarn:
`sh`
yarn add datocms-structured-text-to-markdown
`javascript
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.
`
The renderer supports all DatoCMS Structured Text nodes and converts them to CommonMark-compatible Markdown:
- Headings: # H1 through ###### 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 ()
- Strong: bolditalic
- Emphasis:
- Code: code ~~text~~
- Strikethrough: ==text==
- Highlight: (extended Markdown)text
- Underline: (HTML fallback, no native Markdown)
- Regular links: textrenderLinkToRecord
- Record links: Custom rendering via
- Escaping strategy: renderText escapes \*_{}[]()#+|<> ` to avoid accidental formatting or unintended HTML. For bespoke sanitization, supply a custom renderText implementation.1.
- Ordered list markers: Every numbered list item is rendered as . CommonMark parsers expand these into the correct numeric sequence automatically and this keeps the output stable even when items are reordered.attribution
- Blockquote attribution: When a blockquote contains an field, 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.
The renderer surfaces meaningful RenderError instances when required data is missing:
- inlineItem nodes throw if you provide renderInlineRecord but the requested record is not present in .links. Without the handler, the node is skipped.itemLink
- nodes behave the same way: supplying renderLinkToRecord without the matching record raises, while omitting the handler falls back to the plain link text.block
- and inlineBlock nodes 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.
You can pass custom renderers for nodes and text:
`javascript
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);
`
You can pass custom renderers for itemLink, inlineItem, block, and inlineBlock nodes:
`javascript
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};${children}
default:
return null;
}
},
renderLinkToRecord: ({ record, children }) => {
switch (record.__typename) {
case 'BlogPostRecord':
return ;> [!${calloutType}] ${record.title}\n> ${record.content}\n\n
default:
return null;
}
},
renderBlock: ({ record }) => {
switch (record.__typename) {
case 'CalloutRecord': {
// GitHub-flavored Markdown supports callout syntax
const calloutType = record.style.toUpperCase();
return ;
}
default:
return null;
}
},
};
render(graphqlResponse, options);
// -> Check out this article and 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.
`
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 render inlineItem nodesrenderLinkToRecord
- : Function to render itemLink nodesrenderBlock
- : Function to render block nodesrenderInlineBlock
- : Function to render inlineBlock nodesrenderText
- : 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.