
Research
SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains
An emerging npm supply chain attack that infects repos, steals CI secrets, and targets developer AI toolchains for further compromise.
@portabletext/to-html
Advanced tools
Render Portable Text to HTML.
npm install --save @portabletext/to-html
import {toHTML} from '@portabletext/to-html'
console.log(
toHTML(portableTextBlocks, {
components: {
/* optional object of custom components to use */
},
}),
)
The rendered HTML does not have any styling applied, so you will want to either render a parent container with a class name you can target in your CSS, or pass custom components if you want to pass specific class names.
"Components" are (in this package) just functions, which receive an object of properties that contains the relevant information needed to make a decision about what to render. The return value of the component functions are plain strings containing HTML.
Default component functions are provided for all standard features of the Portable Text spec, with logical HTML defaults.
You can pass an object of component functions to use in the components option, both to override the defaults and to provide components for your custom content types.
The passed components will be merged with the default components.
⚠️ IMPORTANT: Make sure you sanitize/escape the returned HTML!
In the below examples we use the htm and vhtml modules to render "safe" HTML.
import htm from 'htm'
import vhtml from 'vhtml'
import {toHTML, uriLooksSafe} from '@portabletext/to-html'
const html = htm.bind(vhtml)
const myPortableTextComponents = {
types: {
image: ({value}) => html`<img src="${value.imageUrl}" />`,
callToAction: ({value, isInline}) =>
isInline
? html`<a href="${value.url}">${value.text}</a>`
: html`<div class="callToAction">${value.text}</div>`,
},
marks: {
link: ({children, value}) => {
// ⚠️ `value.href` IS NOT "SAFE" BY DEFAULT ⚠️
// ⚠️ Make sure you sanitize/validate the href! ⚠️
const href = value.href || ''
if (uriLooksSafe(href)) {
const rel = href.startsWith('/') ? undefined : 'noreferrer noopener'
return html`<a href="${href}" rel="${rel}">${children}</a>`
}
// If the URI appears unsafe, render the children (eg, text) without the link
return children
},
},
}
console.log(toHTML(somePtValue, {components: myPortableTextComponents}))
These are the overridable/implementable keys:
typesAn object of component functions that renders different types of objects that might appear both as part of the input array, or as inline objects within text blocks - eg alongside text spans.
Use the isInline property to check whether or not this is an inline object or a block.
The object has the shape {typeName: ComponentFn}, where typeName is the value set in individual _type attributes.
marksObject of component functions that renders different types of marks that might appear in spans. Marks can be either be simple "decorators" (eg emphasis, underline, italic) or full "annotations" which include associated data (eg links, references, descriptions).
If the mark is a decorator, the component function will receive a markType property which has the name of the decorator (eg em). If the mark is an annotation, it will receive both a markType with the associated _type property (eg link), and a value property with an object holding the data for this mark.
The component function also receives a children property that should (usually) be rendered in whatever parent container makes sense for this mark (eg <a>, <em>).
blockAn object of component functions that renders portable text blocks with different style properties. The object has the shape {styleName: ComponentFn}, where styleName is the value set in individual style attributes on blocks (normal being the default).
Can also be set to a single component function, which would handle block styles of any type.
listObject of component functions used to render lists of different types (bullet vs number, for instance, which by default is <ul> and <ol>, respectively).
Note that there is no actual "list" node type in the Portable Text specification, but a series of list item blocks with the same level and listItem properties will be grouped into a virtual one inside of this library.
The property can also be set to a single component function, which would handle lists of any type.
listItemObject of component functions used to render different list item styles. The object has the shape {listItemType: ComponentFn}, where listItemType is the value set in individual listItem attributes on blocks.
Can also be set to a single component function, which would handle list items of any type.
hardBreakComponent function to use for rendering "hard breaks", eg \n inside of text spans.
Will by default render a <br />. Pass false to render as-is (\n)
unknownMarkComponent function used when encountering a mark type there is no registered component for in the components.marks option.
unknownTypeComponent function used when encountering an object type there is no registered component for in the components.types option.
unknownBlockStyleComponent function used when encountering a block style there is no registered component for in the components.block option. Only used if components.block is an object.
unknownListComponent function used when encountering a list style there is no registered component for in the components.list option. Only used if components.list is an object.
unknownListItemComponent function used when encountering a list item style there is no registered component for in the components.listItem option. Only used if components.listItem is an object.
If you override the default components but still want access to the original ones, you can access them by importing defaultComponents:
import {defaultComponents, toHTML, escapeHTML} from '@portabletext/to-html'
toHTML(
[
/* array of portable text blocks */
],
{
components: {
marks: {
link: ({children, value, ...rest}) => {
const href = value.href || ''
return href.startsWith('https://my.site/')
? `<a href="${escapeHTML(href)}" class="internalLink">${children}</a>`
: defaultComponents({children, value, ...rest})
},
},
},
},
)
When the library encounters a block, mark, list or list item with a type that is not known (eg it has no corresponding component in the components property), it will by default print a console warning.
To disable this behavior, you can either pass false to the onMissingComponent property, or give it a custom function you want to use to report the error. For instance:
import {toHTML} from '@portabletext/to-html'
toHTML(
[
/* array of portable text blocks */
],
{onMissingComponent: false},
)
// or, pass it a function:
toHTML(
[
/* array of portable text blocks */
],
{
onMissingComponent: (message, options) => {
myErrorLogger.report(message, {
// eg `someUnknownType`
type: options.type,
// 'block' | 'mark' | 'blockStyle' | 'listStyle' | 'listItemStyle'
nodeType: options.nodeType,
})
},
},
)
If you find there are links that are not being rendered, it is likely because the href property (eg the URI) of the link is not considered "safe". This is done to prevent URIs like javascript:someDangerousFn() and similar from being rendered. If you want to override this behavior, provide a custom component for the link mark:
import {escapeHTML} from `@portabletext/to-html`
toHTML(portableTextBlocks, {
components: {
marks: {
link: ({children, value}) => {
// ⚠️ `value.href` IS NOT "SAFE" BY DEFAULT ⚠️
// ⚠️ Make sure you sanitize/validate the href! ⚠️
const unsafeUri = value.href || ''
const looksSafe = /^(http|https|mailto|my-custom-proto):/i.test(unsafeUri)
return looksSafe
? `<a href="${escapeHTML(value.href)}">${children}</a>`
: children
},
}
},
})
MIT © Sanity.io
html-to-text is a package that converts HTML content into plain text. While it performs the opposite function of @portabletext/to-html, it is similar in that it deals with transforming content between different formats. Unlike @portabletext/to-html, it does not handle Portable Text specifically.
remark-html is a plugin for the remark processor that converts Markdown to HTML. It is similar to @portabletext/to-html in that it transforms a structured text format into HTML, but it is specifically designed for Markdown rather than Portable Text.
draft-js-export-html is a package that converts Draft.js editor content to HTML. It is similar to @portabletext/to-html in that it converts a rich text format to HTML, but it is specifically designed for Draft.js content rather than Portable Text.
FAQs
Render Portable Text to HTML
The npm package @portabletext/to-html receives a total of 59,674 weekly downloads. As such, @portabletext/to-html popularity was classified as popular.
We found that @portabletext/to-html demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 10 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
An emerging npm supply chain attack that infects repos, steals CI secrets, and targets developer AI toolchains for further compromise.

Company News
Socket is proud to join the OpenJS Foundation as a Silver Member, deepening our commitment to the long-term health and security of the JavaScript ecosystem.

Security News
npm now links to Socket's security analysis on every package page. Here's what you'll find when you click through.