Dedicated to the Hydrogen framework, headless commerce, and building custom storefronts using the Storefront API.
Hi, when I query rich text from a product metafield I get a liquid format. But i m using Hydrogen and i would like to know if it possible to convert it into a HTML format.
Hi,
I stumbled across the same problem and found a solution.
Try using the metafield_tag Liquid Filter :
{{ product.metafields.custom.info | metafield_tag }}
See the docs for more info :
https://shopify.dev/docs/api/liquid/filters/metafield_tag
Hi, I have the same issue but I'm using the headless Storefront API. I'm getting this format when I declare a custom field as a rich text. Any idea how to get it formatted? (like the description field, with or without HTML for example). Or do we have access to a JS helper to parse the RichText type? Thanks!
Hey mate, did you find a solution for this, I have the same issue.
I actually built it myself at first. I would have loved to give you the React code I wrote, but since we actually changed our mind in the content type, we did it differently. To give you a bit of context, I'm using Shopify flow to push data into my custom field. However, the RichText type was not supported to I switched back to multi-line text.
The recursive parsing of the API output was not difficult though (and I was easily able to plug my design system to it which was a good thing). However, having an official parser would be great in case one day this API response is changing 😕
Sorry for not being able to help you more on this one!
Thanks mate, I appreciate you taking the time to answer.
Not the most elegant thing in the world, but anyone reading this looking for a solution, you can just grab that 😬
export function toHTML(content) {
let parsed = JSON.parse(content);
let html = '';
parsed.children.forEach((node) => {
switch (node.type) {
case 'heading':
html += `<h${node.level}>${node.children[0].value}</h${node.level}>`;
break;
case 'list':
html += `<${node.listType === 'unordered' ? 'ul' : 'ol'}>`;
node.children.forEach((item) => {
html += `<li>${item.children[0].value}</li>`;
});
html += `<${node.listType === 'unordered' ? '/ul' : '/ol'}>`;
break;
case 'paragraph':
html += `<p>`;
node.children.forEach((item) => {
if (item.type === 'text' && item.bold) {
html += `<strong>${item.value}</strong>` + ' ';
} else if (item.type === 'text' && item.italic) {
html += `<em>${item.value}</em>` + ' ';
} else if (item.type === 'text') {
html += `${item.value}` + ' ';
}
if (item.type === 'link' && item.bold) {
html +=
`<a href="${item.url}" target="${item.target}"><strong>${item.children[0].value}</strong></a>` +
' ';
} else if (item.type === 'link' && item.italic) {
html +=
`<a href="${item.url}" target="${item.target}"><em>${item.children[0].value}</em></a>` +
' ';
} else if (item.type === 'link') {
html +=
`<a href="${item.url}" target="${item.target}">${item.children[0].value}</a>` + ' ';
}
});
html += `</p>`;
break;
}
});
return html;
}
Appreciate this! Worked for our use case too, thank you!
Here is a package for rendering shopify rich text to html.
I have used it and it works great. Super easy.
This package no longer exists on NPM. The repository is still open with no information as to why.
Also, you appear to be the author of the package, so I don't know why you are pretending to act as a user leaving a review but that's a bit of red flag for me.
I’m not pretending to be a user. Yes, I wrote the package. I have been using it and had no issues. I also stated it’s easy to use. Sorry for any confusion. The code is pretty simple. It’s open source, go read it. If I was pretending to be a user I wouldn’t have chosen my github handle as a username here lol.
The package is right here on npm:
https://www.npmjs.com/package/@thebeyondgroup/shopify-rich-text-renderer
Hey there, sorry for the misplaced judgement! Unless you updated the repository, it appears there was an issue on NPM's end. It was not found when navigating via the link in the repo and when copy/pasting the install command. That, along with what I interpreted as trying to lure people in posing as a user, made me suspicious. My apologies! Take care
Edit: Looks like it was an NPM issue, I still had the install command in my terminal and it worked just fine now.
Yep, no worries. I see how my wording could have been better. I ran across this issue while trying to use meta objects in hydrogen. After search shopify docs and looking far and wide for a Shopify official serializer
(which I assumed they must have for their custom rich text AST). I ended up writing my own and we have used it internally at our company for awhile before publicly releasing a package for others to use.
I meant it is simple to use as it is pretty basic compared to some other solutions I see here. It just takes the AST and converts it to html with the option to add a class on the wrapper element in order scope septic CSS for the rich text HTML. I also figured it would be easy to use in non-react/hydrogen projects as well.
I do think adding an option to add classes to each node type, h1, ul, p etc… could be useful and could be something I add in the future.
Anyways I hope others devs find it useful and don’t have to worry about writing their own serializer for the metaobject rich text field. That’s why we wanted to make it simple and easy to use for devs at all experience levels.
Thanks for posting your reply and have a great rest of your week!
Let me know if there is anything you would like to see added to the package! 🙂
This works. Using it in Angular as of 12-16-2023. Since there is no types for the packages, the import had to be done like this:
// @TS-ignore
import { convertSchemaToHtml } from '@thebeyondgroup/shopify-rich-text-renderer';
product = {
...product,
specs: !!product.specs
? {
type: product.specs.type,
value: convertSchemaToHtml(
JSON.parse(product.specs.value),
true
),
}
: null,
};
I am using it to convert a metafield's rich text to html. Don't forget to sanitize the html 🙂
Here is a package for this purpose: shopify-rich-text-renderer
Like @Lokiii mentioned before, it would be nice to have an official serialiser.
Hi there, if it can help, here is a recursive (needed for the children type) React + Typescript implementation which should be relatively easy to turn into any other language you need (or in Liquid?). Can work easily with any CSS framework with the classNames provided as well.
type ShopifyRichTextRoot = {
type: 'root'
children: ShopifyRichTextTypes[]
}
type ShopifyRichTextList = {
type: 'list'
listType: 'unordered' | 'ordered'
children: ShopifyRichTextTypes[]
}
type ShopifyRichTextListItem = {
type: 'list-item'
children: ShopifyRichTextTypes[]
}
type ShopifyRichTextParagraph = {
type: 'paragraph'
children: ShopifyRichTextTypes[]
}
type ShopifyRichTextHeading = {
type: 'heading'
level: 1 | 2 | 3 | 4 | 5 | 6
children: ShopifyRichTextTypes[]
}
type ShopifyRichTextValue = {
type: 'text'
value: string
bold: boolean
italic: boolean
}
type ShopifyRichTextLink = {
type: 'link'
url: string
title: string
target: string
children: ShopifyRichTextTypes[]
}
type ShopifyRichTextTypes =
| ShopifyRichTextRoot
| ShopifyRichTextParagraph
| ShopifyRichTextValue
| ShopifyRichTextHeading
| ShopifyRichTextLink
| ShopifyRichTextList
| ShopifyRichTextListItem
const ShopifyRichText = (
node: ShopifyRichTextTypes & {
options: Array<{
type: 'bold' | 'italic'
className: string
}>
},
) => {
const renderChildren = () => {
if ('children' in node) {
return node.children.map((child, index) => (
<ShopifyRichText key={index} {...child} options={node.options} />
))
}
return null
}
if (node.type === 'root') {
return renderChildren()
}
if (node.type === 'paragraph') {
return <p>{renderChildren()}</p>
}
if (node.type === 'list-item') {
return <li>{renderChildren()}</li>
}
if (node.type === 'link') {
return (
<a href={node.url} target={node.target} title={node.title}>
{renderChildren()}
</a>
)
}
if (node.type === 'text') {
const nodeClassName = node.options
.filter(({ type }) => node[type])
.map(({ className }) => className)
.join(' ')
if (nodeClassName === '') {
return node.value
}
return <span className={nodeClassName}>{node.value}</span>
}
if (node.type === 'heading') {
const HeadingTag = `h${node.level}` as const
return <HeadingTag>{renderChildren()}</HeadingTag>
}
if (node.type === 'list') {
const ListTag = node.listType === 'ordered' ? 'ol' : 'ul'
return <ListTag>{renderChildren()}</ListTag>
}
return null
}
And you can call it this way:
<ShopifyRichText
{...JSON.parse(richText)}
options={[
{ type: 'bold', className: 'font-bold' },
{ type: 'italic', className: 'italic' },
]}
/>
Great solution for TS, thanks for sharing 😄
So lucky to have found your post! Thanks for sharing, perfect for Tailwind integration.
Edit: In lieu of the package that was being shared here no longer being available on NPM, would you allow me to create a package with your code? I will of course give you credit and I may add some additional optional configurations, too. If so, you can find my email on my profile here. Thanks! @Lokiii
Hi there 👋
Sure, no worries you can take it 🙂 I didn't do it because I (sadly) don't have time to maintain this package according to the future RichText releases (hence it would have been better if Shopify provided an integration which would evolve with the future updates).
But if you feel like it's relevant, go ahead and take it 🎁 🙂
For anyone who is interested, I figured out that there is one 'feature' missing from the metafield_tag solution: respecting newlines. To add that functionality use an additional newline_to_br tag:
{{ product.metafields.custom.info | metafield_tag | newline_to_br }}
And whoever is looking to modify the rich_text metafield themselves, I found this solution too late, so I had already implemented it myself:
<div class="metafield-rich_text_field">
{% for item in product.metafields.custom.info.value.children %}
{% if item.type == "heading" and item.children %}
<h{{item.level}}>{% for child in item.children %}{% if child.bold %}<strong>{% endif %}{% if child.italic %}<i>{% endif %}{{ child.value | newline_to_br }}{% if child.bold %}</strong>{% endif %}{% if child.italic %}</i>{% endif %}{% endfor %}</h{{item.level}}>
{% endif %}
{% if item.type == "list" and item.children %}
{% if item.listType == 'ordered' %}
<ol>
{% else %}
<ul>
{% endif %}
{% for listItem in item.children %}
<li>
{% for child in listItem.children %}
{% if child.bold %}<strong>{% endif %}{% if child.italic %}<i>{% endif %}{{ child.value | newline_to_br }}{% if child.bold %}</strong>{% endif %}{% if child.italic %}</i>{% endif %}
{% endfor %}
</li>
{% endfor %}
{% if item.listType == 'ordered' %}
</ol>
{% else %}
</ul>
{% endif %}
{% endif %}
{% if item.type == "paragraph" and item.children %}
<p>
{% for child in item.children %}
{% if child.type == "link" %}<a href="{{child.url}}"{% if child.target %} target="{{child.target}}"{% endif %}>{% for text in child.children %}{% if text.bold %}<strong>{% endif %}{% if text.italic %}<i>{% endif %}{{ text.value | newline_to_br }}{% if text.bold %}</strong>{% endif %}{% if text.italic %}</i>{% endif %}{% endfor %}</a>
{% else %}
{% if child.bold %}<strong>{% endif %}{% if child.italic %}<i>{% endif %}{{ child.value | newline_to_br }}{% if child.bold %}</strong>{% endif %}{% if child.italic %}</i>{% endif %}
{% endif %}
{% endfor %}
</p>
{% endif %}
{% endfor %}
</div>
Sorry, if there are some formatting issues, but on my side at least it produced the same output as above one-line-solution.
You will only have to adjust the metafield identifier in the first for-loop, other modifications are up to you.
Please use with caution, I give no guarantee that the code is error-free.
You could maybe try this one: https://www.npmjs.com/package/@novatize-mattheri/shopify-richtext-renderer?activeTab=readme
It works by creating React nodes allowing you to pass props (classNames, ids, etc...).
Is there no internal method within Shopify (specifically Shopify Flows) that allows you to export/output the richtext as HTML? The "metafield_tag" is not a known function in Flows, and the large manual "foreach" loop shown further down in this thread does not also parse in Flow - kicking up too many errors of invalid code.
Anyone following this for Flows - they have introduced allowing "Run Code" to run JS as an action step. (https://help.shopify.com/en/manual/shopify-flow/reference/actions/run-code) You can't import modules, but a custom built JS function could now be run as a step to format the input metafield JSON into some kind of HTML output.... in theory?!