Shopify AJAX API
The Shopify theme endpoints Smootify answers on a Webflow site (/cart.js, /cart/add.js, /products/{handle}.js and more), what each one supports, how to start app code after Smootify, and how to turn the emulation off.
Apps written for Shopify themes call the theme’s AJAX endpoints, such as /cart.js or /cart/add.js. A Webflow site has no Shopify theme, so Smootify answers these calls on the page itself, from Shopify’s Storefront API, with the same response shape a theme gets. The cart they change is the page’s cart. This page lists what is answered and how to make an app’s code work with it.
How it works
- What is intercepted.
fetchandXMLHttpRequestcalls to the site’s own address, and to the store’s.myshopify.comaddress, whose path is one of the endpoints below. Every other request goes out unchanged. - Same cart as the page. A change through
/cart/add.js,/cart/update.js(or their.jsonforms),/cart/change.jsor/cart/clear.jsgoes through Smootify’s cart: the page’s cart updates as if the visitor had used it, andsmootify:cart_updatedgoes out (withsmootify:added_to_cartfor an addition). - Filters apply. Lines added with
/cart/add.jsgo through theadd_to_cart_linesfilters, with noelementin the context. - The request address. As on Shopify, each answer carries the address of the request:
response.urlforfetch(its clones too),responseURLforXMLHttpRequest. Some app snippets tell an addition from a read by it. window.Shopify. Smootify also setsShopify.shop,Shopify.locale,Shopify.country,Shopify.routes.rootandShopify.theme("Smootify"), which theme scripts read.Shopify.actions.updateCart. The cart action of Shopify’s storefront web components, such as the Shopify Inbox chat. Smootify defineswindow.Shopify.actions.updateCart(payload)unless the page already has one:linesin the Storefront API shape (merchandiseIdadds a line, theidof a/cart.jsonitem key sets its quantity,0removes it),discountCodesreplaces the codes,notesets the note. It answers{ cart, userErrors }, with the cart in the Storefront API shape and line ids it accepts back. Smootify also writes Shopify’scartcookie, which these components read.
The endpoints
| Endpoint | Status | Notes |
|---|---|---|
GET /cart.js, /cart.json | answered | the page’s cart |
POST /cart/add.js, /cart/add, /cart/add.json | answered | form data or JSON, items[], properties, selling_plan; sections ignored |
POST /cart/update.js, /cart/update.json | answered | updates by id, key or position, note, attributes |
POST /cart/change.js | answered | id or line, quantity, properties |
POST /cart/clear.js | answered | |
GET /products/{handle}.js, .json | answered | from Smootify’s cache when it has the product |
GET /products.json | first page | limit up to 250 (default 30); page above 1 answers an empty list |
GET /collections/{handle}/products.json | first page | limit up to 250 (default 50) |
GET /recommendations/products.json | answered | product_id, limit up to 10, intent related (default) or complementary |
GET /variants/{id}.json | answered | { product_variant }, as the Online Store answers |
GET /search/suggest.json | answered | q, resources[type], resources[limit] up to 10: products, collections, pages, articles, queries |
POST /localization | answered | country_code, language_code change the market; no redirect |
GET /cart/shipping_rates.json, POST /cart/prepare_shipping_rates.json | not supported | 422, with Shopify’s error shape: shipping rates exist only at checkout |
/apps/*, /a/*, /community/*, /tools/* | not answered | see below |
The same list is in the Reference: AJAX API.
products.json and collections/{handle}/products.json answer only their first page, because the Storefront API pages through results in another way.
Start the app’s code after Smootify
Smootify loads as an asynchronous module. A plain script at the end of the page runs before it: its /cart.js calls reach Webflow, which answers 404, and a snippet that wraps window.fetch there never sees /cart/add.js. Two ways to wait:
- Change the snippet’s type to
smootify-load. Smootify runs it right aftersmootify:loaded. Adddata-type="module"if it was a module; its other attributes are kept. - Start it from a
smootify:loadedlistener registered in the Head code, before Smootify finishes loading. The event fires once per page: code added later, by a tag manager for example, does not see it.
<script type="smootify-load">
fetch("/cart/add.js", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items: [{ id: 44012345678901, quantity: 1 }] }),
}).then((response) => response.json());
</script>More in Run your scripts with Smootify. For designers adding an app’s snippet, see Shopify apps on a Webflow site.
App proxy paths
Some Shopify apps call their own server through the store, on paths such as /apps/…, /a/…, /community/… or /tools/…. These app proxies need Shopify’s Online Store. Smootify does not answer them: the calls reach the network as they are, and a Webflow site answers 404. On a .webflow.io address, the debugger names the calls.
Two exceptions keep working: Shopify Inbox’s /apps/ba-rev gets an empty answer, and the consent request to /api/unstable/graphql.json goes to the store.
An app that needs its proxy does not work on a Webflow site.
Turn it off
When another script on the page answers these endpoints, turn Smootify’s emulation off in the options:
<script>
window.SmootifyUserOptions = { ...(window.SmootifyUserOptions || {}), ajaxApi: false };
</script>Set it before the Smootify loader script.
Where people get stuck
Check these first
- An app gets a 404 for
/cart.js. Its code ran before Smootify. Change its script type tosmootify-load. - An app that wraps
fetchnever sees additions. It wrappedfetchbefore Smootify did. Start it withsmootify-load. - An app calls
/apps/…and fails. App proxy paths need Shopify’s Online Store and are not answered. - A product list from
products.jsonstops after the first page. Only the first page is answered. Raiselimit, up to 250. - Shipping rates never load. Shipping rates exist only at checkout: the endpoints answer
422.
Coming from 1.x
1.x answered cart.js, cart/add.js, cart/update.js and products/{handle}.js. 2.0 adds every other endpoint in the table, and Shopify.actions.updateCart. App proxy paths went through a Smootify server route in 1.x; that route is switched off for good, so these calls already failed there.