# Add-to-cart links and buttons that keep the cart

> Add a product to the Shopify cart without emptying it: the product form to /cart/add with return_to, the /cart/add.js call, and when to use a permalink instead.

Page: https://lintelagency.com/resources/shopify-tricks/add-to-cart-links

## In short

Shopify documents two ways to add to the existing cart: a product form that posts to /cart/add, and a POST to /cart/add.js from JavaScript. By default the form then opens the cart page; return_to: 'back' keeps the customer where they were. For a plain link in an email or bio, use a cart permalink (/cart/<variant ID>:<quantity>), which replaces the cart and opens checkout.

## Facts

- **Form:** POST /cart/add ({% form 'product' %})
- **JavaScript:** POST /cart/add.js with items[]
- **Plain link:** Cart permalink /cart/<variant ID>:<qty>
- **Plan:** Every plan
- **Last checked:** 2 November 2026

## How to use it

1. Inside a product template or section, wrap a hidden id input (the variant ID) and a quantity in {% form 'product', product %}.
2. Add return_to: 'back' to the form tag to send the customer back to the same page instead of the cart.
3. For an add without a page load, POST JSON {items: [{id, quantity}]} to Shopify.routes.root + 'cart/add.js' and handle a 422 response.
4. For a link that works anywhere, build a cart permalink instead and test it in a private window.

## Code

### add-to-cart.js

```js
// Adds a variant to the cart without leaving the page. Shopify.routes.root
// keeps the market's language path (/fr/), so prices stay in the right currency.
async function addToCart(variantId, quantity = 1) {
  const response = await fetch(window.Shopify.routes.root + 'cart/add.js', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({items: [{id: variantId, quantity}]}),
  });
  const data = await response.json();
  // 422 means sold out, not enough stock, or an unknown variant ID.
  if (!response.ok) throw new Error(data.description || 'Could not add to cart');
  return data;
}

addToCart(12345678901234, 2).catch((error) => console.error(error.message));
```

### add-to-cart-form.liquid

```liquid
{%- comment -%}
  An add-to-cart button that keeps the customer on the page they were on.
  The product form posts to /cart/add, which adds to the existing cart
  (a /cart/ permalink replaces it). return_to: 'back' overrides the default
  redirect to the cart page.
{%- endcomment -%}
{%- assign variant = product.selected_or_first_available_variant -%}
{% form 'product', product, return_to: 'back' %}
  <input type="hidden" name="id" value="{{ variant.id }}">
  <input type="hidden" name="quantity" value="1">
  <button type="submit" {% unless variant.available %}disabled{% endunless %}>
    Add to cart
  </button>
{% endform %}
```

### which-link.txt

```text
# Documented ways to add to the cart
POST /cart/add          product form ({% form 'product' %}), adds to the existing cart
POST /cart/add.js       Ajax Cart API, returns JSON, adds to the existing cart
GET  /cart/<variant ID>:<qty>   cart permalink, replaces the cart and opens checkout

# A plain link someone can click (email, chat, social bio): use a cart permalink
https://your-store.com/cart/12345678901234:1

# Same, but open the cart page instead of checkout
https://your-store.com/cart/12345678901234:1?storefront=true

# return_to values for the product form
return_to: 'back'                 the page the customer was on
return_to: '/collections/all'     a relative path
return_to: routes.root_url        a routes attribute
```

## Limits and edge cases

- A GET link such as /cart/add?id=…&quantity=… isn’t part of Shopify’s documentation, so don’t rely on it in campaigns; cart permalinks are the documented link format.
- If you ask for more than is in stock, /cart/add.js adds what’s available and returns a 422 error with a description.
- Changes made through the GraphQL Admin API can take a few seconds to reach the Cart API; a brand-new variant may return “Cannot find variant” at first.
- Build the URL from Shopify.routes.root or routes.cart_add_url so a visitor on /fr/ keeps their language and currency.

## Questions

### How do I add to cart in Shopify without going to the cart page?

Add return_to: 'back' to the product form tag, or send the request to /cart/add.js with JavaScript and update the cart drawer yourself.

### Does /cart/add?id= work as a link?

Shopify documents /cart/add as a form POST and /cart/add.js as a JSON POST, not as a GET link. For a clickable link, use a cart permalink like /cart/12345678901234:1.

### What is the difference between /cart/add and a cart permalink?

/cart/add adds to the cart the customer already has. A cart permalink replaces the cart with the items in the link and opens checkout (or the cart page with ?storefront=true).

### Can I add several products at once?

Yes. /cart/add.js takes an items array with one object per variant, and a cart permalink takes comma-separated variant:quantity pairs.

## Shopify documentation

- [Cart API reference](https://shopify.dev/docs/api/ajax/reference/cart)
- [Liquid form tag (return_to)](https://shopify.dev/docs/api/liquid/tags/form)
- [Create cart permalinks](https://shopify.dev/docs/apps/build/checkout/create-cart-permalinks)

## Related

- [Cart permalinks: links that fill the cart](https://lintelagency.com/resources/shopify-tricks/cart-permalinks)
- [Get a variant ID from the storefront](https://lintelagency.com/resources/shopify-tricks/variant-id-from-storefront)
- [Sticky add to cart Shopify section](https://lintelagency.com/resources/shopify-sections/sticky-add-to-cart)
- [Create a cart and add items with the Storefront API](https://lintelagency.com/resources/graphql/storefront-cart-create-and-add)

License: MIT. Free to use in your own store and in client work.
