A configured line carries two kinds of information: the selections — Room, Colour, Width, Height — and a handful of internal fields the app needs to price and reconcile the order.
Out of the box Shopify shows your customers neither: its default order confirmation only lists line properties for gift cards, and the staff New order email has no properties loop at all. So this article is two jobs — get the selections in front of the people who need them, and keep the internal fields away from the people who do not.
The internal fields
You will see these on a line in your Shopify admin:
_config_id: b7ea8d2c-f5af-4cc2-ad40-089a8c6fe237
_price: 2410.77
_sig: ce6c3284d248ea95f6823aa414eb05a086000f6a…
_ruleset_version: 1
They are not decoration. _price and _sig are the signed price — the pair
that lets Shopify charge the configured amount and refuse a tampered one.
_config_id links the line to its full specification. _ruleset_version
records which version of your pricing produced it, so a price can be explained
months later.
They cannot be removed from the admin. Shopify shows every line item property on the order page, and the app genuinely needs these to reach Shopify. That is the trade: the admin is your workspace, and Shopify assumes you want to see everything that arrived with the order.
They are already hidden from your customers. A property whose name begins with an underscore is hidden by Shopify from the cart, the checkout and the order status page. That is the whole reason for the prefix.
Emails are the one place worth checking, because notification templates are yours to edit and an old or customised one may not filter them.
The order confirmation shows nothing — here is why
Shopify's current default Order confirmation template does have a
properties loop, correctly filtered, so _sig and _price can never reach a
customer. But look at what it is wrapped in:
{% if line.gift_card and line.properties["__shopify_send_gift_card_to_recipient"] %}
{% for property in line.properties %}
{% assign property_first_char = property.first | slice: 0 %}
{% if property.last != blank and property_first_char != '_' %}
...
It only runs for gift cards. On every other product the loop is skipped entirely, so a customer who spent ten minutes configuring a blind gets a confirmation email listing the product name and nothing they chose.
The fix is to widen that one condition rather than paste anything new — the markup, the classes and the underscore filter inside it are already Shopify's own, so the result looks native and stays safe.
Settings → Notifications → Order confirmation → Edit code. Find:
{% if line.gift_card and line.properties["__shopify_send_gift_card_to_recipient"] %}
Replace with:
{% if line.properties.size > 0 %}
Do it everywhere it appears
Shopify inlines the same line-item block once per delivery scenario — split
carts, delivery agreements, bundles, and the ordinary case — so that condition
appears several times in the template. Use your browser's Find
(⌘F / Ctrl-F) for gift_card and and change every one, or the details will
show on some orders and not others depending on how the order shipped.
One of them reads component.gift_card and component.properties[…] rather than
line. — that is the bundle branch. Change it the same way, to
{% if component.properties.size > 0 %}.
If your template has no loop at all
Older or heavily customised templates may not have it. Paste this inside the line-item loop, under the variant:
{% for property in line.properties %}
{% assign first_char = property.first | slice: 0 %}
{% if property.last != blank and first_char != '_' %}
<div class="order-list__item-property">
<dt>{{ property.first }}:</dt>
<dd>{{ property.last }}</dd>
</div>
{% endif %}
{% endfor %}
first_char != '_' is the part that matters — it is what keeps _sig and
_price out of a customer's inbox. Keep it even if you change everything else.
The same applies to Shipping confirmation, Order invoice and the Draft order invoice. Each is a separate template.
Sending the SKU too
If you have set an answer's SKU and a separator under Settings → Orders and
links, the combined code arrives as a visible property named SKU, so the loop
above already prints it. To pull it out on its own instead:
{% assign line_sku = line.properties['SKU'] %}
{% if line_sku != blank %}<div>Code: {{ line_sku }}</div>{% endif %}
Staff notifications
The New order email your team gets is a different template, and it is worse: it has no properties loop at all. Not gated like the customer one — simply absent. Staff get a product title, a variant, a SKU and a price, and nothing about the specification they are supposed to make.
Settings → Notifications. Two separate things live under the staff section:
- Who gets them. Add each address under the staff order notification
settings. These are plain addresses — a recipient does not need a Shopify
account, so
workshop@or a shared inbox works. - What they say. Open New order and Edit code.
Find this, which appears in every line-item block:
{% if line.sku != blank %}
<span class="order-list__item-variant">SKU: {{ line.sku }}</span>
{% endif %}
Paste directly after its {% endif %}:
{% for property in line.properties %}
{% assign first_char = property.first | slice: 0 %}
{% if property.last != blank and first_char != '_' %}
<br><span class="order-list__item-variant">{{ property.first }}: {{ property.last }}</span>
{% endif %}
{% endfor %}
The order-list__item-variant class is the template's own, so this inherits the
existing styling rather than fighting it.
Like the customer template, that SKU block is inlined several times for the
different delivery scenarios. Find (⌘F / Ctrl-F) for SKU: {{ line.sku }} and
add the loop after each one. Some blocks use component. instead of line. —
change the loop to match, or those lines will silently print nothing.
Keeping the reference on an internal email
The filter hides everything internal, which is right for a customer and
sometimes wrong for staff: _config_id is how you find a line's full
specification when somebody rings up about it. On an internal template you can
let that one through:
{% assign config_id = line.properties['_config_id'] %}
{% if config_id != blank %}
<br><span class="order-list__item-variant">Reference: {{ config_id }}</span>
{% endif %}
Name it explicitly like that rather than loosening the underscore filter — that
way _sig and _price stay out. One long hash nobody can act on is exactly the
noise this article exists to remove.
Only on staff templates. A staff notification is internal; an order confirmation is not.
Packing slips
Packing slips are edited separately, under Settings → Shipping and delivery → Packing slips. The same snippet works there, and this is usually the one worth doing first — it is what the person actually making the product reads.
Testing it
Shopify can send you a preview: on any notification template, use Preview or Send test email. A test email uses sample data rather than a real order, so to see your own selections, place a real test order and resend its confirmation from the order page (More actions → Resend order confirmation).
Check the email for anything beginning with an underscore. If you see one, the filter is missing from that template.
