Render pages on demand
On this page
Every storefront page can be fetched as JSON instead of HTML, and every form endpoint accepts a JSON request in place of a normal submit. Use this from your theme’s JavaScript to update part of a page, or to add to the cart, without a full reload.
This needs JavaScript in your theme. See Adding custom JavaScript, CSS and head content for where it goes.
Fetch a page as JSON
Send the request with Accept: application/json. The response carries the rendered page HTML plus any flash messages, rather than a full document.
```javascript
fetch(‘/products/example-product’, { headers: { ‘Accept’: ‘application/json’ } }) .then(response => response.json()) .then(data => { document.querySelector(‘#SC-Main’).innerHTML = data.html; }) .catch(error => console.error(‘Error fetching JSON:’, error)); ```
The response has this shape:
```json
{ “html”: “<div>…</div>”, “flash”: {}, “alert”: null, “notice”: null } ```
alert and notice carry the same messages the page would have shown in its flash bar.
Post to a store endpoint
Any endpoint a storefront form posts to accepts the same request as JSON. Two things are required on every non-GET request:
- The
X-CSRF-Tokenheader, read from the<meta name="csrf-token">tag the layout renders on every page. Without it the request is rejected. Accept: application/json, so the response comes back as JSON rather than a redirect.
Add-to-cart posts to /products/<product-id>/add, where <product-id> is the product’s Salesforce record ID (product.id in Liquid), with quantity in the body:
```javascript
const csrfToken = document.querySelector(‘meta[name=”csrf-token”]’).getAttribute(‘content’);
fetch(‘/products/01tAAAAREPLACEAAAA/add’, { method: ‘POST’, headers: { ‘Accept’: ‘application/json’, ‘Content-Type’: ‘application/json’, ‘X-CSRF-Token’: csrfToken }, body: JSON.stringify({ quantity: 1 }) }) .then(response => response.json()) .then(data => { console.log(data.notice); // “Example Product added to your cart” }) .catch(error => console.error(‘Error adding to cart:’, error)); ```
A successful post returns where the browser would have been sent, plus the flash messages:
```json
{ “redirect”: “/products/example-product”, “flash”: {}, “alert”: null, “notice”: “Example Product added to your cart”, “data”: {} } ```
On failure, alert holds the reason, such as an out-of-stock message, and redirect points back to the product page.
Replace 01tAAAAREPLACEAAAA with a real product ID. For a product with variants, post the variant’s ID; posting the master returns an alert asking the customer to choose an option. Add after=cart or after=checkout to the body to change the redirect value to the cart or checkout page.
:::note
The store’s own add-to-cart form posts to the same endpoint with the same fields, so the JSON version behaves identically, including stock checks, pricing, and any product form questions.
:::
Keep it fast
Every JSON request renders a page server-side, so each one costs about as much as a page load. Fetch only what an interaction needs, and reuse the response rather than requesting again. A page that fires several requests on load, or on every keystroke, gets slower than the reload it was meant to avoid, and can block actions such as proceeding to checkout.
Keep the JavaScript in a theme asset rather than inline in a content block, so it is versioned with the theme and easier to debug.
Requests only work from pages served by your store, because the CSRF token is tied to the visitor’s session. Calling these endpoints from another site or a server needs the API instead.
Was this article helpful?
Thanks for your feedback! It helps us improve our docs.