Updating custom forms for CartThrob 10
CartThrob 10 signs protected form permissions and fixed values as one context-bound policy. Native CartThrob form tags generate it automatically. The migration rule is simple: put trusted permissions and fixed values on the native tag, keep ordinary customer inputs inside it, and do not build CartThrob’s internal hidden authority fields yourself.
Run CartThrob → Version Review → Form Reviews before editing. Rerun it after the changes and test the rendered storefront form.
Hand-built protected fields
An older custom form may create an internal field such as AUP, NTX, NSH, OTF, PR, SHP, or RLS directly. CartThrob 10 ignores those independently created values.
Old custom form:
<form method="post" action="{site_url}">
<input type="hidden" name="ACT" value="123">
<input type="hidden" name="entry_id" value="{entry_id}">
<input type="hidden" name="AUP" value="{encoded:yes}">
<label>Donation <input name="price"></label>
<button type="submit">Add donation</button>
</form>
CartThrob 10 form:
{exp:cartthrob:add_to_cart_form
entry_id="{entry_id}"
allow_user_price="yes"
return="store/cart"}
<label>Donation <input name="price" inputmode="decimal"></label>
<button type="submit">Add donation</button>
{/exp:cartthrob:add_to_cart_form}
Do not copy an internal hidden field from one rendered form to another. Let each native tag generate its own policy.
Put the product ID on the tag
A hidden entry_id is customer input. When a catalog add-to-cart form grants price, shipping, weight, exemption, entitlement, metadata, upload, expiry, or subscription authority, CartThrob must know the product while it renders the policy.
Old:
{exp:cartthrob:add_to_cart_form allow_user_price="yes"}
<input type="hidden" name="entry_id" value="{entry_id}">
<input name="price">
{/exp:cartthrob:add_to_cart_form}
Updated:
{exp:cartthrob:add_to_cart_form
entry_id="{entry_id}"
allow_user_price="yes"}
<input name="price">
{/exp:cartthrob:add_to_cart_form}
Use on_the_fly="yes" only for an intentional product that has no channel entry.
Update multi-add exemptions and product scope
Older multi-add examples posted reusable pNTX or pNSH fields. Remove them. Configure Tax Exempt Field and Shipping Exempt Field in the CartThrob Product Channel settings and store each decision on its product entry.
Old:
{exp:cartthrob:multi_add_to_cart_form}
<input type="hidden" name="entry_id[1]" value="12">
<input type="hidden" name="pNTX[1]" value="{encoded:yes}">
<input type="hidden" name="pNSH[1]" value="{encoded:yes}">
<input name="quantity[1]" value="1">
{/exp:cartthrob:multi_add_to_cart_form}
Updated server-priced catalog form:
{exp:cartthrob:multi_add_to_cart_form}
<input type="hidden" name="entry_id[1]" value="12">
<input name="quantity[1]" value="1">
{/exp:cartthrob:multi_add_to_cart_form}
If the catalog form also grants product-specific permissions, bind it to the exact trusted products displayed by the server:
{exp:cartthrob:multi_add_to_cart_form
authorized_entry_ids="12|34|56"
allow_user_price="yes"}
<!-- Render rows 12, 34, and 56 from the same trusted catalog query. -->
{/exp:cartthrob:multi_add_to_cart_form}
Never build authorized_entry_ids from submitted customer data.
Authorize editable cart money on the same form
CartThrob ignores submitted item price, shipping, or weight unless the same update_cart_form explicitly grants the matching permission.
Old, misleading control:
{exp:cartthrob:update_cart_form}
<input name="price[{row_id}]" value="{item_price_numeric}">
{/exp:cartthrob:update_cart_form}
Updated, when customer editing is intentional:
{exp:cartthrob:update_cart_form allow_user_price="yes"}
<input name="price[{row_id}]" value="{item_price_numeric}" inputmode="decimal">
{/exp:cartthrob:update_cart_form}
If customers should not control the value, remove the editable input. Equivalent permissions are allow_user_shipping and allow_user_weight.
Checkout forms and custom JavaScript
Keep fixed price, shipping, tax, gateway, member, order, and Vault choices on checkout_form. Built-in gateway fields handle the supported quote flow. Custom Stripe Payments, PayPal Standard, or PayPal Advanced JavaScript must finalize the quote after the last coupon, address, shipping, gateway, or amount change and submit the resulting checkout_quote_id through that gateway’s supported integration.
Do not create or capture a provider payment from an earlier cart total. Do not replace only hosted gateway-field HTML when switching gateways; rebuild or reload the complete checkout so CartThrob and the provider initialize together.
PHP, Twig, external templates, and extensions
Review the final rendered HTML, not only the source template. Confirm that custom code:
- uses a native CartThrob form tag for the action;
- does not manufacture, copy, or alter CartThrob internal protected fields;
- preserves the product, cart, shipping, gateway, member, and Vault context;
- applies intended pricing changes before checkout quote preparation;
- does not change raw validation data after CartThrob authenticates it; and
- produces the same authorized behavior for every possible dynamic parameter value.
cartthrob_checkout_action_start and cartthrob_pre_process run too late for new quote-affecting decisions. Use cartthrob_checkout_quote_options for deterministic supported checkout-option changes, and treat cartthrob_checkout_quote_finalized as observation only.
Verification checklist
After purging all external caches:
- Rerun the complete Form Reviews scan.
- Test ordinary, exempt, donation, gift-certificate, subscription, package, and multi-add products used by the site.
- Test cart price/shipping/weight editing only where intentionally enabled.
- Confirm catalog prices, tax, and shipping match server-side product configuration unless that exact form grants an override.
- Test every selectable gateway and confirm provider amount, currency, reference, and saved Order total agree.
- Check extensions and external templates that the automatic scanner could not fully inspect.
A Reviewed mark records the manual check; it does not make an incompatible form valid.