How to Add Custom Fields to a Shopify Product Page (No App Needed)
You want customers to type an engraving, choose a gift-wrap option or add a note on the product page, and you want that information to appear on the order. You don't need an app for this. Shopify has a built-in feature called line item properties, and in an Online Store 2.0 theme you can add one in a few minutes with a Custom Liquid block.
How it works: any form field named properties[Something] that's submitted with the add-to-cart form gets attached to that cart line. It shows up in the cart, at checkout, in the order admin and in order notification emails, with no extra setup.
Step 1: Add a Custom Liquid block to the product page
- Go to Online Store → Themes. Click … → Duplicate on your live theme and work on the copy.
- Click Customize on the copy, and pick Products → Default product from the template dropdown at the top.
- In the Product information section, click Add block → Custom Liquid, and drag the block above the Buy buttons.
Step 2: Paste the field code
The key detail is the form attribute. In Dawn and most OS 2.0 themes, the add-to-cart form isn't wrapped around your block, so the input has to point to the form by its ID. In Dawn that ID is product-form- followed by the section ID:
<div class="product-form__input">
<label class="form__label" for="engraving-{{ section.id }}">Engraving (max 20 characters)</label>
<input
type="text"
id="engraving-{{ section.id }}"
name="properties[Engraving]"
form="product-form-{{ section.id }}"
maxlength="20"
class="field__input"
>
</div>
Save, add the product to the cart on the preview, and the cart should show Engraving: your text under the product.
If nothing shows in the cart: find your theme's form ID
Not every theme uses Dawn's ID. Open the product page preview, right-click the Add to cart button and choose Inspect. Find the enclosing <form tag and copy its id. It might be something like product-form-template--123456__main, which is product-form-{{ section.id }} with the section ID filled in. Use the matching Liquid in your form="..." attribute.
Step 3: Other field types
Dropdown
<label for="gift-wrap-{{ section.id }}">Gift wrap</label>
<select id="gift-wrap-{{ section.id }}" name="properties[Gift wrap]" form="product-form-{{ section.id }}">
<option value="No">No</option>
<option value="Yes - red paper">Yes, red paper</option>
<option value="Yes - gold paper">Yes, gold paper</option>
</select>
Checkbox
<label>
<input type="checkbox" name="properties[Include gift note]" value="Yes" form="product-form-{{ section.id }}">
Include a handwritten gift note
</label>
An unchecked checkbox sends nothing, so the property simply won't appear on the order.
Longer text
<textarea name="properties[Message]" form="product-form-{{ section.id }}" rows="3" maxlength="200"></textarea>
Making a field required
Add the required attribute to the input. Because it's associated with the product form, the browser checks it when the customer clicks Add to cart:
<input type="text" name="properties[Engraving]" form="product-form-{{ section.id }}" required>
Test this on your theme. Some themes add to cart with custom JavaScript that skips the browser's validation. If the product goes into the cart with the field empty, the theme's product form script needs a small change to check form.reportValidity() before submitting.
Show the field only on some products
A Custom Liquid block on the default template shows on every product. Two clean ways to limit it:
- A separate product template: in the customizer, create a new template (e.g. "product.engraving"), add the block only there, then assign that template to the right products in each product's admin page.
- A condition in the block: wrap the code in a tag check:
{% if product.tags contains 'engravable' %}
<!-- field code from Step 2 -->
{% endif %}
Hidden properties
Property names that start with an underscore, like properties[_source], are treated as private: themes usually don't show them in the cart, and Shopify hides them at checkout, but they're still saved on the order. That's useful for internal data like a configurator reference or a campaign code.
Showing properties in the cart
Dawn and most modern themes already display properties in the cart. If yours doesn't, the cart template needs a loop like this inside the line item:
{% for property in item.properties %}
{% unless property.last == blank or property.first contains '_' %}
<p>{{ property.first }}: {{ property.last }}</p>
{% endunless %}
{% endfor %}
The check on the first character skips the hidden properties described above.
Limitations to know about
- Line item properties don't change the price. To charge extra for engraving, the usual options are a separate "Engraving" product added to the cart alongside it, product variants, or Shopify Functions / an app for true price add-ons.
- The same product with different properties becomes separate cart lines. That's expected: two mugs with different engravings are two lines.
- File uploads (
type="file") technically work with line item properties, but theme support varies, and an app is often the more reliable choice for uploads.
Written by
Sameer Hussain, Senior Shopify & WordPress Engineer
Top Rated on Upwork and the Shopify & WordPress engineer at Inceptus Digital. Short on time, or not comfortable editing code? Send me your URL and the error on Upwork and I'll tell you how I'd fix it before you commit.
Need Custom Engineering or Growth Systems?
Our team helps ambitious brands build bespoke software, AI integrations, and high-conversion platforms.