Shopify CSRF Token — Every POST Form Needs authenticity_token (Here's Why)
Cross-Site Request Forgery (CSRF) is an attack where a malicious website tricks a logged-in Shopify customer into submitting a form they did not intend to submit — adding items to their cart, changing their account details, or triggering purchases. Shopify's built-in protection is the authenticity_token: a unique, session-bound token that must be present in every POST form for the request to be accepted. Omitting it either breaks form submission or leaves the action unprotected. Here is why it matters and how to add it correctly to every form.
What CSRF is and how it affects Shopify stores
A CSRF attack works as follows: a customer is logged into a Shopify store. They visit a malicious website. That website contains a hidden form that POSTs to the Shopify store's /cart/add endpoint, or the /account endpoint, with parameters the attacker controls. Because the customer is logged in, the browser sends their session cookies automatically with the request. Without CSRF protection, the Shopify store processes the request as if the customer submitted it intentionally. Shopify protects against this with authenticity_token — a token the malicious site cannot know because it is specific to the customer's session.
How to add authenticity_token to Liquid forms
For forms that use Shopify's {% form %} tag, the token is added automatically: {% form 'customer' %}...{% endform %}. You do not need to add it manually. For custom HTML forms that POST to Shopify endpoints, add the token as a hidden input: <form method='POST' action='{{ routes.cart_add_url }}'><input type='hidden' name='authenticity_token' value='{{ form.authenticity_token }}'> or use: <input type='hidden' name='authenticity_token' value='{{ content_for_header | authenticate_form_token }}>. The simplest approach is always using {% form %} tags for Shopify endpoint forms.
When custom HTML forms are needed and how to protect them
If you build a custom form outside of {% form %} — for example, a custom cart form, a custom login form, or an AJAX-based form — you need to include authenticity_token explicitly. For AJAX POST requests, include it in the request body or headers: const token = document.querySelector('[name=authenticity_token]').value; fetch('/cart/add.js', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Shopify-Customer-Token': token }, body: JSON.stringify(data) }). Alternatively, the Shopify cart AJAX endpoints (/cart/add.js, /cart/update.js) do not require CSRF tokens for JSON requests — only for traditional form submissions.
Which Shopify endpoints require CSRF protection
Traditional HTML form submissions (application/x-www-form-urlencoded or multipart/form-data) to Shopify endpoints require authenticity_token: /cart, /account, /account/login, /account/register, /contact, /challenge. JSON API calls (application/json Content-Type) to /cart/add.js, /cart/change.js, etc. do not require it — Shopify's CORS policy and the Content-Type requirement provide equivalent protection for these endpoints.
The fix: before and after
// CODE_COMPARISON
Frequently asked questions
- What happens if I submit a Shopify form without authenticity_token?
For forms that POST to Shopify-protected endpoints (/account, /contact, /challenge), the request returns a 422 Unprocessable Entity error and the form does not submit. For /cart endpoints, behavior varies — some cart actions accept the request without the token, which means they are protected by CORS rather than CSRF tokens for JSON requests. Always include the token for traditional form submissions.
- Does the {% form %} tag add authenticity_token for all form types?
Yes. The {% form %} tag supports form types: 'product', 'cart', 'customer', 'customer_login', 'customer_register', 'contact', 'new_comment', 'localization', 'currency', 'create_customer', and more. All automatically include a hidden authenticity_token input with the correct session-bound value. Use {% form %} for all Shopify endpoint forms unless you have a specific reason to build a custom HTML form.
- Are Shopify AJAX cart endpoints (/cart/add.js etc.) vulnerable to CSRF?
No. Shopify's AJAX cart endpoints (/cart/add.js, /cart/change.js, /cart/update.js, /cart/clear.js) require application/json Content-Type. Browsers enforce the Same-Origin Policy for requests with non-standard Content-Type headers — a cross-origin form cannot send JSON Content-Type without a CORS preflight. This preflight check rejects the request. The CSRF token is only required for traditional form submissions (application/x-www-form-urlencoded).
// SCAN_YOUR_CODE
Does your theme have this bug?
Paste your code. Syphio automatically detects and fixes this error and hundreds of others — in seconds.
Validate my Liquid →