Step 1 – Open the right file in your theme.
In Shopify admin go to Online Store → Themes.
On your “Latest” / Current theme, click … → Edit code.
In the left sidebar, under Sections, open:
main-cart-footer.liquid
or, if you don’t see that, open main-cart-items.liquid.
Dawn v15+ usually puts the cart note in main-cart-footer.liquid.
Step 2 – Give the note field an ID (if it doesn’t have one)
Inside main-cart-footer.liquid, look for something like this:
<textarea name="note" ... > {{ cart.note }} </textarea>Change it to:
<textarea id="CartSpecialInstructions" name="note" {{ block.shopify_attributes }} > {{ cart.note }} </textarea>If there’s already an id (like Cart-note), you can keep it, just remember what it is and use that in the script.
Step 3 – Add an error message container
Right under that <textarea>, add:
<div id="CartNoteError" style="display:none; color:red; font-size:0.9rem; margin-top:0.25rem;"> Please enter a note before continuing to checkout. </div>
You can style that with theme classes later; this just makes it work.
Step 4 – Add the validation script in the same section
Scroll to the bottom of main-cart-footer.liquid, and just before the closing {% schema %} (or at the bottom if there’s no schema), paste this:
<!--RP Tech Support - Added Required Fields -->
<script>
document.addEventListener('DOMContentLoaded', function () {
var noteField = document.getElementById('Cart-note');
var errorEl = document.getElementById('CartNoteError');
if (!noteField || !errorEl) return;
function showError(message) {
errorEl.textContent = message;
errorEl.style.display = 'block';
}
function hideError() {
errorEl.style.display = 'none';
}
var checkoutButtons = document.querySelectorAll(
'button[name="checkout"], button[id="checkout"], button[type="submit"][name="checkout"]'
);
checkoutButtons.forEach(function (btn) {
// NOTE: third argument "true" = capture phase
btn.addEventListener('click', function (event) {
var value = noteField.value.trim();
if (!value) {
// Block default form submit
event.preventDefault();
// Block any other click handlers on this button
event.stopImmediatePropagation();
showError('Please enter a note before continuing to checkout.');
noteField.focus();
} else {
hideError();
// If valid, do NOT preventDefault — default submit + other JS still run
}
}, true);
});
noteField.addEventListener('input', function () {
if (noteField.value.trim()) {
hideError();
}
});
});
</script>
That will:
Block checkout when the cart note is empty.
Show a red message under the note.
Clear the message as soon as the customer starts typing.
Step 5 – Test in the theme editor
In Online Store → Themes, click Customize on your current theme.
Use the top dropdown to go to the Cart page.
Try:
Leaving the note blank → click Checkout → you should see the error and stay on the cart.
Typing something → click Checkout → you should go to Shopify Checkout.
If you’re using the cart drawer too
The above only enforces it on the cart page. If your theme uses the cart drawer, and you want to force notes there too, you’ll need a similar script in cart-drawer.liquid targeting that drawer’s textarea and checkout button.
If you paste:
A small snippet of your
main-cart-footer.liquidaround the note field, orA screenshot of your cart section in the theme editor,
Update to Only Force the Note for Zero Dollar Items
Here is a Nice tweak 👌 — we’ll only force the note if any cart line is free (0.00).
We’ll use Liquid to detect free items, then JS to enforce the note only in that case.
1️⃣ Add a “has free item” flag in main-cart-footer.liquid
In Edit code → Sections → main-cart-footer.liquid, somewhere above your <textarea> or above the script, add this Liquid snippet:
{%- assign has_free_item = false -%} {%- for item in cart.items -%} {%- if item.final_line_price == 0 -%} {%- assign has_free_item = true -%} {%- endif -%} {%- endfor -%} <div id="CartHasFreeItem" data-has-free-item="{{ has_free_item }}" style="display:none;" ></div>item.final_line_price == 0checks for any line whose total is 0 (e.g. freebies, 100% discounts).We store that as
data-has-free-item="true"or"false".
Leave that div hidden; it’s just for JS.
2️⃣ Keep the note + error message as before
Make sure your note textarea and error container look like this (or similar):
<textarea id="CartSpecialInstructions" name="note" {{ block.shopify_attributes }} > {{ cart.note }} </textarea> <div id="CartNoteError" style="display:none; color:red; font-size:0.9rem; margin-top:0.25rem;"> Please enter a note before continuing to checkout. </div>(If you already added this from the previous step, you’re good.)
3️⃣ Replace the old script with this one
Still in main-cart-footer.liquid, at the bottom of the file (before {% schema %}), remove the previous script I gave you and paste this updated version:
<script>
document.addEventListener('DOMContentLoaded', function () {
var noteField = document.getElementById('Cart-note');
var errorEl = document.getElementById('CartNoteError');
if (!noteField || !errorEl) return;
function showError(message) {
errorEl.textContent = message;
errorEl.style.display = 'block';
}
function hideError() {
errorEl.style.display = 'none';
}
var checkoutButtons = document.querySelectorAll(
'button[name="checkout"], button[id="checkout"], button[type="submit"][name="checkout"]'
);
checkoutButtons.forEach(function (btn) {
// NOTE: third argument "true" = capture phase
btn.addEventListener('click', function (event) {
var flagEl = document.getElementById('CartHasFreeItem');
var hasFreeItem =
flagEl && flagEl.dataset.hasFreeItem === 'true';
// If no free items, don't enforce; let other JS do its thing
if (!hasFreeItem) {
hideError();
return;
}
var value = noteField.value.trim();
if (!value) {
// Block default form submit
event.preventDefault();
// Block any other click handlers on this button
event.stopImmediatePropagation();
showError('Please enter a note before continuing to checkout.');
noteField.focus();
} else {
hideError();
// If valid, do NOT preventDefault — default submit + other JS still run
}
}, true);
});
noteField.addEventListener('input', function () {
if (noteField.value.trim()) {
hideError();
}
});
});
</script>
What this does now
If no line item is free (0.00) → checkout works normally, even with an empty note.
If at least one line item is free → note becomes required; empty note blocks checkout and shows the red message.
4️⃣ Quick test checklist
On the cart page:
No free items in cart → leave note blank → click Checkout → you should go through.
Add a free item (or discount to 100% off) → leave note blank → click Checkout → you should see the error and stay on the cart.
With free item in cart, fill in note → click Checkout → should go through.
Item that are Zero vs Total line being Zero
If you want it to be “price 0 per unit” instead of “line total 0”, here is the tweak the Liquid to check item.final_price instead of item.final_line_price.
You only need to change the Liquid flag block; the JS stays the same.
1️⃣ Update the “has free item” flag to use unit price
In main-cart-footer.liquid, replace the previous flag block with this:
{%- assign has_free_item = false -%} {%- for item in cart.items -%} {%- if item.final_price == 0 -%} {%- assign has_free_item = true -%} {%- endif -%} {%- endfor -%} <div id="CartHasFreeItem" data-has-free-item="{{ has_free_item }}" style="display:none;" ></div>Now it checks
item.final_price == 0→ per-unit final price is free, even if quantity > 1.
Leave your textarea, error div, and script exactly as they were from the last step:
<textarea id="CartSpecialInstructions" name="note" {{ block.shopify_attributes }} > {{ cart.note }} </textarea> <div id="CartNoteError" style="display:none; color:red; font-size:0.9rem; margin-top:0.25rem;"> Please enter a note before continuing to checkout. </div>And the script:
<script> document.addEventListener('DOMContentLoaded', function () { var noteField = document.getElementById('CartSpecialInstructions'); var errorEl = document.getElementById('CartNoteError'); if (!noteField || !errorEl) return; var checkoutButtons = document.querySelectorAll( 'button[name="checkout"], button[id="checkout"], button[type="submit"][name="checkout"]' ); function showError(message) { errorEl.textContent = message; errorEl.style.display = 'block'; } function hideError() { errorEl.style.display = 'none'; } checkoutButtons.forEach(function (btn) { btn.addEventListener('click', function (event) { var flagEl = document.getElementById('CartHasFreeItem'); var hasFreeItem = flagEl && flagEl.dataset.hasFreeItem === 'true'; // If there are no free items by unit price, don't enforce the note if (!hasFreeItem) { return; } var value = noteField.value.trim(); if (!value) { event.preventDefault(); showError('Please enter a note before continuing to checkout.'); noteField.focus(); } else { hideError(); } }); }); // Clear error while typing noteField.addEventListener('input', function () { if (noteField.value.trim()) { hideError(); } }); }); </script>2️⃣ Quick sanity check
Item at $0.00 per unit (no matter the quantity) → note required.
Discounted to $0.00 per unit via automatic discount/code → note required.
All items have unit price > 0 → note optional.
