Open Dawn's main-product section and look at two neighbouring blocks: the title prints {{ product.title | escape }}, and the description prints {{ product.description }} with no filter on it at all.
That pair is the decision.
Shopify's Liquid reference lists product.description as the product's description HTML, the same value as product.content. The strip_html docs even demonstrate a heading and paragraphs coming out of that field unchanged. Shopify's storefront security guidance is equally plain: Liquid leaves output unescaped, and the filter reference has no sanitize filter.
When merchandising pastes supplier HTML, a PIM export, or an embed into the admin description editor, Dawn ships that string as part of the page. Horizon's product-description block does the same when its rich-text setting is empty: product.description is the fallback text, printed without escape.
That output line is the description pipe.
Open the live product template
Do this on an unpublished theme copy, then on the published theme if they differ.
- Open the product JSON template the live PDP uses.
- Find the section or block that claims to show the description.
- Open that Liquid file and write down the exact output line.
- Note whether
escape,strip_html,metafield_tag, ormetafield_textsits on the description pipe. - Note the fallback: Dawn's empty check is
product.description != blank. Horizon falls back toproduct.descriptionwhen the block's rich-text setting has no visible text.
Write the filename next to the line.
A premium theme often hides the same object behind a snippet name, so the map is wrong until you open the snippet. Then walk one product as a shopper and view source on the description region. If the published theme and the unpublished copy differ, write both output lines, because a preview that still uses Dawn's empty check can hide a Horizon fallback you already shipped.
If you see the headings and lists you typed in admin, the description pipe is raw HTML. Encoded <p> in the page means someone already escaped it, and a div.metafield-rich_text_field means you are already on metafield_tag.
You only need the pipe and the owner.
Three description pipes
Most requests arrive as a layout brief about a joke, a regimen, or a trust line when sixty products share one template. Where does the story sit so the buy box stays clean?
Those are layout questions sitting on a quieter choice: which string is allowed to become HTML.
Three description pipes cover almost every live catalogue.
Raw Liquid keeps {{ product.description }} (or Horizon's fallback) and accepts whatever the admin product editor, a bulk import, or an app with product write access saved. Headings and lists stay as markup on the shopper page, and script-shaped tags or broken paste survive with them. That is the description pipe Dawn ships, and Horizon uses the same field as fallback text when its rich-text setting is empty.
Name those writers first.
Rich-text metafield plus metafield_tag moves formatted story off the native field. Shopify documents metafield_tag as the type-specific HTML wrapper, and a rich_text_field renders as a div with class metafield-rich_text_field, so merchandisers edit a field you named while the native description stays short, empty, or plain. Apps that can write products still cannot write that metafield unless you also grant them the definition.
strip_html plus escape keeps the native field as words: strip_html removes tags and escape turns leftover < and & into text, so the shopper sees copy rather than markup. Formatted story has to live somewhere else, or it becomes a paragraph. Put strip_html first so leftover angle brackets are characters, not a second chance for a tag to survive.
A fourth idea shows up in developer threads: dump the string through json, sanitise it in the browser, then inject it. That path takes the description out of the first HTML response and adds a script to every product page. Shopify's security guidance still prefers an allowlist when the value is saved, plus the Liquid filters that already exist.
Treat JavaScript as a written exception.
Score writers and what shoppers see
Score each product type, not the theme as a whole. A gift card, a bundle, and a hero SKU often deserve different description pipes. Write the score on the same sheet as the filename, because a later theme edit will otherwise reuse the hero SKU decision on a supplier-fed variant.
Who may write the field. Staff in the product editor are one writer, a PIM sync is another, and any installed app with product write access is a third. Raw Liquid is only honest when you can name those writers and live with their HTML.
What the shopper must see. Short formatted copy (two headings, a list, bold claims) wants markup. A legal line or a supplier spec sheet often wants words only.
Whether the template is shared. One JSON product template for a whole catalogue is normal. Shared structure plus per-SKU sentences belongs in metafields. Shared structure plus one HTML dump in product.description is how sixty pages start to read identical until the dump, then unreadable.
Whether the buy box stays first. Trust and story that climb above price and add-to-cart fight the form, and the description pipe does not move that form. A metafield printed in a block below the form can carry the story instead.
Whether you will still be able to edit it next quarter. Canvas and Sidekick can add sections. A new description block that prints the native field raw undoes a metafield decision unless the owner catches it in preview. Preview that block on an unpublished theme, and write the second filename on the same sheet if the new block also prints product.description.
The description pipe
Choose a description pipe
Use this matrix on the live template, then fill the last column with a name.
| Pipe | Liquid on the theme | What the shopper gets | Who should edit it | Choose this when |
|---|---|---|---|---|
| Raw native description | {{ product.description }} | Admin HTML, including tags you did not type | A named merchandiser; no unreviewed app writes | Trusted staff, short formatted copy, writers listed |
| Rich-text metafield | {{ product.metafields.namespace.key | metafield_tag }} | Formatted story in a typed wrapper | Merchandiser on that metafield definition | Shared template, per-SKU story, native field kept boring |
| Plain native description | {{ product.description | strip_html | escape }} | Words only | Anyone who can write products; markup cannot leak | PIM or supplier HTML, or unknown writers |
| Words from a metafield | {{ product.metafields.namespace.key | metafield_text }} | Plain text from a typed field | The metafield owner | JSON events, meta tags, or a line that must stay text |
metafield_tag on a multi_line_text_field still wraps the string and skips an allowlist. If that metafield stores pasted HTML, you are back on a raw description pipe with a different name. Prefer rich_text_field when the merchandiser needs headings and lists, because the admin control already limits the marks they can add. Printing .value yourself skips the typed wrapper and can dump markup the same way product.description does, so treat that output line as a fourth pipe you did not mean to open.
Shopify's security note for HTML that app users write is to sanitise on save with an allowlist. A custom app you own can do that before it writes a metafield, because a theme cannot invent that allowlist in Liquid.
The save step owns the allowlist.
What to pick for this catalogue
Trusted merchandiser, short formatted copy
Leave Dawn or Horizon on raw product.description if the only writers are people you can name, and the field stays a short story. Add the owner next to the description pipe, then recheck after every app install that asks for product write access.
View source once a quarter on a product you did not edit yourself. If new tags appear, a writer you missed is already on the field, and the description pipe you named in January is no longer the one shoppers receive.
List that writer before you change the Liquid.
Sixty SKUs on one template
Keep the JSON template and the buy box stable, then create one rich_text_field for the sentence that changes (joke, regimen, origin) and print it with metafield_tag under the form. Shared trust lines (returns, lab testing, years trading) belong in a section the merchandiser can hide on products that do not need them.
47 Skin's UK and US shops share one Online Store 2.0 theme. Merchandisers write launch copy, bundles, and regimen notes in sections and metafields they already own, while the Liquid file holds the layout those campaigns sit inside.
Campaign people edit the sentences.
Do not pour sixty personalities into product.description and hope the shared template will sort them. The template will print the dump, and shoppers will scroll past it.
A shared JSON template stays maintainable when the Liquid file owns structure and the metafield owns the sentence that changes, including jokes, regimen steps, and origin lines that would otherwise inflate the native field.
Supplier, PIM, or unknown app HTML
Print the native field as {{ product.description | strip_html | escape }}. Move any formatted story you still want into a metafield the sync cannot write, or sanitise on the way in if you own the app that writes it.
This is the description pipe that looks harsh in a design review and calm in a peak-traffic week. Markup from a feed cannot become a storefront script if it never leaves the field as tags. If you still want a short formatted story on those SKUs, put it in a metafield the sync is not allowed to write, and leave the native field as words.
Canvas or Sidekick just added a description block
Open the new block the way you opened Dawn, and if it prints product.description raw you have a second description pipe. Either match the pipe you already chose, or remove the block before publish.
Preview on an unpublished theme, then name who rolls it back. A second description pipe on a gift-set or bundle template is the usual reason a feed paste returns on a landing page after you cleaned the main PDP.
Name the owner
A description pipe without a name becomes raw Liquid again the next time someone pastes into admin. The four lines below are the whole record: if a line is blank, the next theme edit will guess, and the guess is almost always an unfiltered product.description.
Write four lines and keep them with the theme notes, on the same page as the unpublished-theme checklist:
- Print line: file, filter chain, fallback
- Writers: staff role, PIM, apps with product write access
- Editor: the person who may change HTML or the metafield
- Rollback: the person who reverts a Canvas or theme-editor change that reprints the native field
Finish the description pipe on one SKU before you restyle the product page. Duplicate landmarks or injected markup can wait until the pipe has a filename, a filter chain, and a named editor, because the same HTML will return after a restyle if the writers are still open.
The title line in Dawn already made a choice, and the description line is waiting for yours.



