Convert rich text into readable HTML format

Jeffrey13
New Member
4 0 2

Jeffrey13_0-1676460361017.png

 

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.

 

Replies 23 (23)

thiag73
Shopify Partner
2 0 2

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

 

Lokiii
Shopify Partner
8 0 8

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!

CBMarketing
Shopify Partner
5 0 2

Hey mate, did you find a solution for this, I have the same issue.

Lokiii
Shopify Partner
8 0 8

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! 

CBMarketing
Shopify Partner
5 0 2

Thanks mate, I appreciate you taking the time to answer.

CBMarketing
Shopify Partner
5 0 2

 

 

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;
}
rb_sss
Shopify Partner
7 0 2

Appreciate this! Worked for our use case too, thank you!

mcqua007
Shopify Partner
11 0 9

Here is a package for rendering shopify rich text to html.

I have used it and it works great. Super easy.

cjfritz9
Shopify Partner
6 0 0

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.

mcqua007
Shopify Partner
11 0 9

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.

mcqua007
Shopify Partner
11 0 9
cjfritz9
Shopify Partner
6 0 0

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.

mcqua007
Shopify Partner
11 0 9

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! 🙂

LFAR
Shopify Partner
2 0 1

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';

 

 
The rich text needs to be parsed before being converted: 
 

 

        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 🙂

 

Figumari
Shopify Partner
5 0 2

Here is a package for this purpose: shopify-rich-text-renderer

Like @Lokiii  mentioned before, it would be nice to have an official serialiser.

Lokiii
Shopify Partner
8 0 8

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' },
  ]}
/>

 

nerioslamaj
Shopify Partner
3 0 0

Great solution for TS, thanks for sharing 😄

cjfritz9
Shopify Partner
6 0 0

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 

Lokiii
Shopify Partner
8 0 8

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 🎁 🙂

KIWI_Dev
Visitor
1 0 2

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.

novatize-mather
Shopify Partner
1 0 0

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...).

ChunkySteveo
Shopify Partner
17 0 5

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.

ChunkySteveo
Shopify Partner
17 0 5

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?!